| /* |
| * Copyright 2016-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.actn.mdsc.tetunnelctl; |
| |
| import com.google.common.collect.Lists; |
| import org.apache.felix.scr.annotations.Activate; |
| import org.apache.felix.scr.annotations.Component; |
| import org.apache.felix.scr.annotations.Deactivate; |
| import org.apache.felix.scr.annotations.Reference; |
| import org.apache.felix.scr.annotations.ReferenceCardinality; |
| import org.onosproject.actn.mdsc.pce.TeTunnelPceService; |
| import org.onosproject.incubator.net.tunnel.Tunnel; |
| import org.onosproject.incubator.net.tunnel.TunnelAdminService; |
| import org.onosproject.incubator.net.tunnel.TunnelEvent; |
| import org.onosproject.incubator.net.tunnel.TunnelId; |
| import org.onosproject.incubator.net.tunnel.TunnelListener; |
| import org.onosproject.incubator.net.tunnel.TunnelService; |
| import org.onosproject.tetopology.management.api.TeTopology; |
| import org.onosproject.tetopology.management.api.TeTopologyKey; |
| import org.onosproject.tetopology.management.api.TeTopologyService; |
| import org.onosproject.tetopology.management.api.node.TeNodeKey; |
| import org.onosproject.tetopology.management.api.node.TtpKey; |
| import org.onosproject.tetunnel.api.TeTunnelAdminService; |
| import org.onosproject.tetunnel.api.TeTunnelService; |
| import org.onosproject.tetunnel.api.tunnel.DefaultTeTunnel; |
| import org.onosproject.tetunnel.api.tunnel.TeTunnel; |
| import org.onosproject.tetunnel.api.tunnel.TeTunnelKey; |
| import org.onosproject.tetunnel.api.tunnel.path.TePath; |
| import org.onosproject.tetunnel.api.tunnel.path.TeRouteSubobject; |
| import org.onosproject.tetunnel.api.tunnel.path.TeRouteUnnumberedLink; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Objects; |
| |
| /** |
| * TE Tunnel controller/processor which manages TE tunnel processing. |
| * <p> |
| * For example, when creating a cross-domain tunnel from a MDSC, the |
| * processor will call a relevant PCE to get an end-to-end cross-domain path, |
| * then spits the path into segment tunnels(domain tunnels), and then informs |
| * PNCs to setup domain tunnels respectively. |
| */ |
| @Component(immediate = true) |
| public class TeTunnelCtl { |
| |
| private static final Logger log = LoggerFactory.getLogger(TeTunnelCtl.class); |
| |
| private final TunnelListener tunnelListener = new InternalTunnelListener(); |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected TunnelService tunnelService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected TunnelAdminService tunnelAdminService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected TeTunnelService teTunnelService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected TeTunnelAdminService teTunnelAdminService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected TeTopologyService teTopologyService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected TeTunnelPceService teTunnelPceService; |
| |
| @Activate |
| protected void activate() { |
| tunnelService.addListener(tunnelListener); |
| |
| log.info("Started"); |
| } |
| |
| @Deactivate |
| protected void deactivate() { |
| tunnelService.removeListener(tunnelListener); |
| |
| log.info("Stopped"); |
| } |
| |
| private void addTeTunnel(TeTunnel teTunnel) { |
| if (teTunnel == null) { |
| return; |
| } |
| |
| Tunnel tunnel = tunnelService.queryTunnel( |
| teTunnelService.getTunnelId(teTunnel.teTunnelKey())); |
| if (tunnel == null) { |
| log.error("tunnel does not exist, {}", teTunnel.teTunnelKey()); |
| return; |
| } |
| if (tunnel.state() != Tunnel.State.INIT) { |
| log.error("tunnel state error, {}, {}", teTunnel.teTunnelKey(), |
| tunnel.state()); |
| return; |
| } |
| tunnelAdminService.updateTunnelState(tunnel, Tunnel.State.ESTABLISHING); |
| |
| //TODO support multi-thread |
| if (isTeTunnelCrossDomain(teTunnel)) { |
| if (!addCrossDomainTeTunnel(teTunnel)) { |
| tunnelAdminService.updateTunnelState(tunnel, Tunnel.State.FAILED); |
| } |
| } |
| /* |
| * "else" is to do nothing. |
| * When adding a single domain tunnel, the TunnelManager will call |
| * tunnel providers, then the providers will pass the request to |
| * the domain controller. Nothing to do here. |
| */ |
| } |
| |
| private boolean isTeTunnelCrossDomain(TeTunnel teTunnel) { |
| TeTopology srcTopo = teTopologyService.teTopology( |
| teTopologyService.teNode(teTunnel.srcNode()) |
| .underlayTeTopologyId()); |
| TeTopology dstTopo = teTopologyService.teTopology( |
| teTopologyService.teNode(teTunnel.dstNode()) |
| .underlayTeTopologyId()); |
| return (srcTopo != null && dstTopo != null |
| && srcTopo.ownerId().equals(dstTopo.ownerId())); |
| } |
| |
| private boolean addCrossDomainTeTunnel(TeTunnel teTunnel) { |
| List<TeRouteSubobject> route = null; |
| TePath primaryPath = teTunnel.primaryPaths().get(0); |
| if (primaryPath != null && |
| primaryPath.type() == TePath.Type.EXPLICIT) { |
| route = primaryPath.explicitRoute(); |
| } else { |
| Collection<List<TeRouteSubobject>> routes = |
| teTunnelPceService.computePaths(teTunnel); |
| if (routes == null || routes.isEmpty()) { |
| log.error("no available route for {}", |
| teTunnel.teTunnelKey()); |
| return false; |
| } |
| |
| //FIXME: try other pce when failed? |
| route = routes.iterator().next(); |
| } |
| |
| if (route == null) { |
| log.error("no available route for {}", |
| teTunnel.teTunnelKey()); |
| return false; |
| } |
| |
| return spitRoute(teTunnel, route); |
| } |
| |
| //spits route to segment tunnels |
| private boolean spitRoute(TeTunnel teTunnel, List<TeRouteSubobject> route) { |
| List<TeTunnelKey> segmentTunnels = Lists.newArrayList(); |
| boolean success = true; |
| TeNodeKey srcNode = teTunnel.srcNode(); |
| TtpKey srcTp = teTunnel.srcTp(); |
| TeNodeKey dstNode = null; |
| TtpKey dstTp = null; |
| |
| for (TeRouteSubobject teRouteSubobject : route) { |
| if (!(teRouteSubobject instanceof TeRouteUnnumberedLink)) { |
| log.error("unsupported type {}", teRouteSubobject.type()); |
| success = false; |
| break; |
| } |
| |
| TeRouteUnnumberedLink teRouteUnnumberedLink = |
| (TeRouteUnnumberedLink) teRouteSubobject; |
| dstNode = teRouteUnnumberedLink.node(); |
| dstTp = teRouteUnnumberedLink.ttp(); |
| if (Objects.equals(srcNode, dstNode) && |
| Objects.equals(srcTp, dstTp)) { |
| continue; |
| } |
| if (Objects.equals(srcNode, dstNode)) { |
| if (!addSegmentTunnel(segmentTunnels, teTunnel, |
| srcNode, srcTp, dstNode, dstTp)) { |
| success = false; |
| break; |
| } |
| } |
| |
| srcNode = dstNode; |
| srcTp = dstTp; |
| } |
| |
| if (success && !(Objects.equals(dstNode, teTunnel.dstNode()) && |
| Objects.equals(dstTp, teTunnel.dstTp()))) { |
| srcNode = dstNode; |
| srcTp = dstTp; |
| dstNode = teTunnel.dstNode(); |
| dstTp = teTunnel.dstTp(); |
| if (!addSegmentTunnel(segmentTunnels, teTunnel, |
| srcNode, srcTp, dstNode, dstTp)) { |
| success = false; |
| } |
| } |
| |
| if (!success) { |
| // roll back segment tunnels |
| for (TeTunnelKey key : segmentTunnels) { |
| teTunnelAdminService.removeTeTunnel(key); |
| } |
| } else { |
| teTunnelAdminService.setSegmentTunnel(teTunnel.teTunnelKey(), |
| segmentTunnels); |
| } |
| return success; |
| } |
| |
| private boolean addSegmentTunnel(List<TeTunnelKey> segmentTunnels, |
| TeTunnel teTunnel, |
| TeNodeKey srcNode, TtpKey srcTp, |
| TeNodeKey dstNode, TtpKey dstTp) { |
| TeTunnelKey teTunnelKey = getNextTeTunnelKey(srcNode.teTopologyKey()); |
| TeTunnel teTunnelSegment = DefaultTeTunnel.builder() |
| .teTunnelKey(teTunnelKey) |
| .srcNode(srcNode) |
| .dstNode(dstNode) |
| .srcTp(srcTp) |
| .dstTp(dstTp) |
| .adminState(teTunnel.adminStatus()) |
| .lspProtectionType(teTunnel.lspProtectionType()) |
| .type(teTunnel.type()) |
| .build(); |
| TunnelId tunnelId = |
| teTunnelAdminService.createTeTunnel(teTunnelSegment); |
| if (tunnelId == null) { |
| log.error("failed to create segment tunnel: {},{},{},{}", |
| srcNode, srcTp, dstNode, dstTp); |
| return false; |
| } |
| segmentTunnels.add(teTunnelKey); |
| return true; |
| } |
| |
| private TeTunnelKey getNextTeTunnelKey(TeTopologyKey key) { |
| //FIXME need a better way to get a te tunnel id |
| long teTunnelId = teTunnelService.getTeTunnels(key).size() + 1L; |
| return new TeTunnelKey(key, teTunnelId); |
| } |
| |
| private void updateTeTunnel(TeTunnel teTunnel, Tunnel tunnel) { |
| if (teTunnel == null) { |
| return; |
| } |
| |
| if (tunnel.state() == Tunnel.State.ESTABLISHED) { |
| tunnelEstablished(teTunnel); |
| } else if (tunnel.state() == Tunnel.State.REMOVING) { |
| removingTunnel(teTunnel); |
| } |
| |
| //TODO update TE tunnel content |
| } |
| |
| private void tunnelEstablished(TeTunnel teTunnel) { |
| TeTunnel e2eTeTunnel = retriveE2eTunnel(teTunnel); |
| if (e2eTeTunnel != null) { |
| boolean goodToContinue = true; |
| for (TeTunnelKey key : e2eTeTunnel.segmentTunnels()) { |
| goodToContinue = checkSegmentTunnel(key); |
| if (!goodToContinue) { |
| break; |
| } |
| } |
| |
| if (goodToContinue) { |
| tunnelAdminService.updateTunnelState( |
| tunnelService.queryTunnel( |
| teTunnelService.getTunnelId( |
| teTunnel.teTunnelKey())), |
| Tunnel.State.ESTABLISHED |
| ); |
| } |
| } |
| } |
| |
| private TeTunnel retriveE2eTunnel(TeTunnel segmentTunnel) { |
| return teTunnelService.getTeTunnel(segmentTunnel.e2eTunnelKey()); |
| } |
| |
| private boolean checkSegmentTunnel(TeTunnelKey key) { |
| Tunnel segmentTunnel = tunnelService.queryTunnel( |
| teTunnelService.getTunnelId(key)); |
| if (segmentTunnel == null || |
| segmentTunnel.state() != Tunnel.State.ESTABLISHED) { |
| return false; |
| } |
| return true; |
| } |
| |
| private void removingTunnel(TeTunnel teTunnel) { |
| List<TeTunnelKey> segmentTunnels = teTunnel.segmentTunnels(); |
| if (segmentTunnels != null && !segmentTunnels.isEmpty()) { |
| for (TeTunnelKey key : segmentTunnels) { |
| teTunnelAdminService.removeTeTunnel(key); |
| } |
| } |
| } |
| |
| // Listens on tunnel events. |
| private class InternalTunnelListener implements TunnelListener { |
| @Override |
| public void event(TunnelEvent event) { |
| switch (event.type()) { |
| case TUNNEL_ADDED: |
| addTunnel(event.subject()); |
| break; |
| case TUNNEL_UPDATED: |
| updateTunnel(event.subject()); |
| break; |
| //TODO: TE Tunnel remove/... event process |
| default: |
| log.warn("unknown event: {}", event.type()); |
| break; |
| } |
| } |
| |
| private void addTunnel(Tunnel tunnel) { |
| addTeTunnel(teTunnelService.getTeTunnel(tunnel.tunnelId())); |
| } |
| |
| private void updateTunnel(Tunnel tunnel) { |
| updateTeTunnel(teTunnelService.getTeTunnel(tunnel.tunnelId()), |
| tunnel); |
| } |
| } |
| } |