/* * Copyright (c) 2017 Ericsson India Global Services Pvt Ltd. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.netvirt.vpnmanager.intervpnlink; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.function.Predicate; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.genius.mdsalutil.NWUtil; import org.opendaylight.mdsal.binding.api.DataBroker; import org.opendaylight.netvirt.vpnmanager.VpnUtil; import org.opendaylight.netvirt.vpnmanager.api.intervpnlink.InterVpnLinkCache; import org.opendaylight.netvirt.vpnmanager.api.intervpnlink.InterVpnLinkDataComposite; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.VpnInstanceOpDataEntry; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.vpn.instance.op.data.entry.VpnTargets; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.vpn.instance.op.data.entry.vpntargets.VpnTarget; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netvirt.inter.vpn.link.rev160311.inter.vpn.links.InterVpnLink; import org.opendaylight.yangtools.yang.common.Uint64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is responsible for searching the best possible DPN(s) to place * an InterVpnLink. * */ @Singleton public class InterVpnLinkLocator { private static final Logger LOG = LoggerFactory.getLogger(InterVpnLinkLocator.class); protected static final String NBR_OF_DPNS_PROPERTY_NAME = "vpnservice.intervpnlink.number.dpns"; private final DataBroker dataBroker; private final InterVpnLinkCache interVpnLinkCache; private final VpnUtil vpnUtil; @Inject public InterVpnLinkLocator(final DataBroker dataBroker, final InterVpnLinkCache interVpnLinkCache, final VpnUtil vpnUtil) { this.dataBroker = dataBroker; this.interVpnLinkCache = interVpnLinkCache; this.vpnUtil = vpnUtil; } /** * Retrieves a list of randomly selected DPNs avoiding to select DPNs * where there is already an InterVpnLink of the same group (i.e., an * InterVpnLink that links similar L3VPNs). * * @param interVpnLink InterVpnLink to find suitable DPNs for. * @return the list of the selected DPN Ids */ public List selectSuitableDpns(InterVpnLink interVpnLink) { int numberOfDpns = Integer.getInteger(NBR_OF_DPNS_PROPERTY_NAME, 1); List dpnIdPool = new ArrayList<>(); try { dpnIdPool = NWUtil.getOperativeDPNs(dataBroker).stream().map(dpn -> dpn).collect(Collectors.toList()); } catch (ExecutionException | InterruptedException e) { LOG.error("selectSuitableDpns: Exception while reading Operative DPNs", e); } LOG.trace("selectSuitableDpns for {} with numberOfDpns={} and availableDpns={}", interVpnLink.getName(), numberOfDpns, dpnIdPool); int poolSize = dpnIdPool.size(); if (poolSize <= numberOfDpns) { // You requested more than there is, I give you all I have. return dpnIdPool; } List allInterVpnLinks = interVpnLinkCache.getAllInterVpnLinks(); // 1st criteria is to select those DPNs where there is no InterVpnLink at all List dpnsWithNoIVL = findDPNsWithNoInterVpnLink(dpnIdPool, allInterVpnLinks); if (dpnsWithNoIVL.size() >= numberOfDpns) { return dpnsWithNoIVL.subList(0, numberOfDpns); // Best case scenario } // Not enough. 2nd criteria is to avoid DPNs where there are InterVpnLinks of the same group List result = new ArrayList<>(dpnsWithNoIVL); dpnIdPool.removeAll(result); int pendingDPNs = numberOfDpns - result.size(); List dpnsToAvoid = findDpnsWithSimilarIVpnLinks(interVpnLink, allInterVpnLinks); result.addAll(dpnIdPool.stream().filter(dpId -> !dpnsToAvoid.contains(dpId)) .limit(pendingDPNs).collect(Collectors.toList())); int currentNbrOfItems = result.size(); if (currentNbrOfItems < numberOfDpns) { // Still not enough. 3rd criteria: whatever is available dpnIdPool.removeAll(result); pendingDPNs = numberOfDpns - currentNbrOfItems; result.addAll(dpnIdPool.subList(0, Math.max(dpnIdPool.size(), pendingDPNs))); } return result; } /* * Given a list of Dpn Ids and a list of InterVpnLinks, this method finds * the DPNs in the first list where no InterVpnLink is instantiated there. * * @param dpnList A list of DPN IDs * @param interVpnLinks A List of InterVpnLinks to avoid * * @return the list of available DPNs among the specified ones */ private List findDPNsWithNoInterVpnLink(List dpnList, List interVpnLinks) { List occupiedDpns = new ArrayList<>(); for (InterVpnLinkDataComposite ivl : interVpnLinks) { if (ivl.isActive()) { occupiedDpns.addAll(ivl.getFirstEndpointDpns()); occupiedDpns.addAll(ivl.getSecondEndpointDpns()); } } List result = new ArrayList<>(dpnList); result.removeAll(occupiedDpns); return result; } /* * Given an InterVpnLink, this method finds those DPNs where there is an * InterVpnLink of the same group. Two InterVpnLinks are in the same group * if they link 2 L3VPNs that are from the same group, and 2 L3VPNs are in * the same group if their iRTs match. * * @param interVpnLink InterVpnLink to be checked * @return the list of dpnIds where the specified InterVpnLink should not * be installed */ @NonNull private List findDpnsWithSimilarIVpnLinks(InterVpnLink interVpnLink, List allInterVpnLinks) { List sameGroupInterVpnLinks = findInterVpnLinksSameGroup(interVpnLink, allInterVpnLinks); Set resultDpns = new HashSet<>(); for (InterVpnLinkDataComposite ivl : sameGroupInterVpnLinks) { resultDpns.addAll(ivl.getFirstEndpointDpns()); resultDpns.addAll(ivl.getSecondEndpointDpns()); } return new ArrayList<>(resultDpns); } private List getRts(VpnInstanceOpDataEntry vpnInstance, VpnTarget.VrfRTType rtType) { String name = vpnInstance.getVpnInstanceName(); VpnTargets targets = vpnInstance.getVpnTargets(); if (targets == null) { LOG.trace("vpn targets not available for {}", name); return new ArrayList<>(); } List vpnTargets = targets.getVpnTarget(); if (vpnTargets == null) { LOG.trace("vpnTarget values not available for {}", name); return new ArrayList<>(); } return vpnTargets.stream() .filter(target -> Objects.equals(target.getVrfRTType(), rtType) || Objects.equals(target.getVrfRTType(), VpnTarget.VrfRTType.Both)) .map(VpnTarget::getVrfRTValue) .collect(Collectors.toList()); } private List getIRTsByVpnName(String vpnName) { String vpn1Rd = vpnUtil.getVpnRd(vpnName); final VpnInstanceOpDataEntry vpnInstance = vpnUtil.getVpnInstanceOpData(vpn1Rd); return getRts(vpnInstance, VpnTarget.VrfRTType.ImportExtcommunity); } @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_MIGHT_BE_INFEASIBLE") private boolean haveSameIRTs(List irts1, List irts2) { if (irts1 == null && irts2 == null) { return true; } if (irts1 == null || irts2 == null) { return false; } // FindBugs reports "Possible null pointer dereference of irts1 on branch that might be infeasible" but irts1 // can't be null here. if (irts1.size() != irts2.size()) { return false; } irts1.sort(/*comparator*/ null); irts2.sort(/*comparator*/ null); return irts1.equals(irts2); } public List findInterVpnLinksSameGroup(InterVpnLink ivpnLinkToMatch, List interVpnLinks) { List vpnToMatch1IRTs = getIRTsByVpnName(ivpnLinkToMatch.getFirstEndpoint().getVpnUuid().getValue()); List vpnToMatch2IRTs = getIRTsByVpnName(ivpnLinkToMatch.getSecondEndpoint().getVpnUuid().getValue()); Predicate areSameGroup = (ivl) -> { if (ivl.getInterVpnLinkName().equals(ivpnLinkToMatch.getName())) { return false; // ivl and ivpnLinlToMatch are the same InterVpnLink } String vpn1Name = ivl.getFirstEndpointVpnUuid().orElse(null); String vpn2Name = ivl.getSecondEndpointVpnUuid().orElse(null); if (vpn1Name == null) { return false; } List vpn1IRTs = getIRTsByVpnName(vpn1Name); List vpn2IRTs = getIRTsByVpnName(vpn2Name); return haveSameIRTs(vpn1IRTs, vpnToMatch1IRTs) && haveSameIRTs(vpn2IRTs, vpnToMatch2IRTs) || haveSameIRTs(vpn1IRTs, vpnToMatch2IRTs) && haveSameIRTs(vpn2IRTs, vpnToMatch1IRTs); }; return interVpnLinks.stream().filter(areSameGroup).collect(Collectors.toList()); } }