blob: e5ca9c528c3683fe5560e302906bc6b21563deaa [file] [log] [blame]
/*
* Copyright 2016 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.ecord.carrierethernet.app;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.util.Bandwidth;
import org.onosproject.ecord.carrierethernet.api.CarrierEthernetPacketNodeService;
import org.onosproject.ecord.carrierethernet.api.CarrierEthernetProvisionerService;
import org.onosproject.net.Link;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Path;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.Device;
import org.onosproject.net.topology.PathService;
import org.onosproject.newoptical.api.OpticalConnectivityId;
import org.onosproject.newoptical.api.OpticalPathEvent;
import org.onosproject.newoptical.api.OpticalPathListener;
import org.onosproject.newoptical.api.OpticalPathService;
import org.onosproject.net.topology.TopologyService;
import org.onosproject.net.DefaultLink;
import org.onosproject.net.provider.ProviderId;
import org.slf4j.Logger;
import java.time.Duration;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Carrier Ethernet provisioner of connectivity for forwarding constructs and bandwidth profiles.
*/
@Component(immediate = true)
@Service
public class CarrierEthernetProvisioner implements CarrierEthernetProvisionerService {
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PathService pathService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected TopologyService topologyService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CarrierEthernetPacketNodeService cePktNodeService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected OpticalPathService opticalPathService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
private OpticalPathListener opticalEventListener = new OpticalEventListener();
private static final int OPTICAL_CONNECT_TIMEOUT_MILLIS = 5000;
// If set to false, the setup of optical connectivity using the metro app is bypassed
// TODO: Use the Component Configuration mechanism to set this parameter
private boolean pktOpticalTopo = false;
// TODO: Refactor this part
private final Map<OpticalConnectivityId, OpticalPathEvent.Type> opticalConnectStatusMap = new ConcurrentHashMap<>();
@Activate
protected void activate() {
opticalPathService.addListener(opticalEventListener);
}
@Deactivate
protected void deactivate() {
opticalPathService.removeListener(opticalEventListener);
}
@Override
public void setupConnectivity(CarrierEthernetForwardingConstruct fc) {
boolean allPairsConnected = true;
HashMap<CarrierEthernetNetworkInterface, HashSet<CarrierEthernetNetworkInterface>> ingressEgressNiMap =
new HashMap<>();
// Temporary set for iterating through LTP pairs
Set<CarrierEthernetLogicalTerminationPoint> tempLtpSet = new HashSet<>(fc.ltpSet());
// Temporary set for indicating which LTPs were finally included
Set<CarrierEthernetLogicalTerminationPoint> usedLtpSet = new HashSet<>();
Iterator<CarrierEthernetLogicalTerminationPoint> ltpIt1 = tempLtpSet.iterator();
while (ltpIt1.hasNext()) {
CarrierEthernetLogicalTerminationPoint ltp1 = ltpIt1.next();
// Iterate through all the remaining NIs
Iterator<CarrierEthernetLogicalTerminationPoint> ltpIt2 = tempLtpSet.iterator();
while (ltpIt2.hasNext()) {
CarrierEthernetLogicalTerminationPoint ltp2 = ltpIt2.next();
// Skip equals
if (ltp1.equals(ltp2)) {
continue;
}
// Do not establish connectivity between leaf NIs (applies to Rooted_Multipoint)
// FIXME: Use proper LTP roles
if (ltp1.role().equals(CarrierEthernetLogicalTerminationPoint.Role.LEAF)
&& ltp2.role().equals(CarrierEthernetLogicalTerminationPoint.Role.LEAF)) {
continue;
}
OpticalConnectivityId opticalConnectId = null;
if (pktOpticalTopo) {
Bandwidth reqBw;
if (ltp1.type().equals(CarrierEthernetNetworkInterface.Type.UNI)) {
reqBw = ((CarrierEthernetUni) ltp1.ni()).bwp().cir();
} else if (ltp2.type().equals(CarrierEthernetNetworkInterface.Type.UNI)) {
reqBw = ((CarrierEthernetUni) ltp2.ni()).bwp().cir();
} else {
reqBw = Bandwidth.bps((double) 0);
}
opticalConnectId = setupOpticalConnectivity(ltp1.ni().cp(), ltp2.ni().cp(), reqBw, fc.maxLatency());
if (opticalConnectId == null ||
opticalConnectStatusMap.get(opticalConnectId) != OpticalPathEvent.Type.PATH_INSTALLED) {
log.error("Could not establish optical connectivity between {} and {}" +
" (optical id and status: {}, {})",
ltp1.ni().cp(), ltp2.ni().cp(), opticalConnectId,
(opticalConnectId == null ? "null" : opticalConnectStatusMap.get(opticalConnectId)));
allPairsConnected = false;
continue;
}
if (opticalConnectId != null) {
fc.setMetroConnectivityId(opticalConnectId);
fc.setMetroConnectivityStatus(opticalConnectStatusMap.get(opticalConnectId));
}
log.info("Metro connectivity id and status for FC {}: {}, {}", fc.id(),
fc.metroConnectivity().id(), fc.metroConnectivity().status());
}
// Update the ingress-egress NI map based on the calculated paths
if (!updateIngressEgressNiMap(ltp1.ni(), ltp2.ni(), ingressEgressNiMap,
fc.congruentPaths(), fc.type())) {
removeOpticalConnectivity(opticalConnectId);
allPairsConnected = false;
continue;
}
// Indicate that connection for at least one NI pair has been established
fc.setState(CarrierEthernetForwardingConstruct.State.ACTIVE);
// Add NIs to the set of NIs used by the EVC
usedLtpSet.add(ltp1);
usedLtpSet.add(ltp2);
}
// Remove NI from temporary set so that each pair is visited only once
ltpIt1.remove();
}
// Establish connectivity using the ingressEgressNiMap
ingressEgressNiMap.keySet().forEach(srcNi -> {
// Set forwarding only on packet switches
if (deviceService.getDevice(srcNi.cp().deviceId()).type().equals(Device.Type.SWITCH)) {
cePktNodeService.setNodeForwarding(fc, srcNi, ingressEgressNiMap.get(srcNi));
}
});
// Update the NI set, based on the NIs actually used
fc.setLtpSet(usedLtpSet);
if (fc.isActive()) {
if (!allPairsConnected) {
fc.setState(CarrierEthernetConnection.State.PARTIAL);
}
}
}
/**
* Select feasible link paths between two NIs in both directions and update
* ingressEgressNiMap accordingly.
*
* @param ni1 the first NI
* @param ni2 the second NI
* @param ingressEgressNiMap the method will add here any ingress-egress NI associations
* @param congruentPaths if true indicates that n1->n2 will follow the same path as n2->n1
* @return true if the path was updated and false if a path could not be found in any of the directions
*/
private boolean updateIngressEgressNiMap(CarrierEthernetNetworkInterface ni1, CarrierEthernetNetworkInterface ni2,
HashMap<CarrierEthernetNetworkInterface,
HashSet<CarrierEthernetNetworkInterface>> ingressEgressNiMap,
boolean congruentPaths, CarrierEthernetVirtualConnection.Type evcType) {
// Find the paths for both directions at the same time, so that we can skip the pair if needed
List<Link> forwardLinks = generateLinkList(ni1.cp(), ni2.cp(), evcType);
List<Link> backwardLinks =
congruentPaths ? generateInverseLinkList(forwardLinks) : generateLinkList(ni2.cp(), ni1.cp(), evcType);
// Skip this UNI pair if no feasible path could be found
if (forwardLinks == null || (backwardLinks == null)) {
log.warn("There are no feasible paths between {} and {}.",
ni1.cp().deviceId(), ni2.cp().deviceId());
return false;
}
// Populate the ingress/egress NI map for the forward and backward paths
populateIngressEgressNiMap(ni1, ni2, forwardLinks, ingressEgressNiMap);
populateIngressEgressNiMap(ni2, ni1, backwardLinks, ingressEgressNiMap);
return true;
}
private void populateIngressEgressNiMap(CarrierEthernetNetworkInterface srcNi,
CarrierEthernetNetworkInterface dstNi,
List<Link> linkList,
HashMap<CarrierEthernetNetworkInterface,
HashSet<CarrierEthernetNetworkInterface>> ingressEgressNiMap
) {
// FIXME: Fix the method - avoid generating GENERIC NIs if not needed
// Add the src and destination NIs as well as the associated Generic NIs
ingressEgressNiMap.putIfAbsent(srcNi, new HashSet<>());
// Add last hop entry only if srcNi, dstNi aren't on same device (in which case srcNi, ingressNi would coincide)
if (!srcNi.cp().deviceId().equals(dstNi.cp().deviceId())) {
// If srcNi, dstNi are not on the same device, create mappings to/from new GENERIC NIs
ingressEgressNiMap.get(srcNi).add(new CarrierEthernetGenericNi(linkList.get(1).src(), null));
CarrierEthernetGenericNi ingressNi =
new CarrierEthernetGenericNi(linkList.get(linkList.size() - 2).dst(), null);
ingressEgressNiMap.putIfAbsent(ingressNi, new HashSet<>());
ingressEgressNiMap.get(ingressNi).add(dstNi);
} else {
// If srcNi, dstNi are on the same device, this is the only mapping that will be created
ingressEgressNiMap.get(srcNi).add(dstNi);
}
// Go through the links and create/add the intermediate NIs
for (int i = 1; i < linkList.size() - 2; i++) {
CarrierEthernetGenericNi ingressNi = new CarrierEthernetGenericNi(linkList.get(i).dst(), null);
ingressEgressNiMap.putIfAbsent(ingressNi, new HashSet<>());
ingressEgressNiMap.get(ingressNi).add(new CarrierEthernetGenericNi(linkList.get(i + 1).src(), null));
}
}
private List<Link> generateLinkList(ConnectPoint cp1, ConnectPoint cp2,
CarrierEthernetVirtualConnection.Type evcType) {
Set<Path> paths;
Path path = null;
if (!cp1.deviceId().equals(cp2.deviceId())) {
// If cp1 and cp2 are not on the same device a path must be found
if (evcType.equals(CarrierEthernetVirtualConnection.Type.POINT_TO_POINT)) {
// For point-to-point connectivity use pre-calculated paths to make sure the shortest paths are chosen
paths = pathService.getPaths(cp1.deviceId(), cp2.deviceId());
} else {
// Recalculate path so that it's over the pre-calculated spanning tree
// FIXME: Find a more efficient way (avoid recalculating paths)
paths = pathService.getPaths(cp1.deviceId(), cp2.deviceId(),
new CarrierEthernetSpanningTreeWeight(topologyService));
}
// Just select any of the returned paths
// TODO: Select path in more sophisticated way and return null if any of the constraints cannot be met
path = paths.iterator().hasNext() ? paths.iterator().next() : null;
if (path == null) {
return null;
}
}
List<Link> links = new ArrayList<>();
links.add(createEdgeLink(cp1, true));
if (!cp1.deviceId().equals(cp2.deviceId())) {
links.addAll(path.links());
}
links.add(createEdgeLink(cp2, false));
return links;
}
private List<Link> generateInverseLinkList(List<Link> originalLinks) {
if (originalLinks == null) {
return null;
}
List<Link> inverseLinks = new ArrayList<>();
inverseLinks.add(createEdgeLink(originalLinks.get(originalLinks.size() - 1).src(), true));
for (int i = originalLinks.size() - 2; i > 0; i--) {
// FIXME: Check again the Link creation parameters
inverseLinks.add(DefaultLink.builder()
.src(originalLinks.get(i).dst())
.dst(originalLinks.get(i).src())
.type(Link.Type.DIRECT)
.providerId(new ProviderId("none", "none"))
.build());
}
inverseLinks.add(createEdgeLink(originalLinks.get(0).dst(), false));
return inverseLinks;
}
@Override
public void removeConnectivity(CarrierEthernetForwardingConstruct fc) {
cePktNodeService.removeAllForwardingResources(fc);
removeOpticalConnectivity(fc.metroConnectivity().id());
}
@Override
public void createBandwidthProfiles(CarrierEthernetForwardingConstruct fc) {
fc.uniSet().forEach(uni -> cePktNodeService.createBandwidthProfileResources(fc, uni));
}
@Override
public void applyBandwidthProfiles(CarrierEthernetForwardingConstruct fc) {
// TODO: Select node manager depending on device protocol
fc.uniSet().forEach(uni -> cePktNodeService.applyBandwidthProfileResources(fc, uni));
}
@Override
public void removeBandwidthProfiles(CarrierEthernetForwardingConstruct fc) {
// TODO: Select node manager depending on device protocol
fc.ltpSet().forEach((ltp -> {
if (ltp.ni().type().equals(CarrierEthernetNetworkInterface.Type.UNI)) {
cePktNodeService.removeBandwidthProfileResources(fc, (CarrierEthernetUni) ltp.ni());
}
}));
}
private class OpticalEventListener implements OpticalPathListener {
@Override
public void event(OpticalPathEvent event) {
switch (event.type()) {
case PATH_INSTALLED: case PATH_REMOVED:
log.info("Optical path event {} received for {}.", event.type(), event.subject());
opticalConnectStatusMap.put(event.subject(), event.type());
break;
default:
log.error("Unexpected optical event type.");
break;
}
}
}
private OpticalConnectivityId setupOpticalConnectivity(ConnectPoint ingress, ConnectPoint egress,
Bandwidth bandwidth, Duration latency) {
OpticalConnectivityId opticalConnectId = opticalPathService
.setupConnectivity(ingress, egress, bandwidth, latency);
if (opticalConnectId != null) {
long startTime = System.currentTimeMillis();
while (((System.currentTimeMillis() - startTime) < OPTICAL_CONNECT_TIMEOUT_MILLIS) &&
(opticalConnectStatusMap.get(opticalConnectId) != OpticalPathEvent.Type.PATH_INSTALLED)) {
// FIXME WTF
}
}
return opticalConnectId;
}
private void removeOpticalConnectivity(OpticalConnectivityId opticalConnectId) {
if (opticalConnectId != null) {
opticalPathService.removeConnectivity(opticalConnectId);
long startTime = System.currentTimeMillis();
while (((System.currentTimeMillis() - startTime) < OPTICAL_CONNECT_TIMEOUT_MILLIS) &&
(opticalConnectStatusMap.get(opticalConnectId) != OpticalPathEvent.Type.PATH_REMOVED)) {
// FIXME WTF
}
}
}
/**
* Indicates if the CE app is meant to control a packet-optical topology.
*
* @param pktOpticalTopo true if CE app controls a packet-optical topology;
* false otherwise
*/
public void setPktOpticalTopo(boolean pktOpticalTopo) {
this.pktOpticalTopo = pktOpticalTopo;
}
/**
* Determines it the CE app is meant to control a packet-optical topology.
*
* @return true if CE app is meant to control a packet-optical topology;
* false otherwise
*/
public boolean getPktOpticalTopo() {
return pktOpticalTopo;
}
}