Skip to content

Commit

Permalink
Merge pull request #5754 from telstra/test/evacuate-y-flow
Browse files Browse the repository at this point in the history
[TEST]: Isl/Switch maintenance mode: Y-Flow evacuation
  • Loading branch information
IvanChupin authored Nov 28, 2024
2 parents 4f1878e + 817a9d4 commit bd2e3db
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.openkilda.functionaltests.spec.links

import static org.openkilda.functionaltests.extension.tags.Tag.ISL_RECOVER_ON_FAIL
import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE
import static org.openkilda.functionaltests.helpers.Wrappers.timedLoop
import static org.openkilda.testing.Constants.DEFAULT_COST
import static org.openkilda.testing.Constants.PATH_INSTALLATION_TIME
import static org.openkilda.testing.Constants.WAIT_OFFSET
Expand All @@ -11,6 +12,8 @@ import org.openkilda.functionaltests.extension.tags.Tags
import org.openkilda.functionaltests.helpers.Wrappers
import org.openkilda.functionaltests.helpers.factory.FlowFactory
import org.openkilda.functionaltests.helpers.model.FlowEntityPath
import org.openkilda.functionaltests.helpers.model.FlowWithSubFlowsEntityPath
import org.openkilda.functionaltests.helpers.model.YFlowFactory
import org.openkilda.messaging.payload.flow.FlowState

