Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TEST]: Isl/Switch maintenance mode: Y-Flow evacuation #5754

Merged
merged 1 commit into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading