blob: 86e392f4501ff9e4e816fa984efcdce1e3c2c560 [file] [log] [blame]
/*
* Copyright 2018-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.pipelines.basic;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.Sets;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.onlab.util.ImmutableByteSequence;
import org.onlab.util.SharedExecutors;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.inbandtelemetry.api.IntConfig;
import org.onosproject.inbandtelemetry.api.IntIntent;
import org.onosproject.inbandtelemetry.api.IntObjective;
import org.onosproject.inbandtelemetry.api.IntProgrammable;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Port;
import org.onosproject.net.PortNumber;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.driver.AbstractHandlerBehaviour;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.criteria.IPCriterion;
import org.onosproject.net.flow.criteria.PiCriterion;
import org.onosproject.net.flow.criteria.TcpPortCriterion;
import org.onosproject.net.flow.criteria.UdpPortCriterion;
import org.onosproject.net.host.HostService;
import org.onosproject.net.pi.model.PiActionId;
import org.onosproject.net.pi.model.PiMatchFieldId;
import org.onosproject.net.pi.model.PiTableId;
import org.onosproject.net.pi.runtime.PiAction;
import org.onosproject.net.pi.runtime.PiActionParam;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import static org.slf4j.LoggerFactory.getLogger;
public class IntProgrammableImpl extends AbstractHandlerBehaviour implements IntProgrammable {
// TODO: change this value to the value of diameter of a network.
private static final int MAXHOP = 64;
private static final int PORTMASK = 0xffff;
private static final int IDLE_TIMEOUT = 100;
private static final int PKT_INSTANCE_TYPE_INGRESS_CLONE = 1;
// Application name of the pipeline which adds this implementation to the pipeconf
private static final String PIPELINE_APP_NAME = "org.onosproject.pipelines.basic";
private final Logger log = getLogger(getClass());
private ApplicationId appId;
private static final Set<Criterion.Type> SUPPORTED_CRITERION = Sets.newHashSet(
Criterion.Type.IPV4_DST, Criterion.Type.IPV4_SRC,
Criterion.Type.UDP_SRC, Criterion.Type.UDP_DST,
Criterion.Type.TCP_SRC, Criterion.Type.TCP_DST,
Criterion.Type.IP_PROTO);
@Reference(cardinality = ReferenceCardinality.MANDATORY)
private FlowRuleService flowRuleService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
private DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
private HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
private CoreService coreService;
private DeviceId deviceId;
private static final int DEFAULT_PRIORITY = 10000;
private static final ImmutableBiMap<Integer, PiActionId> INST_0003_ACTION_MAP =
ImmutableBiMap.<Integer, PiActionId>builder()
.put(0, IntConstants.ACT_INT_SET_HEADER_0003_I0_ID)
.put(1, IntConstants.ACT_INT_SET_HEADER_0003_I1_ID)
.put(2, IntConstants.ACT_INT_SET_HEADER_0003_I2_ID)
.put(3, IntConstants.ACT_INT_SET_HEADER_0003_I3_ID)
.put(4, IntConstants.ACT_INT_SET_HEADER_0003_I4_ID)
.put(5, IntConstants.ACT_INT_SET_HEADER_0003_I5_ID)
.put(6, IntConstants.ACT_INT_SET_HEADER_0003_I6_ID)
.put(7, IntConstants.ACT_INT_SET_HEADER_0003_I7_ID)
.put(8, IntConstants.ACT_INT_SET_HEADER_0003_I8_ID)
.put(9, IntConstants.ACT_INT_SET_HEADER_0003_I9_ID)
.put(10, IntConstants.ACT_INT_SET_HEADER_0003_I10_ID)
.put(11, IntConstants.ACT_INT_SET_HEADER_0003_I11_ID)
.put(12, IntConstants.ACT_INT_SET_HEADER_0003_I12_ID)
.put(13, IntConstants.ACT_INT_SET_HEADER_0003_I13_ID)
.put(14, IntConstants.ACT_INT_SET_HEADER_0003_I14_ID)
.put(15, IntConstants.ACT_INT_SET_HEADER_0003_I15_ID)
.build();
private static final ImmutableBiMap<Integer, PiActionId> INST_0407_ACTION_MAP =
ImmutableBiMap.<Integer, PiActionId>builder()
.put(0, IntConstants.ACT_INT_SET_HEADER_0407_I0_ID)
.put(1, IntConstants.ACT_INT_SET_HEADER_0407_I1_ID)
.put(2, IntConstants.ACT_INT_SET_HEADER_0407_I2_ID)
.put(3, IntConstants.ACT_INT_SET_HEADER_0407_I3_ID)
.put(4, IntConstants.ACT_INT_SET_HEADER_0407_I4_ID)
.put(5, IntConstants.ACT_INT_SET_HEADER_0407_I5_ID)
.put(6, IntConstants.ACT_INT_SET_HEADER_0407_I6_ID)
.put(7, IntConstants.ACT_INT_SET_HEADER_0407_I7_ID)
.put(8, IntConstants.ACT_INT_SET_HEADER_0407_I8_ID)
.put(9, IntConstants.ACT_INT_SET_HEADER_0407_I9_ID)
.put(10, IntConstants.ACT_INT_SET_HEADER_0407_I10_ID)
.put(11, IntConstants.ACT_INT_SET_HEADER_0407_I11_ID)
.put(12, IntConstants.ACT_INT_SET_HEADER_0407_I12_ID)
.put(13, IntConstants.ACT_INT_SET_HEADER_0407_I13_ID)
.put(14, IntConstants.ACT_INT_SET_HEADER_0407_I14_ID)
.put(15, IntConstants.ACT_INT_SET_HEADER_0407_I15_ID)
.build();
@Override
public void init() {
deviceId = this.data().deviceId();
flowRuleService = handler().get(FlowRuleService.class);
deviceService = handler().get(DeviceService.class);
hostService = handler().get(HostService.class);
coreService = handler().get(CoreService.class);
appId = coreService.getAppId(PIPELINE_APP_NAME);
if (appId == null) {
log.warn("Application ID is null. Cannot initialize INT-pipeline.");
return;
}
Set<PortNumber> hostPorts = deviceService.getPorts(deviceId).stream().filter(port ->
hostService.getConnectedHosts(new ConnectPoint(deviceId, port.number())).size() > 0
).map(Port::number).collect(Collectors.toSet());
List<FlowRule> flowRules = new ArrayList<>();
// process_int_transit.tb_int_insert
PiActionParam transitIdParam = new PiActionParam(
IntConstants.ACT_PRM_SWITCH_ID,
ImmutableByteSequence.copyFrom(
Integer.parseInt(deviceId.toString().substring(
deviceId.toString().length() - 2))));
PiAction transitAction = PiAction.builder()
.withId(IntConstants.ACT_INT_TRANSIT_ID)
.withParameter(transitIdParam)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.piTableAction(transitAction)
.build();
FlowRule transitFlowRule = DefaultFlowRule.builder()
.withTreatment(treatment)
.fromApp(appId)
.withPriority(DEFAULT_PRIORITY)
.makePermanent()
.forDevice(deviceId)
.forTable(IntConstants.TBL_INT_INSERT_ID)
.build();
flowRules.add(transitFlowRule);
for (PortNumber portNumber: hostPorts) {
// process_set_source_sink.tb_set_source for each host-facing port
PiCriterion ingressCriterion = PiCriterion.builder()
.matchExact(BasicConstants.HDR_IN_PORT_ID, portNumber.toLong())
.build();
TrafficSelector srcSelector = DefaultTrafficSelector.builder()
.matchPi(ingressCriterion)
.build();
PiAction setSourceAct = PiAction.builder()
.withId(IntConstants.ACT_INT_SET_SOURCE_ID)
.build();
TrafficTreatment srcTreatment = DefaultTrafficTreatment.builder()
.piTableAction(setSourceAct)
.build();
FlowRule srcFlowRule = DefaultFlowRule.builder()
.withSelector(srcSelector)
.withTreatment(srcTreatment)
.fromApp(appId)
.withPriority(DEFAULT_PRIORITY)
.makePermanent()
.forDevice(deviceId)
.forTable(IntConstants.TBL_SET_SOURCE_ID)
.build();
flowRules.add(srcFlowRule);
// process_set_source_sink.tb_set_sink
PiCriterion egressCriterion = PiCriterion.builder()
.matchExact(IntConstants.HDR_OUT_PORT_ID, portNumber.toLong())
.build();
TrafficSelector sinkSelector = DefaultTrafficSelector.builder()
.matchPi(egressCriterion)
.build();
PiAction setSinkAct = PiAction.builder()
.withId(IntConstants.ACT_INT_SET_SINK_ID)
.build();
TrafficTreatment sinkTreatment = DefaultTrafficTreatment.builder()
.piTableAction(setSinkAct)
.build();
FlowRule sinkFlowRule = DefaultFlowRule.builder()
.withSelector(sinkSelector)
.withTreatment(sinkTreatment)
.fromApp(appId)
.withPriority(DEFAULT_PRIORITY)
.makePermanent()
.forDevice(deviceId)
.forTable(IntConstants.TBL_SET_SINK_ID)
.build();
flowRules.add(sinkFlowRule);
}
flowRules.forEach(flowRule -> flowRuleService.applyFlowRules(flowRule));
// Populate tb_int_inst_0003 table
INST_0003_ACTION_MAP.forEach((matchValue, actionId) ->
populateInstTableEntry(IntConstants.TBL_INT_INST_0003_ID,
IntConstants.INT_HDR_INST_MASK_0003_ID,
matchValue,
actionId,
appId));
// Populate tb_int_inst_0407 table
INST_0407_ACTION_MAP.forEach((matchValue, actionId) ->
populateInstTableEntry(IntConstants.TBL_INT_INST_0407_ID,
IntConstants.INT_HDR_INST_MASK_0407_ID,
matchValue,
actionId,
appId));
}
@Override
public CompletableFuture<Boolean> addIntObjective(IntObjective obj) {
// TODO: support different types of watchlist other than flow watchlist
return CompletableFuture.supplyAsync(
() -> processIntObjective(obj, true),
SharedExecutors.getPoolThreadExecutor()
);
}
@Override
public CompletableFuture<Boolean> removeIntObjective(IntObjective obj) {
return CompletableFuture.supplyAsync(
() -> processIntObjective(obj, false),
SharedExecutors.getPoolThreadExecutor()
);
}
@Override
public CompletableFuture<Boolean> setupIntConfig(IntConfig config) {
return CompletableFuture.supplyAsync(
() -> setupIntReportInternal(config),
SharedExecutors.getPoolThreadExecutor()
);
}
private void populateInstTableEntry(PiTableId tableId, PiMatchFieldId matchFieldId,
int matchValue, PiActionId actionId, ApplicationId appId) {
PiCriterion instCriterion = PiCriterion.builder()
.matchExact(matchFieldId, matchValue)
.build();
TrafficSelector instSelector = DefaultTrafficSelector.builder()
.matchPi(instCriterion)
.build();
PiAction instAction = PiAction.builder()
.withId(actionId)
.build();
TrafficTreatment instTreatment = DefaultTrafficTreatment.builder()
.piTableAction(instAction)
.build();
FlowRule instFlowRule = DefaultFlowRule.builder()
.withSelector(instSelector)
.withTreatment(instTreatment)
.withPriority(DEFAULT_PRIORITY)
.makePermanent()
.forDevice(deviceId)
.forTable(tableId)
.fromApp(appId)
.build();
flowRuleService.applyFlowRules(instFlowRule);
}
private FlowRule buildWatchlistEntry(IntObjective obj) {
coreService = handler().get(CoreService.class);
appId = coreService.getAppId(PIPELINE_APP_NAME);
if (appId == null) {
log.warn("Application ID is null. Cannot initialize INT-pipeline.");
return null;
}
int instructionBitmap = buildInstructionBitmap(obj.metadataTypes());
PiActionParam maxHopParam = new PiActionParam(
IntConstants.ACT_PRM_MAX_HOP_ID,
ImmutableByteSequence.copyFrom(MAXHOP));
PiActionParam instCntParam = new PiActionParam(
IntConstants.ACT_PRM_INS_CNT_ID,
ImmutableByteSequence.copyFrom(Integer.bitCount(instructionBitmap)));
PiActionParam inst0003Param = new PiActionParam(
IntConstants.ACT_PRM_INS_MASK0003_ID,
ImmutableByteSequence.copyFrom((instructionBitmap >> 12) & 0xF));
PiActionParam inst0407Param = new PiActionParam(
IntConstants.ACT_PRM_INS_MASK0407_ID,
ImmutableByteSequence.copyFrom((instructionBitmap >> 8) & 0xF));
PiAction intSourceAction = PiAction.builder()
.withId(IntConstants.ACT_INT_SOURCE_DSCP_ID)
.withParameter(maxHopParam)
.withParameter(instCntParam)
.withParameter(inst0003Param)
.withParameter(inst0407Param)
.build();
TrafficTreatment instTreatment = DefaultTrafficTreatment.builder()
.piTableAction(intSourceAction)
.build();
TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
for (Criterion criterion : obj.selector().criteria()) {
switch (criterion.type()) {
case IPV4_SRC:
sBuilder.matchIPSrc(((IPCriterion) criterion).ip());
break;
case IPV4_DST:
sBuilder.matchIPDst(((IPCriterion) criterion).ip());
break;
case TCP_SRC:
sBuilder.matchPi(
PiCriterion.builder().matchTernary(
IntConstants.LOCAL_META_SRC_PORT_ID,
((TcpPortCriterion) criterion).tcpPort().toInt(), PORTMASK)
.build());
break;
case UDP_SRC:
sBuilder.matchPi(
PiCriterion.builder().matchTernary(
IntConstants.LOCAL_META_SRC_PORT_ID,
((UdpPortCriterion) criterion).udpPort().toInt(), PORTMASK)
.build());
break;
case TCP_DST:
sBuilder.matchPi(
PiCriterion.builder().matchTernary(
IntConstants.LOCAL_META_DST_PORT_ID,
((TcpPortCriterion) criterion).tcpPort().toInt(), PORTMASK)
.build());
break;
case UDP_DST:
sBuilder.matchPi(
PiCriterion.builder().matchTernary(
IntConstants.LOCAL_META_DST_PORT_ID,
((UdpPortCriterion) criterion).udpPort().toInt(), PORTMASK)
.build());
break;
default:
log.warn("Unsupported criterion type: {}", criterion.type());
}
}
return DefaultFlowRule.builder()
.forDevice(this.data().deviceId())
.withSelector(sBuilder.build())
.withTreatment(instTreatment)
.withPriority(DEFAULT_PRIORITY)
.forTable(IntConstants.TBL_INT_SOURCE_ID)
.fromApp(appId)
.withIdleTimeout(IDLE_TIMEOUT)
.build();
}
private int buildInstructionBitmap(Set<IntIntent.IntMetadataType> metadataTypes) {
int instBitmap = 0;
for (IntIntent.IntMetadataType metadataType : metadataTypes) {
switch (metadataType) {
case SWITCH_ID:
instBitmap |= (1 << 15);
break;
case L1_PORT_ID:
instBitmap |= (1 << 14);
break;
case HOP_LATENCY:
instBitmap |= (1 << 13);
break;
case QUEUE_OCCUPANCY:
instBitmap |= (1 << 12);
break;
case INGRESS_TIMESTAMP:
instBitmap |= (1 << 11);
break;
case EGRESS_TIMESTAMP:
instBitmap |= (1 << 10);
break;
case L2_PORT_ID:
instBitmap |= (1 << 9);
break;
case EGRESS_TX_UTIL:
instBitmap |= (1 << 8);
break;
default:
log.info("Unsupported metadata type {}. Ignoring...", metadataType);
break;
}
}
return instBitmap;
}
/**
* Returns a subset of Criterion from given selector,
* which is unsupported by this INT pipeline.
*
* @param selector a traffic selector
* @return a subset of Criterion from given selector, unsupported by this INT pipeline,
* empty if all criteria are supported.
*/
private Set<Criterion> unsupportedSelectors(TrafficSelector selector) {
return selector.criteria().stream()
.filter(criterion -> !SUPPORTED_CRITERION.contains(criterion.type()))
.collect(Collectors.toSet());
}
private boolean processIntObjective(IntObjective obj, boolean install) {
flowRuleService = handler().get(FlowRuleService.class);
deviceId = this.data().deviceId();
if (install && !unsupportedSelectors(obj.selector()).isEmpty()) {
log.warn("Device {} does not support criteria {} for INT.",
deviceId, unsupportedSelectors(obj.selector()));
return false;
}
FlowRule flowRule = buildWatchlistEntry(obj);
if (flowRule != null) {
if (install) {
flowRuleService.applyFlowRules(flowRule);
} else {
flowRuleService.removeFlowRules(flowRule);
}
log.debug("IntObjective {} has been {} {}",
obj, install ? "installed to" : "removed from", deviceId);
return true;
} else {
log.warn("Failed to {} IntObjective {} on {}",
install ? "install" : "remove", obj, deviceId);
return false;
}
}
private boolean setupIntReportInternal(IntConfig cfg) {
flowRuleService = handler().get(FlowRuleService.class);
FlowRule reportRule = buildReportEntry(cfg, PKT_INSTANCE_TYPE_INGRESS_CLONE);
if (reportRule != null) {
flowRuleService.applyFlowRules(reportRule);
log.info("Report entry {} has been added to {}", reportRule, this.data().deviceId());
return true;
} else {
log.warn("Failed to add report entry on {}", this.data().deviceId());
return false;
}
}
private FlowRule buildReportEntry(IntConfig cfg, int type) {
coreService = handler().get(CoreService.class);
appId = coreService.getAppId(PIPELINE_APP_NAME);
if (appId == null) {
log.warn("Application ID is null. Cannot build report entry.");
return null;
}
PiCriterion instTypeCriterion = PiCriterion.builder()
.matchExact(IntConstants.STD_META_INSTANCE_TYPE_ID, type)
.build();
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchPi(instTypeCriterion)
.build();
PiActionParam srcMacParam = new PiActionParam(
IntConstants.ACT_PRM_SRC_MAC_ID,
ImmutableByteSequence.copyFrom(cfg.sinkMac().toBytes()));
PiActionParam nextHopMacParam = new PiActionParam(
IntConstants.ACT_PRM_MON_MAC_ID,
ImmutableByteSequence.copyFrom(cfg.collectorNextHopMac().toBytes()));
PiActionParam srcIpParam = new PiActionParam(
IntConstants.ACT_PRM_SRC_IP_ID,
ImmutableByteSequence.copyFrom(cfg.sinkIp().toOctets()));
PiActionParam monIpParam = new PiActionParam(
IntConstants.ACT_PRM_MON_IP_ID,
ImmutableByteSequence.copyFrom(cfg.collectorIp().toOctets()));
PiActionParam monPortParam = new PiActionParam(
IntConstants.ACT_PRM_MON_PORT_ID,
ImmutableByteSequence.copyFrom(cfg.collectorPort().toInt()));
PiAction reportAction = PiAction.builder()
.withId(IntConstants.ACT_DO_REPORT_ENCAP_ID)
.withParameter(srcMacParam)
.withParameter(nextHopMacParam)
.withParameter(srcIpParam)
.withParameter(monIpParam)
.withParameter(monPortParam)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.piTableAction(reportAction)
.build();
return DefaultFlowRule.builder()
.withSelector(selector)
.withTreatment(treatment)
.fromApp(appId)
.withPriority(DEFAULT_PRIORITY)
.makePermanent()
.forDevice(this.data().deviceId())
.forTable(IntConstants.TBL_GENERATE_REPORT_ID)
.build();
}
}