Skip to content

Commit

Permalink
[TEST]: Improvement: Paths manipulation: Regular flow
Browse files Browse the repository at this point in the history
  • Loading branch information
Yuliia Miroshnychenko authored and pablomuri committed Jan 30, 2025
1 parent c161e66 commit 3fd4d21
Show file tree
Hide file tree
Showing 7 changed files with 334 additions and 168 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package org.openkilda.functionaltests.helpers

import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.DELETE_ISLS_PROPERTIES
import static org.springframework.beans.factory.config.ConfigurableBeanFactory.SCOPE_PROTOTYPE

import org.openkilda.functionaltests.model.cleanup.CleanupManager
import org.openkilda.messaging.info.event.PathNode
import org.openkilda.messaging.payload.flow.FlowPathPayload
import org.openkilda.northbound.dto.v2.flows.FlowPathV2.PathNodeV2
import org.openkilda.testing.model.topology.TopologyDefinition
import org.openkilda.testing.model.topology.TopologyDefinition.Isl
import org.openkilda.testing.model.topology.TopologyDefinition.Switch
import org.openkilda.testing.service.database.Database
import org.openkilda.testing.service.northbound.NorthboundService
import org.openkilda.testing.service.northbound.NorthboundServiceV2
import org.openkilda.testing.tools.IslUtils

import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.annotation.Scope
import org.springframework.stereotype.Component

import java.util.AbstractMap.SimpleEntry
import java.util.stream.Collectors