import org.springframework.beans.factory.annotation.Autowired
Expand All @@ -22,6 +25,10 @@ class LinkMaintenanceSpec extends HealthCheckSpecification {
@Shared
FlowFactory flowFactory

@Autowired
@Shared
YFlowFactory yFlowFactory

@Tags(SMOKE)
def "Maintenance mode can be set/unset for a particular link"() {
given: "An active link"
Expand Down Expand Up @@ -65,8 +72,10 @@ class LinkMaintenanceSpec extends HealthCheckSpecification {
islHelper.setLinkMaintenance(isl, true, false)

then: "Flows are not evacuated (rerouted) and have the same paths"
flow1.retrieveAllEntityPaths() == flow1Path
flow2.retrieveAllEntityPaths() == flow2Path
timedLoop(3) {
assert flow1.retrieveAllEntityPaths() == flow1Path
assert flow2.retrieveAllEntityPaths() == flow2Path
}

when: "Set maintenance mode again with flows evacuation flag for the same link"
northbound.setLinkMaintenance(islUtils.toLinkUnderMaintenance(isl, true, true))
Expand All @@ -88,6 +97,91 @@ class LinkMaintenanceSpec extends HealthCheckSpecification {
!flow2PathUpdated.getInvolvedIsls().contains(isl)
}

def "Y-Flows can be evacuated (rerouted) from a particular link when setting maintenance mode for it"() {
given: "Switch triplet with two possible paths at least for non-neighbouring switches"
def swTriplet = switchTriplets.all().nonNeighbouring().withAtLeastNNonOverlappingPaths(2).random()

and: "Create Y-Flows going through selected switch triplet"
def yFlow1 = yFlowFactory.getRandom(swTriplet, false)
def yFlow1Path = yFlow1.retrieveAllEntityPaths()

def yFlow2 = yFlowFactory.getRandom(swTriplet, false, yFlow1.occupiedEndpoints())
def yFlow2Path = yFlow2.retrieveAllEntityPaths()
assert yFlow1Path.getInvolvedIsls().sort() == yFlow2Path.getInvolvedIsls().sort()

when: "Set maintenance mode without flows evacuation flag for the first link involved in flow paths"
def isl = yFlow1Path.getInvolvedIsls().first()
islHelper.setLinkMaintenance(isl, true, false)

then: "Y-Flows are not evacuated (rerouted) and have the same paths"
timedLoop(3) {
assert yFlow1.retrieveAllEntityPaths() == yFlow1Path
assert yFlow2.retrieveAllEntityPaths() == yFlow2Path
}

when: "Set maintenance mode again with flows evacuation flag for the same link"
northbound.setLinkMaintenance(islUtils.toLinkUnderMaintenance(isl, true, true))

then: "Y-Flows are evacuated (rerouted) and link under maintenance is not involved in new flow paths"
FlowWithSubFlowsEntityPath yFlow1PathUpdated, yFlow2PathUpdated
Wrappers.wait(PATH_INSTALLATION_TIME + WAIT_OFFSET) {
[yFlow1, yFlow2].each { flow -> assert flow.retrieveDetails().status == FlowState.UP }
yFlow1PathUpdated = yFlow1.retrieveAllEntityPaths()
yFlow2PathUpdated = yFlow2.retrieveAllEntityPaths()

assert yFlow1PathUpdated != yFlow1Path
assert yFlow2PathUpdated != yFlow2Path
}

and: "Link under maintenance is not involved in new Y-Flow paths"
!yFlow1PathUpdated.getInvolvedIsls().contains(isl)
!yFlow2PathUpdated.getInvolvedIsls().contains(isl)
}

@Tags(SMOKE)
def "Both Y-Flow and Flow can be evacuated (rerouted) from a particular link when setting maintenance mode for it"() {
given: "Switch triplet with active switches"
def swTriplet = switchTriplets.all().withSharedEpEp1Ep2InChain().random()

and: "Create Y-Flows going through selected switch triplet"
def yFlow = yFlowFactory.getRandom(swTriplet, false)
def yFlowPath = yFlow.retrieveAllEntityPaths()
def isl = yFlowPath.getInvolvedIsls().first()

and: "Switch pair has been selected based on Y-Flow used Isl"
def switchPair = switchPairs.all().specificPair(isl.srcSwitch, isl.dstSwitch)

and: "Create Flow going through selected switch pair"
def flow = flowFactory.getRandom(switchPair)
def flowPath = flow.retrieveAllEntityPaths()
assert flowPath.getInvolvedIsls().contains(isl)

when: "Set maintenance mode without flows evacuation flag for the first link involved in flow paths"
islHelper.setLinkMaintenance(isl, true, false)

then: "Both Y-Flow and Flow are not evacuated (rerouted) and have the same paths"
timedLoop(3) {
assert flow.retrieveAllEntityPaths() == flowPath
assert yFlow.retrieveAllEntityPaths() == yFlowPath
}

when: "Set maintenance mode again with flows evacuation flag for the same link"
northbound.setLinkMaintenance(islUtils.toLinkUnderMaintenance(isl, true, true))

then: "Both Y-Flow and Flow are evacuated (rerouted)"
Wrappers.wait(PATH_INSTALLATION_TIME + WAIT_OFFSET) {
assert flow.retrieveFlowStatus().status == FlowState.UP
assert yFlow.retrieveDetails().status == FlowState.UP

assert flow.retrieveAllEntityPaths() != flowPath
assert yFlow.retrieveAllEntityPaths() != yFlowPath
}

and: "Link under maintenance is not involved in new flow paths"
!flow.retrieveAllEntityPaths().getInvolvedIsls().contains(isl)
!yFlow.retrieveAllEntityPaths().getInvolvedIsls().contains(isl)
}

@Tags(ISL_RECOVER_ON_FAIL)
def "Flows are rerouted to a path with link under maintenance when there are no other paths available"() {
given: "Two active not neighboring switches with two possible paths at least"
Expand Down Expand Up @@ -118,7 +212,7 @@ class LinkMaintenanceSpec extends HealthCheckSpecification {

then: "Flows are rerouted to alternative path with link under maintenance"
Wrappers.wait(rerouteDelay + WAIT_OFFSET * 2) {
[flow1, flow2].each { flow -> assert flow.retrieveFlowStatus().status == FlowState.UP }
[flow1, flow2].each { flow -> assert flow.retrieveFlowStatus().status == FlowState.UP }

def flow1PathUpdated = flow1.retrieveAllEntityPaths()
def flow2PathUpdated = flow2.retrieveAllEntityPaths()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.openkilda.functionaltests.spec.switches
import static org.junit.jupiter.api.Assumptions.assumeTrue
import static org.openkilda.functionaltests.extension.tags.Tag.ISL_RECOVER_ON_FAIL
import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE
import static org.openkilda.functionaltests.helpers.Wrappers.timedLoop
import static org.openkilda.testing.Constants.DEFAULT_COST
import static org.openkilda.testing.Constants.PATH_INSTALLATION_TIME
import static org.openkilda.testing.Constants.WAIT_OFFSET
Expand All @@ -11,8 +12,8 @@ import org.openkilda.functionaltests.HealthCheckSpecification
import org.openkilda.functionaltests.extension.tags.Tags
import org.openkilda.functionaltests.helpers.Wrappers
import org.openkilda.functionaltests.helpers.factory.FlowFactory
import org.openkilda.functionaltests.helpers.model.FlowEntityPath
import org.openkilda.functionaltests.helpers.model.Path
import org.openkilda.functionaltests.helpers.model.YFlowFactory
import org.openkilda.messaging.info.event.IslChangeType
import org.openkilda.messaging.payload.flow.FlowState
import org.openkilda.model.SwitchId
Expand All @@ -26,6 +27,9 @@ class SwitchMaintenanceSpec extends HealthCheckSpecification {
@Shared
@Autowired
FlowFactory flowFactory
@Shared
@Autowired
YFlowFactory yFlowFactory

@Tags(SMOKE)
def "Maintenance mode can be set/unset for a particular switch"() {
Expand Down Expand Up @@ -69,54 +73,132 @@ class SwitchMaintenanceSpec extends HealthCheckSpecification {
}
}

@Tags(SMOKE)
def "Flows can be evacuated (rerouted) from a particular switch when setting maintenance mode for it"() {
def "Flows can be evacuated (rerouted) from a particular switch(several Isls are in use) when setting maintenance mode for it"() {
given: "Two active not neighboring switches and a switch to be maintained"
SwitchId swId
List<Isl> pathIsls
List<Isl> intermediateSwIsls
List<Isl> flow1PathIsls
def switchPair = switchPairs.all().nonNeighbouring().getSwitchPairs().find {
List<Path> availablePath = it.retrieveAvailablePaths()
pathIsls = availablePath.find { Path aPath ->
flow1PathIsls = availablePath.find { Path aPath ->
swId = aPath.getInvolvedSwitches().find { aSw ->
availablePath.findAll { it != aPath }.find { !it.getInvolvedSwitches().contains(aSw) }
intermediateSwIsls = topology.getRelatedIsls(aSw)
availablePath.findAll { it != aPath }.find { !it.getInvolvedSwitches().contains(aSw) && intermediateSwIsls.size() > 2 }
}
}.getInvolvedIsls()
} ?: assumeTrue(false, "No suiting switches found. Need a switch pair with at least 2 paths and one of the " +
"paths should not use the maintenance switch")
switchPair.retrieveAvailablePaths().collect { it.getInvolvedIsls() }.findAll { it != pathIsls }
.each { islHelper.makePathIslsMorePreferable(pathIsls, it) }
switchPair.retrieveAvailablePaths().collect { it.getInvolvedIsls() }.findAll { it != flow1PathIsls }
.each { islHelper.makePathIslsMorePreferable(flow1PathIsls, it) }
and: "Create a couple of flows going through these switches"
and: "Create a Flow going through these switches"
def flow1 = flowFactory.getRandom(switchPair)
def flow2 = flowFactory.getRandom(switchPair, false, FlowState.UP, flow1.occupiedEndpoints())
assert flow1.retrieveAllEntityPaths().getInvolvedIsls() == pathIsls
assert flow2.retrieveAllEntityPaths().getInvolvedIsls()== pathIsls
def flow1Path = flow1.retrieveAllEntityPaths()
def flow1IntermediateSwIsl = flow1PathIsls.findAll { it in intermediateSwIsls || it.reversed in intermediateSwIsls }
assert flow1Path.getInvolvedIsls().containsAll(flow1IntermediateSwIsl)
and: "Create an additional Flow that has the same intermediate switch, but other ISLs are involved into a path"
def additionalSwitchPair = switchPairs.all().nonNeighbouring()
.excludeSwitches([switchPair.src, topology.switches.find { it.dpId == swId }]).random()
def flow2PathIsls = additionalSwitchPair.retrieveAvailablePaths()
.find { it.getInvolvedSwitches().contains(swId) && it.getInvolvedIsls().intersect(flow1PathIsls).isEmpty() }.getInvolvedIsls()
additionalSwitchPair.retrieveAvailablePaths().collect { it.getInvolvedIsls() }.findAll { it != flow2PathIsls }
.each { islHelper.makePathIslsMorePreferable(flow2PathIsls, it) }
def flow2 = flowFactory.getRandom(additionalSwitchPair, false)
def flow2Path = flow2.retrieveAllEntityPaths()
def flow2IntermediateSwIsl = flow2PathIsls.findAll { it in intermediateSwIsls || it.reversed in intermediateSwIsls }
assert flow2Path.getInvolvedIsls().containsAll(flow2IntermediateSwIsl)
assert flow2IntermediateSwIsl.intersect(flow1IntermediateSwIsl).isEmpty()
when: "Set maintenance mode without flows evacuation flag for some intermediate switch involved in flow paths"
switchHelper.setSwitchMaintenance(swId, true, false)
then: "Flows are not evacuated (rerouted) and have the same paths"
flow1.retrieveAllEntityPaths().getInvolvedIsls() == pathIsls
flow2.retrieveAllEntityPaths().getInvolvedIsls() == pathIsls
timedLoop(3) {
assert flow1.retrieveAllEntityPaths() == flow1Path
assert flow2.retrieveAllEntityPaths() == flow2Path
}
when: "Set maintenance mode again with flows evacuation flag for the same switch"
northbound.setSwitchMaintenance(swId, true, true)
then: "Flows are evacuated (rerouted)"
FlowEntityPath flow1PathUpdated, flow2PathUpdated
Wrappers.wait(PATH_INSTALLATION_TIME + WAIT_OFFSET) {
[flow1, flow2].each { assert it.retrieveFlowStatus().status == FlowState.UP }
assert flow1.retrieveAllEntityPaths() != flow1Path
assert flow2.retrieveAllEntityPaths() != flow2Path
}
and: "Switch under maintenance is not involved in new flow paths"
!flow1.retrieveAllEntityPaths().getInvolvedSwitches().contains(swId)
!flow2.retrieveAllEntityPaths().getInvolvedSwitches().contains(swId)

}

flow1PathUpdated = flow1.retrieveAllEntityPaths()
flow2PathUpdated = flow2.retrieveAllEntityPaths()
@Tags(SMOKE)
def "Both Y-Flow and Flow can be evacuated (rerouted) from a particular switch when setting maintenance mode for it"() {
given: "Switch triplet has been selected"
def swTriplet = profile == "hardware" ? switchTriplets.all().withSharedEpEp1Ep2InChain().random()
: switchTriplets.all().nonNeighbouring().random() ?: assumeTrue(false, "No suiting switches found.")
and: "Create a Y-Flow going through selected switches and has intermediate switch in a path"
if (profile == "hardware") {
//additional steps due to the HW limitation
def availablePaths = swTriplet.pathsEp1[0].size() == 2 ?
swTriplet.retrieveAvailablePathsEp1().collect { it.getInvolvedIsls() } :
swTriplet.retrieveAvailablePathsEp2().collect { it.getInvolvedIsls() }
// 1 isl is equal to 2 pathNodes
def preferablePath = availablePaths.find { it.size() > 1 }
availablePaths.findAll { it != preferablePath }.each {
islHelper.makePathIslsMorePreferable(preferablePath, it)
}
}
def yFlow = yFlowFactory.getRandom(swTriplet, false)
def yFlowPath = yFlow.retrieveAllEntityPaths()
def intermediateSwId = yFlowPath.getInvolvedSwitches()
.find { !(it in [swTriplet.shared.dpId, swTriplet.ep1.dpId, swTriplet.ep2.dpId]) }
assert intermediateSwId
and: "Two active not neighboring switches and preferable path with intermediate switch"
def dstSw = swTriplet.pathsEp1[0].size() == 4 ? swTriplet.ep1 : swTriplet.ep2
def swPair = switchPairs.all().specificPair(swTriplet.shared, dstSw)
def availablePaths = swPair.retrieveAvailablePaths()
List<Isl> pathIsls = availablePaths.find { path -> intermediateSwId in path.getInvolvedSwitches() }.getInvolvedIsls()
availablePaths.collect { it.getInvolvedIsls() }.findAll { it != pathIsls }
.each { islHelper.makePathIslsMorePreferable(pathIsls, it) }
and: "Create a Flow going through these switches"
def flow = flowFactory.getRandom(swPair)
def flowPath = flow.retrieveAllEntityPaths()
assert flowPath.getInvolvedSwitches().contains(intermediateSwId)
when: "Set maintenance mode without flows evacuation flag for some intermediate switch involved in flow paths"
switchHelper.setSwitchMaintenance(intermediateSwId, true, false)
then: "Both Flow and Y-Flow are not evacuated (rerouted) and have the same paths"
timedLoop(3) {
assert flow.retrieveAllEntityPaths() == flowPath
assert yFlow.retrieveAllEntityPaths() == yFlowPath
}
when: "Set maintenance mode again with flows evacuation flag for the same switch"
northbound.setSwitchMaintenance(intermediateSwId, true, true)
then: "Both Flow and Y-Flow are evacuated (rerouted)"
Wrappers.wait(PATH_INSTALLATION_TIME + WAIT_OFFSET) {
assert flow.retrieveFlowStatus().status == FlowState.UP
assert yFlow.retrieveDetails().status == FlowState.UP
assert flow1PathUpdated.getInvolvedIsls() != pathIsls
assert flow2PathUpdated.getInvolvedIsls()!= pathIsls
assert flow.retrieveAllEntityPaths() != flowPath
assert yFlow.retrieveAllEntityPaths() != yFlowPath
}
and: "Switch under maintenance is not involved in new flow paths"
!flow1PathUpdated.getInvolvedSwitches().contains(swId)
!flow2PathUpdated.getInvolvedSwitches().contains(swId)
!flow.retrieveAllEntityPaths().getInvolvedSwitches().contains(intermediateSwId)
!yFlow.retrieveAllEntityPaths().getInvolvedSwitches().contains(intermediateSwId)
}

@Tags(ISL_RECOVER_ON_FAIL)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,18 +229,23 @@ public List<Integer> getBusyPortsForSwitch(SwitchId swId) {
* actual ISLs.
*/
@JsonIgnore
public List<Isl> getRelatedIsls(Switch sw) {
public List<Isl> getRelatedIsls(SwitchId swId) {
List<Isl> isls = getIslsForActiveSwitches().stream().filter(isl ->
isl.getSrcSwitch().getDpId().equals(sw.getDpId()) || isl.getDstSwitch().getDpId().equals(sw.getDpId()))
isl.getSrcSwitch().getDpId().equals(swId) || isl.getDstSwitch().getDpId().equals(swId))
.collect(toList());
for (Isl isl : isls) {
if (isl.getDstSwitch().getDpId().equals(sw.getDpId())) {
if (isl.getDstSwitch().getDpId().equals(swId)) {
isls.set(isls.indexOf(isl), isl.getReversed());
}
}
return isls;
}

@JsonIgnore
public List<Isl> getRelatedIsls(Switch sw) {
return getRelatedIsls(sw.getDpId());
}

/**
* Get random ISLs between two switches.
* Returns only outgoing from the first switch ISL.
Expand Down

0 comments on commit bd2e3db

Please sign in to comment.