blob: 187ef190e00231c63418d84e61aa669096dad983 [file] [log] [blame]
/*
* Copyright 2017-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.imr;
import com.google.common.collect.ImmutableSet;
import org.apache.commons.lang3.tuple.Pair;
import org.onlab.util.KryoNamespace;
import org.onosproject.core.ApplicationId;
import org.onosproject.imr.data.Path;
import org.onosproject.imr.data.Route;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.ElementId;
import org.onosproject.net.FilteredConnectPoint;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.net.Link;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.FlowEntry;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleEvent;
import org.onosproject.net.flow.FlowRuleListener;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.Instructions;
import org.onosproject.net.host.HostService;
import org.onosproject.net.intent.ConnectivityIntent;
import org.onosproject.net.intent.FlowRuleIntent;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentEvent;
import org.onosproject.net.intent.IntentListener;
import org.onosproject.net.intent.IntentService;
import org.onosproject.net.intent.IntentState;
import org.onosproject.net.intent.Key;
import org.onosproject.net.intent.LinkCollectionIntent;
import org.onosproject.net.intent.PointToPointIntent;
import org.onosproject.net.link.LinkService;
import org.onosproject.net.statistic.FlowStatisticStore;
import org.onosproject.store.serializers.KryoNamespaces;
import org.onosproject.store.service.ConsistentMap;
import org.onosproject.store.service.DistributedSet;
import org.onosproject.store.service.Serializer;
import org.onosproject.store.service.StorageService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Manager of Intent Monitor and Reroute.
*/
@Component(immediate = true, service = IntentMonitorAndRerouteService.class)
public class IntentMonitorAndRerouteManager implements IntentMonitorAndRerouteService {
private final Logger log = LoggerFactory.getLogger(getClass());
private ConsistentMap<ApplicationId, Map<Key, ConnectivityIntent>> monitoredIntentsDistr;
private Map<ApplicationId, Map<Key, ConnectivityIntent>> monitoredIntents;
private DistributedSet<Key> toBeMonitoredIntents;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected IntentService intentService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected FlowRuleService flowRuleService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected LinkService linkService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected FlowStatisticStore statsStore;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected StorageService storageService;
private InternalIntentListener intentListener = new InternalIntentListener();
private InternalFlowRuleListener flowRuleListener = new InternalFlowRuleListener();
@Activate
protected void activate() {
intentService.addListener(intentListener);
flowRuleService.addListener(flowRuleListener);
monitoredIntentsDistr = storageService
.<ApplicationId, Map<Key, ConnectivityIntent>>consistentMapBuilder()
.withSerializer(Serializer.using(KryoNamespaces.API))
.withName("IMR-monitoredIntents")
.build();
monitoredIntents = monitoredIntentsDistr.asJavaMap();
toBeMonitoredIntents = storageService.<Key>setBuilder()
.withSerializer(Serializer.using(
new KryoNamespace.Builder()
.register(KryoNamespaces.API)
.register(Key.class)
.build()))
.withName("IMR-toMonitorIntents")
.build()
.asDistributedSet();
log.info("IntentMonitorAndReroute activated");
}
@Deactivate
protected void deactivate() {
intentService.removeListener(intentListener);
flowRuleService.removeListener(flowRuleListener);
monitoredIntents
.forEach(((applicationId, keyConnectivityIntentMap) ->
keyConnectivityIntentMap.keySet()
.forEach(this::removeIntent)));
log.info("IntentMonitorAndReroute deactivated");
}
private synchronized void storeMonitoredIntent(ConnectivityIntent intent) {
log.debug("Store Monitored Intent {}", intent.key());
Map<Key, ConnectivityIntent> temp = monitoredIntents.getOrDefault(intent.appId(), new ConcurrentHashMap<>());
temp.put(intent.key(), intent);
monitoredIntents.put(intent.appId(), temp);
}
@Override
public synchronized boolean startMonitorIntent(Key intentKey) {
checkNotNull(intentKey, "Intent Key must not be null");
log.debug("Start Monitor Intent: {}", intentKey.toString());
toBeMonitoredIntents.add(intentKey);
//Check if the requested intent is already present in the intent manager
Intent installedIntent = intentService.getIntent(intentKey);
if (!allowedIntent(installedIntent)) {
return false;
}
//Check if the intent that is present in the intent subsystem is already installed
if (intentService.getIntentState(intentKey) == IntentState.INSTALLED) {
storeMonitoredIntent((ConnectivityIntent) installedIntent);
}
return true;
}
/**
* Returns whether the intent can be monitored or not.
* @param intent The intent you want to check if it is allowed to be monitored.
* @return true if the intent's type is of one of the allowed types
* ({@link LinkCollectionIntent}, {@link PointToPointIntent}).
*/
public boolean allowedIntent(Intent intent) {
return intent instanceof LinkCollectionIntent || intent instanceof PointToPointIntent;
}
@Override
public synchronized boolean stopMonitorIntent(Key intentKey) {
checkNotNull(intentKey, "Intent key must not be null");
log.debug("Stop Monitor Intent: ", intentKey.toString());
if (!toBeMonitoredIntents.contains(intentKey)) {
return false;
}
removeIntent(intentKey);
toBeMonitoredIntents.remove(intentKey);
return true;
}
/**
* Removes the intent from the internal structure.
* @param intentKey Key of the intent to be removed.
* @return true if the intent is found and removed, false otherwise.
*/
private synchronized boolean removeIntent(Key intentKey) {
for (Map.Entry<ApplicationId, Map<Key, ConnectivityIntent>> appIntents
: monitoredIntents.entrySet()) {
if (appIntents.getValue().containsKey(intentKey)) {
appIntents.getValue().remove(intentKey);
//TODO: check if it works without reputting the map
flushIntentStatStore(intentKey);
monitoredIntents.put(appIntents.getKey(), appIntents.getValue());
if (appIntents.getValue().isEmpty()) {
monitoredIntents.remove(appIntents.getKey());
}
return true;
}
}
return false;
}
/**
* Flushes the statistics (from the statistics store) of an intent.
* @param intentKey Key of the intent which statistics has to be cleaned.
*/
private synchronized void flushIntentStatStore(Key intentKey) {
checkNotNull(intentKey);
//Remove all the flow rule on the stats store related to the passed intentKey
intentService.getInstallableIntents(intentKey)
.stream()
.map(intent -> (FlowRuleIntent) intent)
.forEach(intent -> intent.flowRules()
.forEach(flowRule -> statsStore.removeFlowStatistic(flowRule))
);
}
/**
* Generates a new {@Link LinkCollectionIntent} applying the new path.
* @param links List of links of the new path.
* @param intentKey Key of the intent you want to re-route.
* @param appId Application id that submits initially the intent.
* @return The new intent, if not possibile it will return the old intent already installed.
*/
private ConnectivityIntent generateLinkCollectionIntent(
List<Link> links,
Key intentKey,
ApplicationId appId) {
checkNotNull(links);
checkNotNull(appId);
// Gets the oldIntent already installed
ConnectivityIntent oldIntent = monitoredIntents.get(appId).get(intentKey);
//Flush the statistics of the currently installed intent
flushIntentStatStore(intentKey);
//get the connect point of the old intent
// Left element of the Pair is the ingress, right one is the egress
Pair<Set<FilteredConnectPoint>, Set<FilteredConnectPoint>> cpPair = extractEndConnectPoints(oldIntent);
if (cpPair == null) {
return oldIntent;
}
// Now generate the new intent
LinkCollectionIntent newIntent = LinkCollectionIntent.builder()
.appId(oldIntent.appId())
.key(intentKey)
.selector(oldIntent.selector())
.filteredIngressPoints(ImmutableSet.copyOf(cpPair.getLeft()))
.filteredEgressPoints(ImmutableSet.copyOf(cpPair.getRight()))
.treatment(oldIntent.treatment())
.priority(oldIntent.priority())
.constraints(oldIntent.constraints())
.links(ImmutableSet.copyOf(links))
//TODO: is there a way to get from the old intent?
.applyTreatmentOnEgress(true)
.build();
return newIntent;
}
@Override
public boolean applyPath(Route route) {
checkNotNull(route, "Route to apply must be not null");
checkNotNull(route.appId(), "Application id must be not null");
checkNotNull(route.key(), "Intent key to apply must be not null");
checkNotNull(route.paths(), "New path must be not null");
checkArgument(route.paths().size() >= 1);
ApplicationId appId = route.appId();
Key key = route.key();
// check if the app and the intent key are monitored
if (!monitoredIntents.containsKey(appId)) {
return false;
}
if (!monitoredIntents.get(appId).containsKey(key)) {
return false;
}
// TODO: now we manage only the unsplittable routing
Path currentPath = route.paths()
.stream()
.max(Comparator.comparing(Path::weight))
.get();
// Check if the last and first element of the path are HostId
// in this case remove them from the list
if (currentPath.path().get(0) instanceof HostId) {
currentPath.path().remove(0);
}
if (currentPath.path().get(currentPath.path().size() - 1) instanceof HostId) {
currentPath.path().remove(currentPath.path().size() - 1);
}
List<Link> links = createPathFromDeviceList(currentPath.path());
// Generate the new Link collection intent, if not possible it will return the old intent
ConnectivityIntent intent = generateLinkCollectionIntent(links, key, appId);
storeMonitoredIntent(intent);
intentService.submit(intent);
return true;
}
@Override
public Map<ApplicationId, Map<Key, List<FlowEntry>>> getStats() {
//TODO: check if there is a better way to get the statistics
Map<ApplicationId, Map<Key, List<FlowEntry>>> currentStatistics = new HashMap<>();
monitoredIntents.forEach((appId, mapIntentKey) ->
currentStatistics.putAll(getStats(appId))
);
return currentStatistics;
}
@Override
public Map<ApplicationId, Map<Key, List<FlowEntry>>> getStats(ApplicationId appId) {
checkNotNull(appId);
//TODO: is there a better way to get statistics?
Map<ApplicationId, Map<Key, List<FlowEntry>>> currentStatistics = new HashMap<>();
currentStatistics.put(appId, new HashMap<>());
if (monitoredIntents.containsKey(appId)) {
Set<Key> keySet = monitoredIntents.get(appId).keySet();
for (Key intentKey : keySet) {
List<FlowEntry> flowEntries = getStats(intentKey);
currentStatistics.get(appId).put(intentKey, flowEntries);
}
}
return currentStatistics;
}
@Override
public Map<ApplicationId, Map<Key, List<FlowEntry>>> getStats(ApplicationId appId, Key intentKey) {
checkNotNull(appId);
checkNotNull(intentKey);
checkArgument(monitoredIntents.containsKey(appId));
checkArgument(monitoredIntents.get(appId).containsKey(intentKey));
Map<ApplicationId, Map<Key, List<FlowEntry>>> currentStatistics = new HashMap<>();
currentStatistics.put(appId, new HashMap<>());
List<FlowEntry> flowEntries = getStats(intentKey);
currentStatistics.get(appId).put(intentKey, flowEntries);
return currentStatistics;
}
/**
* Returns the list of flow entries of a particular intent.
* @param intentKey
* @return List of the flow entries of the specified intent,
* it contains all the statistics of that intent.
*/
private List<FlowEntry> getStats(Key intentKey) {
List<FlowEntry> currentStatistics = new LinkedList<>();
intentService.getInstallableIntents(intentKey)
.forEach(intent -> ((FlowRuleIntent) intent).flowRules()
.forEach(flowRule -> {
ConnectPoint cp = buildConnectPoint(flowRule);
currentStatistics.addAll(getStats(cp, flowRule));
})
);
return currentStatistics;
}
/**
* Returns a list of flow entry related to the connect point and flow rule passed.
* @param cp ConnectPoint we want to retrieve the flow entry from.
* @param flowRule FlowRule.
* @return List of flow entries.
*/
private List<FlowEntry> getStats(ConnectPoint cp, FlowRule flowRule) {
return statsStore.getCurrentFlowStatistic(cp)
.stream()
.filter(flowEntry -> flowEntry
.id()
.equals(flowRule.id()))
.collect(Collectors.toList());
}
/**
* Returns a list of links starting from a list of devices.
* @param deviceList List of devices.
* @return A path in terms of list of links.
*/
private List<Link> createPathFromDeviceList(List<ElementId> deviceList) {
List<Link> path = new ArrayList<>();
if (deviceList.size() == 1) {
return path;
}
// Left element represents the input and right the output
List<Pair<DeviceId, DeviceId>> devicePairs = IntStream.
range(0, deviceList.size() - 1)
.mapToObj(i -> Pair.of((DeviceId) deviceList.get(i), (DeviceId) deviceList.get(i + 1)))
.collect(Collectors.toList());
devicePairs.forEach(pair -> {
//TODO use GetPath pair by pair?
// The common Link between DevEgress and DevIngress is the intersection of their links
Set<Link> commonLinks = new HashSet<>(linkService.getDeviceEgressLinks(pair.getLeft()));
commonLinks.retainAll(linkService.getDeviceIngressLinks(pair.getRight()));
if (commonLinks.size() == 0) {
log.error("No link found between node {} and node {}!",
pair.getLeft(), pair.getRight());
} else if (commonLinks.size() == 1) {
path.add(commonLinks.iterator().next());
} else {
//TODO select the one with more bandwidth?
log.warn("{} links found between node {} and node {}: taking the first one!",
commonLinks.size(), pair.getLeft(), pair.getRight());
path.add(commonLinks.iterator().next());
}
});
return path;
}
@Override
public Map<ApplicationId, Map<Key, Pair<Set<ElementId>, Set<ElementId>>>> getMonitoredIntents() {
Map<ApplicationId, Map<Key, Pair<Set<ElementId>, Set<ElementId>>>> currentMonitoredIntents
= new ConcurrentHashMap<>();
monitoredIntents.forEach((appId, appIntents) -> {
currentMonitoredIntents.put(appId, new ConcurrentHashMap<>());
appIntents.forEach((intentKey, intent) -> {
Pair<Set<ElementId>, Set<ElementId>> endPair = extractEndPoints(intent);
if (endPair != null) {
currentMonitoredIntents.get(appId).put(intentKey, endPair);
}
});
});
return currentMonitoredIntents;
}
@Override
public Map<ApplicationId, Map<Key, Pair<Set<ElementId>, Set<ElementId>>>> getMonitoredIntents(
ApplicationId appId) {
Map<ApplicationId, Map<Key, Pair<Set<ElementId>, Set<ElementId>>>> currentMonitoredIntents
= new ConcurrentHashMap<>();
currentMonitoredIntents.put(appId, new ConcurrentHashMap<>());
if (monitoredIntents.containsKey(appId)) {
monitoredIntents.get(appId).forEach((intentKey, intent) -> {
Pair<Set<ElementId>, Set<ElementId>> endPair = extractEndPoints(intent);
if (endPair != null) {
currentMonitoredIntents.get(appId).put(intentKey, endPair);
}
});
}
return currentMonitoredIntents;
}
private Set<ElementId> connectedElements(Set<FilteredConnectPoint> cpSet) {
Set<ElementId> connectedElem = new HashSet<>();
cpSet.forEach(
fcp -> {
Set<Host> connectedHosts = hostService.getConnectedHosts(fcp.connectPoint());
if (connectedHosts.size() == 0) {
// In this case the end point is an ELEMENT without host connected
connectedElem.add(fcp.connectPoint().elementId());
} else {
// In this case we can have a set of hosts connected to that endpoint
connectedElem.addAll(connectedHosts.stream().map(Host::id)
.collect(Collectors.toSet()));
}
}
);
return connectedElem;
}
/**
* Extracts the endpoint from an intent.
* @param intent
* @return {@link Pair} containing in the Left element the set of input {@link ElementId},
* in the Right element the set of output {@link ElementId}.
*/
private Pair<Set<ElementId>, Set<ElementId>> extractEndPoints(Intent intent) {
checkNotNull(intent, "intent must not be null");
Pair<Set<FilteredConnectPoint>, Set<FilteredConnectPoint>> cpPair;
cpPair = extractEndConnectPoints(intent);
if (cpPair == null) {
return null;
}
return Pair.of(connectedElements(cpPair.getLeft()), connectedElements(cpPair.getRight()));
}
/**
* Returns the end connect points of an intent.
* @param intent
* @return {@link Pair} containing in the Left element the input end connect points,
* in the Right element the output end connect points.
*/
private Pair<Set<FilteredConnectPoint>, Set<FilteredConnectPoint>> extractEndConnectPoints(Intent intent) {
checkNotNull(intent, "intent must not be null");
Set<FilteredConnectPoint> inSet = new HashSet<>();
Set<FilteredConnectPoint> outSet = new HashSet<>();
if (intent instanceof PointToPointIntent) {
inSet.add(((PointToPointIntent) intent).filteredIngressPoint());
outSet.add(((PointToPointIntent) intent).filteredEgressPoint());
} else if (intent instanceof LinkCollectionIntent) {
inSet.addAll(((LinkCollectionIntent) intent).filteredIngressPoints());
outSet.addAll(((LinkCollectionIntent) intent).filteredEgressPoints());
}
return Pair.of(inSet, outSet);
}
/**
* Returns the connect point related to the output port of the rule.
* @param rule
* @return
*/
private ConnectPoint buildConnectPoint(FlowRule rule) {
PortNumber port = getOutput(rule);
if (port == null) {
return null;
}
return new ConnectPoint(rule.deviceId(), port);
}
/**
* Returns the output port related to the rule.
* @param rule
* @return
*/
private PortNumber getOutput(FlowRule rule) {
for (Instruction i : rule.treatment().allInstructions()) {
if (i.type() == Instruction.Type.OUTPUT) {
Instructions.OutputInstruction out = (Instructions.OutputInstruction) i;
return out.port();
}
}
return null;
}
private class InternalIntentListener implements IntentListener {
@Override
public void event(IntentEvent event) {
// It receives only events related to ConnectivityIntent to be monitored
Key intentKey = event.subject().key();
switch (event.type()) {
case INSTALLED:
// When an intent is installed and it need to be monitored
// it will pass from the "toBeMonitored" state to the "monitored" state
log.info("Monitored intent INSTALLED");
storeMonitoredIntent((ConnectivityIntent) event.subject());
break;
case WITHDRAWN:
// When an intent is withdrawn
// it will go back from the "monitored" state to the "toBeMonitored"
log.info("Monitored intent WITHDWRAWN");
removeIntent(intentKey);
break;
case FAILED:
log.warn("FAILED event not handled");
break;
default:
log.warn("Unknown intent event");
}
}
@Override
public boolean isRelevant(IntentEvent event) {
/*
* Check if the Intent event is relevant.
* An intent event is relevant if it is of one of the allowed types
* and if it is one of the monitored ones.
*/
Key intentKey = event.subject().key();
return allowedIntent(event.subject())
&& toBeMonitoredIntents.contains(intentKey);
}
}
private class InternalFlowRuleListener implements FlowRuleListener {
@Override
public void event(FlowRuleEvent event) {
FlowRule rule = event.subject();
switch (event.type()) {
case RULE_ADDED:
case RULE_UPDATED:
// In case of rule update, flow statistics are updated
if (rule instanceof FlowEntry) {
statsStore.updateFlowStatistic((FlowEntry) rule);
}
break;
case RULE_REMOVED:
// In case of rule removal, flow statistics are removed from the store
log.info("Rule removed: {}", rule.id());
statsStore.removeFlowStatistic(rule);
break;
default:
log.warn("Unknown flow rule event");
}
}
@Override
public boolean isRelevant(FlowRuleEvent event) {
/*
* Check if the rule event is relevant and it needs to be managed
* A Rule event is relevant if the flow rule it refers to is
* part of one of the monitored intents
*/
FlowRule rule = event.subject();
for (Map.Entry<ApplicationId, Map<Key, ConnectivityIntent>> entry : monitoredIntents.entrySet()) {
for (Key key : entry.getValue().keySet()) {
List<Intent> ints = intentService.getInstallableIntents(key);
for (Intent i : ints) {
if (i instanceof FlowRuleIntent
&& ((FlowRuleIntent) i).flowRules().contains(rule)) {
return true;
}
}
}
}
return false;
}
}
}