/**
* Holds utility methods for working with flow paths.
*/
@Component
@Slf4j
@Scope(SCOPE_PROTOTYPE)
class PathHelper {

@Autowired
TopologyDefinition topology
@Autowired
@Qualifier("islandNb")
NorthboundService northbound
@Autowired
@Qualifier("islandNbV2")
NorthboundServiceV2 northboundV2
@Autowired
IslUtils islUtils
@Autowired
Database database
@Autowired
CleanupManager cleanupManager


/**
* If required, makes one path more preferable than another.
* Finds a unique ISL of less preferable path and adds 'cost difference between paths + 1' to its cost
*
* @param morePreferablePath path that should become more preferable over the 'lessPreferablePath'
* @param lessPreferablePath path that should become less preferable compared to 'morePreferablePath'
* @return The changed ISL (one-way ISL, but actually changed in both directions) or null
*/
Isl makePathMorePreferable(List<PathNode> morePreferablePath, List<PathNode> lessPreferablePath) {
def morePreferableIsls = getInvolvedIsls(morePreferablePath)
def lessPreferableIsls = getInvolvedIsls(lessPreferablePath)
List<Isl> uniqueIsls = (morePreferableIsls + lessPreferableIsls)
.unique { a, b -> a == b || a == b.reversed ? 0 : 1 }
HashMap<Isl, Integer> islCosts = uniqueIsls.parallelStream().flatMap({ isl ->
Integer cost = northbound.getLink(isl).cost ?: 700
[isl, isl.reversed].stream().map({ biIsl -> new SimpleEntry<>(biIsl, cost) })
}).collect(Collectors.toMap({ it.getKey() }, { it.getValue() }))
// under specific condition cost of isl can be 0, but at the same time for the system 0 == 700
def totalCostOfMorePrefPath = morePreferableIsls.sum { islCosts.get(it) }
def totalCostOfLessPrefPath = lessPreferableIsls.sum { islCosts.get(it) }
def difference = totalCostOfMorePrefPath - totalCostOfLessPrefPath
def islToAvoid
if (difference >= 0) {
islToAvoid = lessPreferableIsls.find {
!morePreferableIsls.contains(it) && !morePreferableIsls.contains(it.reversed)
}
if (!islToAvoid) {
//this should be impossible
throw new Exception("Unable to make some path more preferable because both paths use same ISLs")
}
log.debug "ISL to avoid: $islToAvoid"
cleanupManager.addAction(DELETE_ISLS_PROPERTIES, {northbound.deleteLinkProps(northbound.getLinkProps(topology.isls))})
northbound.updateLinkProps([islUtils.toLinkProps(islToAvoid,
["cost": (islCosts.get(islToAvoid) + difference + 1).toString()])])
}
return islToAvoid
}


/**
* Get list of ISLs that are involved in given path.
* Note: will only return forward-way isls. You'll have to reverse them yourself if required.
* Note2: will try to search for an ISL in given topology.yaml. If not found, will create a new ISL object
* with 0 bandwidth and null a-switch (which may not be the actual value)
* Note3: poorly handle situation if switchId is not present in toppology.yaml at all (will create
* ISL with src/dst switches as null)
*/
List<Isl> getInvolvedIsls(List<PathNode> path) {
if (path.size() % 2 != 0) {
throw new IllegalArgumentException("Path should have even amount of nodes")
}
if (path.empty) {
return new ArrayList<Isl>()
}
def involvedIsls = []
for (int i = 1; i < path.size(); i += 2) {
def src = path[i - 1]
def dst = path[i]
def matchingIsl = {
it.srcSwitch?.dpId == src.switchId && it?.srcPort == src.portNo &&
it.dstPort == dst.portNo && it.dstSwitch.dpId == dst.switchId
}
def involvedIsl = topology.isls.find(matchingIsl) ?:
topology.isls.collect { it.reversed }.find(matchingIsl) ?:
Isl.factory(topology.switches.find { it.dpId == src.switchId }, src.portNo,
topology.switches.find { it.dpId == dst.switchId }, dst.portNo,
0, null)
involvedIsls << involvedIsl
}
return involvedIsls
}

List<Isl> getInvolvedIsls(FlowPathPayload path) {
getInvolvedIsls(convert(path))
}

List<Isl> getInvolvedIsls(String flowId) {
getInvolvedIsls(convert(northbound.getFlowPath(flowId)))
}

/**
* Converts FlowPathPayload path representation to a List<PathNode> representation
*/
static List<PathNode> convert(FlowPathPayload pathPayload, pathToConvert = "forwardPath") {
def path = pathPayload."$pathToConvert"
getPathNodes(path)
}



/**
* Returns a List<PathNode> representation of a path
*/
static List<PathNode> getPathNodes(path, boolean removeTail = true) {
if (path.empty) {
throw new IllegalArgumentException("Path cannot be empty. " +
"This should be impossible for valid FlowPathPayload")
}
List<PathNode> pathNodes = []
path.each { pathEntry ->
pathNodes << new PathNode(pathEntry.switchId, pathEntry.inputPort == null ? 0 : pathEntry.inputPort, 0)
pathNodes << new PathNode(pathEntry.switchId, pathEntry.outputPort == null ? 0 : pathEntry.outputPort, 0)
}
def seqId = 0
if (pathNodes.size() > 2) {
pathNodes = pathNodes.tail() //remove first elements (not used in PathNode view)
if (removeTail) {
pathNodes = pathNodes.dropRight(1) //remove last elements (not used in PathNode view)
}
}
pathNodes.each { it.seqId = seqId++ } //set valid seqId indexes
return pathNodes
}


/**
* Converts List<PathNodeV2> path representation to the list of PathNodeV2, but with null segmentLatency field
*/
static List<PathNodeV2> setNullLatency(List<PathNodeV2> path) {
return path.collect { new PathNodeV2(it.switchId, it.portNo, null)}
}

/**
* Get list of switches involved in a given path.
*/
List<Switch> getInvolvedSwitches(List<PathNode> path) {
return (List<Switch>) getInvolvedIsls(path).collect { [it.srcSwitch, it.dstSwitch] }.flatten().unique()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,6 @@ class SwitchPairs {
return this
}

SwitchPairs withoutOf12Switches() {
switchPairs = switchPairs.findAll { it.src.ofVersion != "OF_12" && it.dst.ofVersion != "OF_12" }
return this
}

SwitchPairs withTraffgensOnBothEnds() {
switchPairs = switchPairs.findAll { [it.src, it.dst].every { !it.traffGens.isEmpty() } }
return this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ class SwitchTriplets {
SwitchTriplets nonNeighbouring() {
switchTriplets = switchTriplets.findAll {
it.shared != it.ep1 && it.ep1 != it.ep2 && it.ep2 != it.shared &&
it.pathsEp1.every {it.size() > 2} &&
it.pathsEp2.every {it.size() > 2}}
it.getPathsEp1().every {it.size() > 2} &&
it.getPathsEp2().every {it.size() > 2}}

return this
}
Expand Down Expand Up @@ -149,7 +149,7 @@ class SwitchTriplets {
.unique(false) { a, b -> a.intersect(b) == [] ? 1 : 0 }
def yPoints = topologyHelper.findPotentialYPoints(it)

yPoints.size() == 1 && (isYPointOnSharedEp ? yPoints[0] == it.shared.dpId : yPoints[0] != it.shared.dpId) && yPoints[0] != it.ep1.dpId && yPoints[0] != it.ep2.dpId &&
yPoints.size() == 1 && (isYPointOnSharedEp ? yPoints[0] == it.shared : yPoints[0] != it.shared) && yPoints[0] != it.ep1 && yPoints[0] != it.ep2 &&
ep1paths.size() >= 2 && ep2paths.size() >= 2
}
}
Expand Down Expand Up @@ -184,7 +184,7 @@ class SwitchTriplets {

SwitchTriplet findSwitchTripletWithYPointOnSharedEp() {
return switchTriplets.find {
topologyHelper.findPotentialYPoints(it).size() == 1 && it.getShared().getDpId() == topologyHelper.findPotentialYPoints(it).get(0)
topologyHelper.findPotentialYPoints(it).size() == 1 && it.getShared().getDpId() == topologyHelper.findPotentialYPoints(it).get(0).getDpId()
}
}

Expand Down Expand Up @@ -226,8 +226,7 @@ class SwitchTriplets {
ep1: ep1,
ep2: ep2,
pathsEp1: retrievePairPathNode(shared.dpId, ep1.dpId),
pathsEp2: retrievePairPathNode(shared.dpId, ep2.dpId),
topologyDefinition: topology)
pathsEp2: retrievePairPathNode(shared.dpId, ep2.dpId))
}
}

Expand Down
Loading

0 comments on commit 3fd4d21

Please sign in to comment.