diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/PathHelper.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/PathHelper.groovy new file mode 100644 index 0000000000..33af083aa9 --- /dev/null +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/PathHelper.groovy @@ -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 morePreferablePath, List lessPreferablePath) { + def morePreferableIsls = getInvolvedIsls(morePreferablePath) + def lessPreferableIsls = getInvolvedIsls(lessPreferablePath) + List uniqueIsls = (morePreferableIsls + lessPreferableIsls) + .unique { a, b -> a == b || a == b.reversed ? 0 : 1 } + HashMap 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 getInvolvedIsls(List path) { + if (path.size() % 2 != 0) { + throw new IllegalArgumentException("Path should have even amount of nodes") + } + if (path.empty) { + return new ArrayList() + } + 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 getInvolvedIsls(FlowPathPayload path) { + getInvolvedIsls(convert(path)) + } + + List getInvolvedIsls(String flowId) { + getInvolvedIsls(convert(northbound.getFlowPath(flowId))) + } + + /** + * Converts FlowPathPayload path representation to a List representation + */ + static List convert(FlowPathPayload pathPayload, pathToConvert = "forwardPath") { + def path = pathPayload."$pathToConvert" + getPathNodes(path) + } + + + + /** + * Returns a List representation of a path + */ + static List getPathNodes(path, boolean removeTail = true) { + if (path.empty) { + throw new IllegalArgumentException("Path cannot be empty. " + + "This should be impossible for valid FlowPathPayload") + } + List 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 path representation to the list of PathNodeV2, but with null segmentLatency field + */ + static List setNullLatency(List path) { + return path.collect { new PathNodeV2(it.switchId, it.portNo, null)} + } + + /** + * Get list of switches involved in a given path. + */ + List getInvolvedSwitches(List path) { + return (List) getInvolvedIsls(path).collect { [it.srcSwitch, it.dstSwitch] }.flatten().unique() + } + +} diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchPairs.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchPairs.groovy index 4ce3ec3403..95c53f8af5 100644 --- a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchPairs.groovy +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchPairs.groovy @@ -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 diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchTriplets.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchTriplets.groovy index 0a79d305ea..68d7248ecb 100644 --- a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchTriplets.groovy +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchTriplets.groovy @@ -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 } @@ -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 } } @@ -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() } } @@ -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)) } } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowCrudSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowCrudSpec.groovy index a8470f6aa1..032dfd6584 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowCrudSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowCrudSpec.groovy @@ -1,5 +1,8 @@ package org.openkilda.functionaltests.spec.flows +import org.openkilda.messaging.info.rule.FlowEntry + +import groovy.util.logging.Slf4j import org.openkilda.functionaltests.HealthCheckSpecification import org.openkilda.functionaltests.error.flow.FlowNotCreatedExpectedError import org.openkilda.functionaltests.error.flow.FlowNotCreatedWithConflictExpectedError @@ -19,7 +22,6 @@ import org.openkilda.functionaltests.helpers.model.PathComputationStrategy import org.openkilda.functionaltests.helpers.model.SwitchPair import org.openkilda.functionaltests.helpers.model.SwitchRulesFactory import org.openkilda.functionaltests.model.stats.Direction -import org.openkilda.messaging.info.rule.FlowEntry import org.openkilda.model.SwitchId import org.openkilda.model.cookie.Cookie import org.openkilda.model.cookie.CookieBase.CookieType @@ -30,15 +32,14 @@ import org.openkilda.testing.model.topology.TopologyDefinition.Isl import org.openkilda.testing.model.topology.TopologyDefinition.Switch import org.openkilda.testing.service.traffexam.TraffExamService import org.openkilda.testing.service.traffexam.model.ExamReport - -import groovy.util.logging.Slf4j -import jakarta.inject.Provider import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.client.HttpClientErrorException import spock.lang.Narrative import spock.lang.See import spock.lang.Shared +import javax.inject.Provider + import static groovyx.gpars.GParsPool.withPool import static org.junit.jupiter.api.Assumptions.assumeTrue import static org.openkilda.functionaltests.extension.tags.Tag.ISL_RECOVER_ON_FAIL @@ -53,8 +54,6 @@ import static org.openkilda.messaging.info.event.IslChangeType.DISCOVERED import static org.openkilda.messaging.info.event.IslChangeType.MOVED import static org.openkilda.messaging.payload.flow.FlowState.IN_PROGRESS import static org.openkilda.messaging.payload.flow.FlowState.UP -import static org.openkilda.model.FlowEncapsulationType.TRANSIT_VLAN -import static org.openkilda.model.FlowEncapsulationType.VXLAN import static org.openkilda.testing.Constants.PATH_INSTALLATION_TIME import static org.openkilda.testing.Constants.RULES_DELETION_TIME import static org.openkilda.testing.Constants.RULES_INSTALLATION_TIME @@ -389,7 +388,7 @@ class FlowCrudSpec extends HealthCheckSpecification { def expectedFlowEntity = flowFactory.getBuilder(srcSwitch, dstSwitch).build() and: "Another potential flow with #data.conflict" - FlowExtended conflictingFlow = data.makeFlowsConflicting(expectedFlowEntity, flowFactory.getBuilder(srcSwitch, dstSwitch)) + FlowExtended conflictingFlow = data.makeFlowsConflicting(expectedFlowEntity, flowFactory.getBuilder(srcSwitch, dstSwitch)) when: "Create the first flow" def flow = expectedFlowEntity.create() @@ -405,7 +404,7 @@ class FlowCrudSpec extends HealthCheckSpecification { flow.waitForBeingInState(UP) and: "Conflict flow does not exist in the system" - if (flow.flowId != conflictingFlow.flowId) { + if(flow.flowId != conflictingFlow.flowId) { !northboundV2.getAllFlows().find { it.flowId == conflictingFlow.flowId } } @@ -420,7 +419,7 @@ class FlowCrudSpec extends HealthCheckSpecification { int shortestIslCountPath = 1 def swPair = switchPairs.all().neighbouring().withAtLeastNNonOverlappingPaths(2).getSwitchPairs().find { availablePathsIsls = it.retrieveAvailablePaths().collect { it.getInvolvedIsls() } - availablePathsIsls.size() > 1 && availablePathsIsls.find { it.size() > shortestIslCountPath } + availablePathsIsls.size() > 1 && availablePathsIsls.find { it.size() > shortestIslCountPath } } ?: assumeTrue(false, "No suiting active neighboring switches with two possible flow paths at least and " + "different number of hops found") @@ -466,7 +465,7 @@ class FlowCrudSpec extends HealthCheckSpecification { data << [ [ isolatedSwitchType: "source", - switchPair : { Switch theSwitch -> + switchPair: { Switch theSwitch -> return switchPairs.all() .nonNeighbouring() .includeSourceSwitch(theSwitch) @@ -475,7 +474,7 @@ class FlowCrudSpec extends HealthCheckSpecification { ], [ isolatedSwitchType: "destination", - switchPair : { Switch theSwitch -> + switchPair: { Switch theSwitch -> return switchPairs.all() .nonNeighbouring() .includeSourceSwitch(theSwitch) @@ -517,8 +516,8 @@ class FlowCrudSpec extends HealthCheckSpecification { def swProps = switchHelper.getCachedSwProps(it) def amountOfServer42Rules = 0 - if (swProps.server42FlowRtt && it in [switchPair.src.dpId, switchPair.dst.dpId]) { - amountOfServer42Rules += 1 + if(swProps.server42FlowRtt && it in [switchPair.src.dpId, switchPair.dst.dpId]) { + amountOfServer42Rules +=1 it == switchPair.src.dpId && flow.source.vlanId && ++amountOfServer42Rules it == switchPair.dst.dpId && flow.destination.vlanId && ++amountOfServer42Rules } @@ -541,26 +540,26 @@ class FlowCrudSpec extends HealthCheckSpecification { expectedException.matches(actualException) where: - problem | update | expectedException - "invalid encapsulation type" | + problem | update | expectedException + "invalid encapsulation type" | { FlowBuilder flowToSpoil -> return flowToSpoil.withEncapsulationType(FlowEncapsulationType.FAKE).build() - } | + } | new FlowNotCreatedExpectedError( "No enum constant org.openkilda.messaging.payload.flow.FlowEncapsulationType.FAKE", ~/Can not parse arguments of the create flow request/) - "unavailable latency" | + "unavailable latency" | { FlowBuilder flowToSpoil -> return flowToSpoil.withMaxLatency(IMPOSSIBLY_LOW_LATENCY) .withPathComputationStrategy(PathComputationStrategy.MAX_LATENCY).build() - } | + } | new FlowNotCreatedWithMissingPathExpectedError( ~/Latency limit\: Requested path must have latency ${IMPOSSIBLY_LOW_LATENCY}ms or lower/) "invalid statistics vlan number" | { FlowBuilder flowToSpoil -> return flowToSpoil.withStatistics(FLOW_STATISTICS_CAUSING_ERROR) .withSourceVlan(0).build() - } | + } | new FlowNotCreatedExpectedError(~/To collect vlan statistics, the vlan IDs must be from 1 up to 4095/) } @@ -579,17 +578,17 @@ class FlowCrudSpec extends HealthCheckSpecification { expectedError.matches(actualError) where: - problem | invalidUpdateParam | expectedError + problem | invalidUpdateParam | expectedError "unavailable bandwidth" | { FlowExtended flowExtended -> return flowExtended.deepCopy().tap { it.maximumBandwidth = IMPOSSIBLY_HIGH_BANDWIDTH } - } | + } | new FlowNotUpdatedWithMissingPathExpectedError(~/Not enough bandwidth or no path found. \ Switch .* doesn't have links with enough bandwidth, \ Failed to find path with requested bandwidth=${IMPOSSIBLY_HIGH_BANDWIDTH}/) "vlan id is above 4095" | { FlowExtended flowExtended -> return flowExtended.deepCopy().tap { it.source.vlanId = 4096 } - } | + } | new FlowNotUpdatedExpectedError(~/Errors: VlanId must be less than 4096/) } @@ -717,7 +716,7 @@ Failed to find path with requested bandwidth=${IMPOSSIBLY_HIGH_BANDWIDTH}/) and: "Rules for main and protected paths are created" wait(WAIT_OFFSET) { HashMap> flowInvolvedSwitchesWithRules = flowPathInfo.getInvolvedSwitches() - .collectEntries { [(it): switchRulesFactory.get(it).getRules()] } as HashMap> + .collectEntries{ [(it): switchRulesFactory.get(it).getRules()] } as HashMap> flow.verifyRulesForProtectedFlowOnSwitches(flowInvolvedSwitchesWithRules) } @@ -730,7 +729,7 @@ Failed to find path with requested bandwidth=${IMPOSSIBLY_HIGH_BANDWIDTH}/) def protectedFlowPath = flowPathInfo.flowPath.protectedPath.forward when: "Update flow: disable protected path(allocateProtectedPath=false)" - def flowWithoutProtectedPath = flow.deepCopy().tap { it.allocateProtectedPath = false } + def flowWithoutProtectedPath = flow.deepCopy().tap { it.allocateProtectedPath = false} flow.update(flowWithoutProtectedPath) then: "Protected path is disabled" @@ -768,13 +767,13 @@ Failed to find path with requested bandwidth=${IMPOSSIBLY_HIGH_BANDWIDTH}/) def flow = expectedFlowEntity.create() then: "Flow is created with needed values" - verifyAll { - flow.priority == expectedFlowEntity.priority - flow.maxLatency == expectedFlowEntity.maxLatency - flow.maxLatencyTier2 == expectedFlowEntity.maxLatencyTier2 - flow.description == expectedFlowEntity.description - flow.periodicPings == expectedFlowEntity.periodicPings - } + verifyAll { + flow.priority == expectedFlowEntity.priority + flow.maxLatency == expectedFlowEntity.maxLatency + flow.maxLatencyTier2 == expectedFlowEntity.maxLatencyTier2 + flow.description == expectedFlowEntity.description + flow.periodicPings == expectedFlowEntity.periodicPings + } when: "Update predefined values" expectedFlowEntity = flow.deepCopy().tap { @@ -797,8 +796,7 @@ Failed to find path with requested bandwidth=${IMPOSSIBLY_HIGH_BANDWIDTH}/) def initialSrcProps = switchHelper.getCachedSwProps(swPair.src.dpId) switchHelper.updateSwitchProperties(swPair.getSrc(), initialSrcProps.jacksonCopy().tap { - it.supportedTransitEncapsulation = [FlowEncapsulationType.TRANSIT_VLAN.toString()] - }) + it.supportedTransitEncapsulation = [FlowEncapsulationType.TRANSIT_VLAN.toString()]}) when: "Create a flow with not supported encapsulation type on the switches" flowFactory.getBuilder(swPair) @@ -870,7 +868,7 @@ types .* or update switch properties and add needed encapsulation type./).matche it.source.portNumber = topology.getAllowedPortsForSwitch(srcSwitch).find { it != flow.source.portNumber } it.source.vlanId = flow.source.vlanId + 1 } - def updatedFlow = flow.update(flowExpectedEntity) + def updatedFlow = flow.update(flowExpectedEntity) then: "Flow is really updated" updatedFlow.hasTheSamePropertiesAs(flowExpectedEntity) @@ -890,7 +888,7 @@ types .* or update switch properties and add needed encapsulation type./).matche def flowInfoFromDb2 = database.getFlow(flow.flowId) wait(RULES_INSTALLATION_TIME) { def rules = switchRulesFactory.get(srcSwitch.dpId).getRules() - .findAll { !new Cookie(it.cookie).serviceFlag } + .findAll {!new Cookie(it.cookie).serviceFlag } rules.cookie.findAll { it == flowInfoFromDb2.forwardPath.cookie.value || it == flowInfoFromDb2.reversePath.cookie.value }.size() == 2 def ingressRule = rules.find { it.cookie == flowInfoFromDb2.forwardPath.cookie.value } @@ -931,15 +929,15 @@ types .* or update switch properties and add needed encapsulation type./).matche def flowInfoFromDb3 = database.getFlow(flow.flowId) wait(RULES_DELETION_TIME) { def cookies = switchRulesFactory.get(dstSwitch.dpId).getRules() - .findAll { !new Cookie(it.cookie).serviceFlag }.cookie - !cookies.findAll { it == flowInfoFromDb2.forwardPath.cookie.value || it == flowInfoFromDb2.reversePath.cookie.value } + .findAll {!new Cookie(it.cookie).serviceFlag }.cookie + !cookies.findAll { it == flowInfoFromDb2.forwardPath.cookie.value || it == flowInfoFromDb2.reversePath.cookie.value} } and: "Flow rules are installed on the new dst switch" wait(RULES_INSTALLATION_TIME) { def cookies = switchRulesFactory.get(newDstSwitch.dpId).getRules() - .findAll { !new Cookie(it.cookie).serviceFlag }.cookie - cookies.findAll { it == flowInfoFromDb3.forwardPath.cookie.value || it == flowInfoFromDb3.reversePath.cookie.value }.size() == 2 + .findAll {!new Cookie(it.cookie).serviceFlag }.cookie + cookies.findAll { it == flowInfoFromDb3.forwardPath.cookie.value || it == flowInfoFromDb3.reversePath.cookie.value}.size() == 2 } and: "Flow is valid and pingable" @@ -1080,7 +1078,7 @@ types .* or update switch properties and add needed encapsulation type./).matche when: "Update the dst endpoint to make this flow as multi switch flow" def newPortNumber = topology.getAllowedPortsForSwitch(topology.activeSwitches - .find { it.dpId == swPair.dst.dpId }).first() + .find {it.dpId == swPair.dst.dpId }).first() def flowExpectedEntity = flow.deepCopy().tap { it.destination.switchId = swPair.dst.dpId it.destination.portNumber = newPortNumber @@ -1118,7 +1116,7 @@ types .* or update switch properties and add needed encapsulation type./).matche @Tags([LOW_PRIORITY]) def "Unable to update flow with incorrect id in request body"() { - given: "A flow" + given:"A flow" def flow = flowFactory.getRandom(switchPairs.all().random()) def newFlowId = "new_flow_id" @@ -1129,7 +1127,7 @@ types .* or update switch properties and add needed encapsulation type./).matche then: "Bad Request response is returned" def error = thrown(HttpClientErrorException) new FlowNotUpdatedExpectedError("flow_id from body and from path are different", - ~/Body flow_id: ${newFlowId}, path flow_id: ${flow.flowId}/).matches(error) + ~/Body flow_id: ${newFlowId}, path flow_id: ${flow.flowId}/).matches(error) } @Tags([LOW_PRIORITY]) @@ -1137,7 +1135,7 @@ types .* or update switch properties and add needed encapsulation type./).matche given: "A flow" def flow = flowFactory.getRandom(switchPairs.all().random()) def newFlowId = "new_flow_id" - def invalidFlowEntity = flow.retrieveDetails().tap { it.flowId = newFlowId } + def invalidFlowEntity = flow.retrieveDetails().tap { it.flowId = newFlowId } when: "Try to update flow with incorrect flow id in request path" invalidFlowEntity.update(flow.tap { maximumBandwidth = maximumBandwidth + 1 }) @@ -1155,7 +1153,7 @@ types .* or update switch properties and add needed encapsulation type./).matche def flow = flowFactory.getBuilder(switches, false) .withSourceVlan(0) .withDestinationVlan(0) - .withStatistics(new FlowStatistics([1, 2, 3] as Set)).build() + .withStatistics( new FlowStatistics([1, 2, 3] as Set)).build() .create() when: "Try to #method update flow with empty VLAN stats and non-zero VLANs" @@ -1260,19 +1258,19 @@ types .* or update switch properties and add needed encapsulation type./).matche return switchPairs.inject([]) { r, switchPair -> boolean isTrafficAvailable = switchPair.src in topology.getActiveTraffGens().switchConnected && switchPair.dst in topology.getActiveTraffGens().switchConnected r << [ - description : "flow without transit switch and with random vlans", - expectedFlowEntity : flowFactory.getBuilder(switchPair).build(), - isTrafficCheckAvailable: isTrafficAvailable + description: "flow without transit switch and with random vlans", + expectedFlowEntity : flowFactory.getBuilder(switchPair).build(), + isTrafficCheckAvailable : isTrafficAvailable ] r << [ - description : "flow without transit switch and without vlans(full port)", - expectedFlowEntity : flowFactory.getBuilder(switchPair).withSourceVlan(0).withDestinationVlan(0).build(), - isTrafficCheckAvailable: isTrafficAvailable + description: "flow without transit switch and without vlans(full port)", + expectedFlowEntity : flowFactory.getBuilder(switchPair).withSourceVlan(0).withDestinationVlan(0).build(), + isTrafficCheckAvailable : isTrafficAvailable ] r << [ - description : "flow without transit switch and vlan only on src", - expectedFlowEntity : flowFactory.getBuilder(switchPair).withDestinationVlan(0).build(), - isTrafficCheckAvailable: isTrafficAvailable + description: "flow without transit switch and vlan only on src", + expectedFlowEntity : flowFactory.getBuilder(switchPair).withDestinationVlan(0).build(), + isTrafficCheckAvailable : isTrafficAvailable ] r } @@ -1289,19 +1287,19 @@ types .* or update switch properties and add needed encapsulation type./).matche return switchPairs.inject([]) { r, switchPair -> boolean isTrafficAvailable = switchPair.src in topology.getActiveTraffGens().switchConnected && switchPair.dst in topology.getActiveTraffGens().switchConnected r << [ - description : "flow with transit switch and random vlans", - expectedFlowEntity : flowFactory.getBuilder(switchPair).build(), - isTrafficCheckAvailable: isTrafficAvailable + description: "flow with transit switch and random vlans", + expectedFlowEntity : flowFactory.getBuilder(switchPair).build(), + isTrafficCheckAvailable : isTrafficAvailable ] r << [ - description : "flow with transit switch and no vlans(full port)", - expectedFlowEntity : flowFactory.getBuilder(switchPair).withSourceVlan(0).withDestinationVlan(0).build(), - isTrafficCheckAvailable: isTrafficAvailable + description: "flow with transit switch and no vlans(full port)", + expectedFlowEntity : flowFactory.getBuilder(switchPair).withSourceVlan(0).withDestinationVlan(0).build(), + isTrafficCheckAvailable : isTrafficAvailable ] r << [ - description : "flow with transit switch and vlan only on dst", - expectedFlowEntity : flowFactory.getBuilder(switchPair).withSourceVlan(0).build(), - isTrafficCheckAvailable: isTrafficAvailable + description: "flow with transit switch and vlan only on dst", + expectedFlowEntity : flowFactory.getBuilder(switchPair).withSourceVlan(0).build(), + isTrafficCheckAvailable : isTrafficAvailable ] r } @@ -1319,23 +1317,23 @@ types .* or update switch properties and add needed encapsulation type./).matche boolean isTrafficAvailable = topology.getActiveTraffGens().findAll { it.switchConnected == sw }.size() == 2 r << [ - description : "single-switch flow with vlans", - expectedFlowEntity : flowFactory.getBuilder(sw, sw).build(), - isTrafficCheckAvailable: isTrafficAvailable + description: "single-switch flow with vlans", + expectedFlowEntity : flowFactory.getBuilder(sw, sw).build(), + isTrafficCheckAvailable : isTrafficAvailable ] r << [ - description : "single-switch flow without vlans", + description: "single-switch flow without vlans", // if sw has only one tg than full port flow cannot be created on the same tg_port(conflict) - expectedFlowEntity : isTrafficAvailable ? + expectedFlowEntity : isTrafficAvailable ? flowFactory.getBuilder(sw, sw).withSourceVlan(0).withDestinationVlan(0).build() : flowFactory.getBuilder(sw, sw, false).withSourceVlan(0).withDestinationVlan(0).build(), - isTrafficCheckAvailable: isTrafficAvailable + isTrafficCheckAvailable : isTrafficAvailable ] r << [ - description : "single-switch flow with vlan only on dst", - expectedFlowEntity : flowFactory.getBuilder(sw, sw).withSourceVlan(0).build(), - isTrafficCheckAvailable: isTrafficAvailable + description: "single-switch flow with vlan only on dst", + expectedFlowEntity : flowFactory.getBuilder(sw, sw).withSourceVlan(0).build(), + isTrafficCheckAvailable : isTrafficAvailable ] r } @@ -1348,7 +1346,7 @@ types .* or update switch properties and add needed encapsulation type./).matche def getSingleSwitchSinglePortFlows() { topology.getActiveSwitches() .unique { it.description } - .collect { singleSwitch -> + .collect {singleSwitch -> flowFactory.getBuilder(singleSwitch, singleSwitch).withSamePortOnSourceAndDestination().build() } } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/MultiRerouteSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/MultiRerouteSpec.groovy index 609bd4639b..2b9823ccbc 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/MultiRerouteSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/MultiRerouteSpec.groovy @@ -11,7 +11,7 @@ import org.openkilda.functionaltests.helpers.factory.FlowFactory import org.openkilda.functionaltests.helpers.model.FlowExtended import org.openkilda.messaging.payload.flow.FlowState import org.openkilda.testing.model.topology.TopologyDefinition.Isl -import org.openkilda.testing.tools.SoftAssertionsWrapper +import org.openkilda.testing.tools.SoftAssertions import org.springframework.beans.factory.annotation.Autowired import spock.lang.Shared @@ -68,7 +68,7 @@ class MultiRerouteSpec extends HealthCheckSpecification { then: "Half of the flows are hosted on the preferable path" def flowsOnPrefPath wait(WAIT_OFFSET * 3) { - def assertions = new SoftAssertionsWrapper() + def assertions = new SoftAssertions() flowsOnPrefPath = flows.findAll { it.retrieveAllEntityPaths().flowPath.getInvolvedIsls() == prefPathIsls } @@ -82,7 +82,7 @@ class MultiRerouteSpec extends HealthCheckSpecification { and: "Rest of the flows are hosted on another alternative paths" def restFlows = flows.findAll { !flowsOnPrefPath*.flowId.contains(it.flowId) } wait(WAIT_OFFSET * 2) { - def assertions = new SoftAssertionsWrapper() + def assertions = new SoftAssertions() restFlows.each { flow -> assertions.checkSucceeds { assert flow.retrieveFlowStatus().status == FlowState.UP } assertions.checkSucceeds { assert flow.retrieveAllEntityPaths().flowPath.getInvolvedIsls() != prefPathIsls } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/haflows/HaFlowIntentionalRerouteSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/haflows/HaFlowIntentionalRerouteSpec.groovy index 48254eeca8..bfd08fc713 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/haflows/HaFlowIntentionalRerouteSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/haflows/HaFlowIntentionalRerouteSpec.groovy @@ -1,28 +1,27 @@ package org.openkilda.functionaltests.spec.flows.haflows -import static groovyx.gpars.GParsPool.withPool -import static org.junit.jupiter.api.Assumptions.assumeTrue -import static org.openkilda.functionaltests.extension.tags.Tag.HA_FLOW -import static org.openkilda.functionaltests.extension.tags.Tag.ISL_PROPS_DB_RESET -import static org.openkilda.functionaltests.helpers.model.FlowEncapsulationType.TRANSIT_VLAN -import static org.openkilda.testing.Constants.WAIT_OFFSET - import org.openkilda.functionaltests.HealthCheckSpecification import org.openkilda.functionaltests.extension.tags.Tags import org.openkilda.functionaltests.helpers.HaFlowFactory import org.openkilda.functionaltests.helpers.IslHelper +import org.openkilda.functionaltests.helpers.PathHelper import org.openkilda.functionaltests.helpers.Wrappers import org.openkilda.functionaltests.helpers.model.FlowWithSubFlowsEntityPath -import org.openkilda.functionaltests.helpers.model.SwitchTriplet +import org.openkilda.messaging.info.event.PathNode import org.openkilda.messaging.payload.flow.FlowState -import org.openkilda.northbound.dto.v2.flows.FlowPathV2.PathNodeV2 import org.openkilda.northbound.dto.v2.haflows.HaFlowRerouteResult import org.openkilda.testing.model.topology.TopologyDefinition.Isl - import org.springframework.beans.factory.annotation.Autowired import spock.lang.Narrative import spock.lang.Shared +import static groovyx.gpars.GParsPool.withPool +import static org.junit.jupiter.api.Assumptions.assumeTrue +import static org.openkilda.functionaltests.extension.tags.Tag.HA_FLOW +import static org.openkilda.functionaltests.extension.tags.Tag.ISL_PROPS_DB_RESET +import static org.openkilda.functionaltests.helpers.model.FlowEncapsulationType.TRANSIT_VLAN +import static org.openkilda.testing.Constants.WAIT_OFFSET + @Narrative("Verify that on-demand HA-Flow reroute operations are performed accurately.") @Tags([HA_FLOW]) class HaFlowIntentionalRerouteSpec extends HealthCheckSpecification { @@ -46,7 +45,7 @@ class HaFlowIntentionalRerouteSpec extends HealthCheckSpecification { islHelper.updateIslsCost(involvedIsls, IslHelper.NOT_PREFERABLE_COST * 3) and: "Make all alternative paths to have not enough bandwidth to handle the HA-Flow" - def alternativePaths = getAlternativesPaths(initialPath, swT) + def alternativePaths = getAlternativesPaths(initialPath, (swT.pathsEp1 + swT.pathsEp2)) setBandwidthForAlternativesPaths(involvedIsls, alternativePaths, haFlow.maximumBandwidth - 1) and: "Init a reroute to a more preferable path" @@ -59,10 +58,10 @@ class HaFlowIntentionalRerouteSpec extends HealthCheckSpecification { Wrappers.wait(WAIT_OFFSET) { assert northboundV2.getHaFlow(haFlow.haFlowId).status == FlowState.UP.toString() } assertRerouteResponsePaths(initialPath, rerouteResponse) - haFlow.retrievedAllEntityPaths() == initialPath + haFlow.retrievedAllEntityPaths() == initialPath and: "And involved switches pass validation" - def mainPathInvolvedSwitches = initialPath.getInvolvedSwitches() + def mainPathInvolvedSwitches = involvedIsls.collect { [it.srcSwitch.dpId, it.dstSwitch.dpId] }.flatten().unique() switchHelper.synchronizeAndCollectFixedDiscrepancies(mainPathInvolvedSwitches).isEmpty() and: "HA-Flow pass validation" @@ -78,32 +77,27 @@ class HaFlowIntentionalRerouteSpec extends HealthCheckSpecification { .withBandwidth(10000).build().create() def initialPath = haFlow.retrievedAllEntityPaths() + def initialPathNodesView = initialPath.subFlowPaths.collect { it.path.forward.nodes.toPathNode() } + def initialInvolvedIsls = initialPath.getInvolvedIsls() String ep1FlowId = haFlow.subFlows.find { it.endpointSwitchId == swT.ep1.dpId }.haSubFlowId String ep2FlowId = haFlow.subFlows.find { it.endpointSwitchId == swT.ep2.dpId }.haSubFlowId - List initialIslsSubFlow1 = initialPath.getSubFlowIsls(ep1FlowId) - List initialIslsSubFlow2 = initialPath.getSubFlowIsls(ep2FlowId) - - def initialFlowIsls = (initialIslsSubFlow1 + initialIslsSubFlow2).unique() - def initialInvolvedSwitchIds = initialPath.getInvolvedSwitches() + def initialInvolvedSwitchIds = initialInvolvedIsls.collect { [it.srcSwitch.dpId, it.dstSwitch.dpId] }.flatten().unique() when: "Make one of the alternative paths to be the most preferable among all others" - def availablePathsIslsEp1 = swT.retrieveAvailablePathsEp1().collect { it.getInvolvedIsls() } - def availablePathsIslsEp2 = swT.retrieveAvailablePathsEp2().collect { it.getInvolvedIsls() } - def preferableAltPathForSubFlow1 = availablePathsIslsEp1.find { it != initialIslsSubFlow1 } - def preferableAltPathForSubFlow2 = availablePathsIslsEp2.find { it != initialIslsSubFlow2 } + def preferableAltPathForSubFlow1 = swT.pathsEp1.find {!initialPathNodesView.contains(it)} + def preferableAltPathForSubFlow2 = swT.pathsEp2.find {!initialPathNodesView.contains(it)} withPool { - availablePathsIslsEp1.findAll { it != preferableAltPathForSubFlow1 }.eachParallel { - islHelper.makePathIslsMorePreferable(preferableAltPathForSubFlow1, it) + swT.pathsEp1.findAll { it != preferableAltPathForSubFlow1 }.eachParallel { + pathHelper.makePathMorePreferable(preferableAltPathForSubFlow1, it) } - availablePathsIslsEp2.findAll { it != preferableAltPathForSubFlow2 }.eachParallel { - islHelper.makePathIslsMorePreferable(preferableAltPathForSubFlow2, it) + swT.pathsEp2.findAll { it != preferableAltPathForSubFlow2 }.eachParallel { + pathHelper.makePathMorePreferable(preferableAltPathForSubFlow2, it) } } and: "Make the future path to have exact bandwidth to handle the HA-Flow" - def thinIsl = setBandwidthForAlternativesPaths(initialFlowIsls, - [preferableAltPathForSubFlow1 + preferableAltPathForSubFlow2], haFlow.maximumBandwidth) + def thinIsl = setBandwidthForAlternativesPaths(initialInvolvedIsls, [preferableAltPathForSubFlow1, preferableAltPathForSubFlow2], haFlow.maximumBandwidth) and: "Init a reroute of the HA-Flow" def rerouteResponse = haFlow.reroute() @@ -112,16 +106,20 @@ class HaFlowIntentionalRerouteSpec extends HealthCheckSpecification { then: "The HA-Flow is successfully rerouted and goes through the preferable path" rerouteResponse.rerouted def haFlowPathAfterReroute = haFlow.retrievedAllEntityPaths() - def actualFlowIslsAfterReroute = haFlowPathAfterReroute.getInvolvedIsls() + def actualIslsAfterReroute = haFlowPathAfterReroute.getInvolvedIsls() assertRerouteResponsePaths(haFlowPathAfterReroute, rerouteResponse) - assert haFlowPathAfterReroute.getSubFlowIsls(ep1FlowId) == preferableAltPathForSubFlow1 - assert haFlowPathAfterReroute.getSubFlowIsls(ep2FlowId) == preferableAltPathForSubFlow2 - actualFlowIslsAfterReroute.containsAll(thinIsl) + assert haFlowPathAfterReroute.subFlowPaths.find {it.flowId == ep1FlowId } + .path.forward.nodes.toPathNode() == preferableAltPathForSubFlow1 + + assert haFlowPathAfterReroute.subFlowPaths.find {it.flowId == ep2FlowId } + .path.forward.nodes.toPathNode() == preferableAltPathForSubFlow2 + + actualIslsAfterReroute.containsAll(thinIsl) and: "And involved switches pass validation" - def allInvolvedSwitchIds = (initialInvolvedSwitchIds + haFlowPathAfterReroute.getInvolvedSwitches()).unique() + def allInvolvedSwitchIds = initialInvolvedSwitchIds + actualIslsAfterReroute.collect { [it.srcSwitch.dpId, it.dstSwitch.dpId] }.flatten().unique() switchHelper.synchronizeAndCollectFixedDiscrepancies(allInvolvedSwitchIds).isEmpty() and: "HA-Flow pass validation" @@ -144,10 +142,10 @@ class HaFlowIntentionalRerouteSpec extends HealthCheckSpecification { def initialPath = haFlow.retrievedAllEntityPaths() def initialInvolvedIsls = initialPath.getInvolvedIsls() - def initialInvolvedSwitchIds = initialPath.getInvolvedSwitches() + def initialInvolvedSwitchIds = initialInvolvedIsls.collect { [it.srcSwitch.dpId, it.dstSwitch.dpId] }.flatten().unique() when: "Make the current path less preferable than alternatives" - def alternativePaths = getAlternativesPaths(initialPath, swT) + def alternativePaths = getAlternativesPaths(initialPath, (swT.pathsEp1 + swT.pathsEp2)) islHelper.updateIslsCost(initialInvolvedIsls, IslHelper.NOT_PREFERABLE_COST * 3) and: "Make all alternative paths to have not enough bandwidth to handle the HA-Flow" @@ -162,19 +160,19 @@ class HaFlowIntentionalRerouteSpec extends HealthCheckSpecification { initialPath.subFlowPaths.size() == rerouteResponse.subFlowPaths.size() initialPath.subFlowPaths.each {subFlow -> - def rerouteSubFlowPath = getSubFlowRerouteNodesResponse(rerouteResponse, subFlow.flowId) - def subFlowNodes = subFlow.path.forward.nodes.toPathNodeV2() - assert subFlowNodes != rerouteSubFlowPath + def rerouteSubFlowPath = rerouteResponse.subFlowPaths.find { it.flowId == subFlow.flowId}.nodes + assert subFlow.path.forward.nodes.toPathNodeV2() != PathHelper.setNullLatency(rerouteSubFlowPath) } haFlow.waitForBeingInState(FlowState.UP) def haFlowPathAfterReroute = haFlow.retrievedAllEntityPaths() + def actualIslsAfterReroute = haFlowPathAfterReroute.getInvolvedIsls() assertRerouteResponsePaths(haFlowPathAfterReroute, rerouteResponse) haFlowPathAfterReroute != initialPath and: "And involved switches pass validation" - def allInvolvedSwitchIds = (initialInvolvedSwitchIds + haFlowPathAfterReroute.getInvolvedSwitches()).unique() + def allInvolvedSwitchIds = initialInvolvedSwitchIds + actualIslsAfterReroute.collect { [it.srcSwitch.dpId, it.dstSwitch.dpId] }.flatten() switchHelper.synchronizeAndCollectFixedDiscrepancies(allInvolvedSwitchIds).isEmpty() and: "HA-Flow pass validation" @@ -191,31 +189,31 @@ class HaFlowIntentionalRerouteSpec extends HealthCheckSpecification { } } - private List> getAlternativesPaths(FlowWithSubFlowsEntityPath haFlowPath, SwitchTriplet swT) { - List> subFlowsPathIsls = haFlowPath.subFlowPaths.collect { it.getInvolvedIsls() } - (swT.retrieveAvailablePathsEp1() + swT.retrieveAvailablePathsEp2()).collect { it.getInvolvedIsls() }.findAll{ - //HA-Flow has 2 sub-flows - it != subFlowsPathIsls.first() && it != subFlowsPathIsls.last() + private getAlternativesPaths(FlowWithSubFlowsEntityPath actualPathNodes, List> existingPaths) { + existingPaths.findAll { + !actualPathNodes.subFlowPaths.collect { it.path.forward.nodes.toPathNode() }.contains(it) } } private void assertRerouteResponsePaths(FlowWithSubFlowsEntityPath haFlowPath, HaFlowRerouteResult rerouteResponse) { assert haFlowPath.subFlowPaths.size() == rerouteResponse.subFlowPaths.size() haFlowPath.subFlowPaths.each {subFlow -> - def rerouteSubFlowPath = getSubFlowRerouteNodesResponse(rerouteResponse, subFlow.flowId) - def subFlowNodes = subFlow.path.forward.nodes.toPathNodeV2() - assert subFlowNodes == rerouteSubFlowPath + def rerouteSubFlowPath = rerouteResponse.subFlowPaths.find { it.flowId == subFlow.flowId}.nodes + assert subFlow.path.forward.nodes.toPathNodeV2() == PathHelper.setNullLatency(rerouteSubFlowPath) } } - private Collection setBandwidthForAlternativesPaths(List flowIsls, List> alternativePaths, long newBandwidth) { - Set changedIsls = alternativePaths.flatten().unique().findAll { !flowIsls.contains(it) && !flowIsls.contains(it.reversed) } + private Collection setBandwidthForAlternativesPaths(currentIsls, alternativePaths, long newBandwidth) { + //collecting only the first Isl from each alternative path + Set changedIsls = withPool { + alternativePaths.collectParallel { List altPath -> + pathHelper.getInvolvedIsls(altPath).find { + !currentIsls.contains(it) && !currentIsls.contains(it.reversed) + } + } + } as Set + islHelper.setAvailableAndMaxBandwidth(changedIsls.collectMany {[it, it.reversed]}, newBandwidth) changedIsls } - - private List getSubFlowRerouteNodesResponse(HaFlowRerouteResult rerouteResult, String subFlowId) { - rerouteResult.subFlowPaths.find { it.flowId == subFlowId}.nodes - .collect { PathNodeV2.builder().switchId(it.switchId).portNo(it.portNo).segmentLatency(null).build() } - } } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowRerouteSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowRerouteSpec.groovy index 3a8319f139..c149fdd992 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowRerouteSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowRerouteSpec.groovy @@ -6,7 +6,6 @@ import static org.openkilda.functionaltests.extension.tags.Tag.ISL_RECOVER_ON_FA import static org.openkilda.functionaltests.extension.tags.Tag.LOW_PRIORITY import static org.openkilda.functionaltests.extension.tags.Tag.TOPOLOGY_DEPENDENT import static org.openkilda.functionaltests.helpers.Wrappers.wait -import static org.openkilda.functionaltests.model.stats.Direction.* import static org.openkilda.functionaltests.model.stats.FlowStatsMetric.FLOW_RAW_BYTES import static org.openkilda.testing.Constants.FLOW_CRUD_TIMEOUT import static org.openkilda.testing.Constants.WAIT_OFFSET @@ -33,7 +32,7 @@ import org.springframework.web.client.HttpClientErrorException import spock.lang.Narrative import spock.lang.Shared -import jakarta.inject.Provider +import javax.inject.Provider @Slf4j @Narrative("Verify reroute operations on y-flows.") @@ -52,7 +51,7 @@ class YFlowRerouteSpec extends HealthCheckSpecification { given: "A qinq y-flow" def swT = switchTriplets.all().withAllDifferentEndpoints().withoutWBSwitch().getSwitchTriplets().find { def yPoints = topologyHelper.findPotentialYPoints(it) - yPoints.size() == 1 && yPoints[0] != it.shared.dpId + yPoints.size() == 1 && yPoints[0] != it.shared } assumeTrue(swT != null, "These cases cannot be covered on given topology:") @@ -155,12 +154,10 @@ class YFlowRerouteSpec extends HealthCheckSpecification { def swT = switchTriplets.all().withAllDifferentEndpoints().withSharedEpEp1Ep2InChain().random() and: "The ISLs cost between switches has been changed to make preferable path" - def pathsEp1 = swT.retrieveAvailablePathsEp1().collect { it.getInvolvedIsls() } - def pathsEp2 = swT.retrieveAvailablePathsEp2().collect { it.getInvolvedIsls() } - List directSwTripletIsls = (pathsEp1[0].size() == 1 ? - pathsEp2.findAll { it.size() == 2 && it.containsAll(pathsEp1[0])} : - pathsEp1.findAll { it.size() == 2 && it.containsAll(pathsEp2[0])}) - .flatten().unique() + List directSwTripletIsls = (swT.pathsEp1[0].size() == 2 ? + swT.pathsEp2.findAll { it.size() == 4 && it.containsAll(swT.pathsEp1[0]) } : + swT.pathsEp1.findAll { it.size() == 4 && it.containsAll(swT.pathsEp2[0]) }) + .collectMany { pathHelper.getInvolvedIsls(it) }.collectMany { [it, it.reversed] }.unique() islHelper.updateIslsCost(directSwTripletIsls, 1) and: "Y-Flow with shared path has been created successfully" @@ -185,10 +182,12 @@ class YFlowRerouteSpec extends HealthCheckSpecification { def yFlowPathAfterReroute = yFlow.retrieveAllEntityPaths() def sharedPathIslAfterReroute = yFlowPathAfterReroute.sharedPath.getInvolvedIsls() - assert sharedPathIslAfterReroute.sort() != sharedPathIslBeforeReroute.sort() - yFlowPathAfterReroute.subFlowPaths.each { subFlow -> - assert yFlowPathBeforeReroute.getSubFlowIsls(subFlow.flowId, FORWARD) != subFlow.getInvolvedIsls(FORWARD) - assert yFlowPathBeforeReroute.getSubFlowIsls(subFlow.flowId, REVERSE) != subFlow.getInvolvedIsls(REVERSE) + verifyAll { + sharedPathIslAfterReroute.sort() != sharedPathIslBeforeReroute.sort() + yFlowPathAfterReroute.subFlowPaths.first().path.forward != yFlowPathBeforeReroute.subFlowPaths.first().path.forward + yFlowPathAfterReroute.subFlowPaths.first().path.reverse != yFlowPathBeforeReroute.subFlowPaths.first().path.reverse + yFlowPathAfterReroute.subFlowPaths.last().path.forward != yFlowPathBeforeReroute.subFlowPaths.last().path.forward + yFlowPathAfterReroute.subFlowPaths.last().path.reverse != yFlowPathBeforeReroute.subFlowPaths.last().path.reverse } } @@ -199,12 +198,10 @@ class YFlowRerouteSpec extends HealthCheckSpecification { def swT = switchTriplets.all().withAllDifferentEndpoints().withSharedEpEp1Ep2InChain().random() and: "The ISLs cost between switches has been changed to make preferable path" - def pathsEp1 = swT.retrieveAvailablePathsEp1().collect { it.getInvolvedIsls() } - def pathsEp2 = swT.retrieveAvailablePathsEp2().collect { it.getInvolvedIsls() } - List directSwTripletIsls = (pathsEp1[0].size() == 1 ? - pathsEp2.findAll { it.size() == 2 && it.containsAll(pathsEp1[0])} : - pathsEp1.findAll { it.size() == 2 && it.containsAll(pathsEp2[0])}) - .flatten().unique() + List directSwTripletIsls = (swT.pathsEp1[0].size() == 2 ? + swT.pathsEp2.findAll { it.size() == 4 && it.containsAll(swT.pathsEp1[0])} : + swT.pathsEp1.findAll { it.size() == 4 && it.containsAll(swT.pathsEp2[0])}) + .collectMany { pathHelper.getInvolvedIsls(it) }.collectMany{[it, it.reversed]}.unique() islHelper.updateIslsCost(directSwTripletIsls, 1) and: "Y-Flow with shared path has been created successfully" @@ -213,11 +210,11 @@ class YFlowRerouteSpec extends HealthCheckSpecification { assert !yFlowPathBeforeReroute.sharedPath.path.isPathAbsent() and: "The required ISLs cost has been updated to make manual reroute available" - def islsSubFlow1 = (yFlowPathBeforeReroute.subFlowPaths.first().getInvolvedIsls(FORWARD) - + yFlowPathBeforeReroute.subFlowPaths.first().getInvolvedIsls(REVERSE)).unique() + def islsSubFlow1 = (yFlowPathBeforeReroute.subFlowPaths.first().getInvolvedIsls(Direction.FORWARD) + + yFlowPathBeforeReroute.subFlowPaths.first().getInvolvedIsls(Direction.REVERSE)).unique() - def islsSubFlow2 = (yFlowPathBeforeReroute.subFlowPaths.last().getInvolvedIsls(FORWARD) - + yFlowPathBeforeReroute.subFlowPaths.last().getInvolvedIsls(REVERSE)).unique() + def islsSubFlow2 = (yFlowPathBeforeReroute.subFlowPaths.last().getInvolvedIsls(Direction.FORWARD) + + yFlowPathBeforeReroute.subFlowPaths.last().getInvolvedIsls(Direction.REVERSE)).unique() assert islsSubFlow1 != islsSubFlow2, "Y-Flow path doesn't allow us to the check this case as subFlows have the same ISLs" @@ -245,12 +242,11 @@ class YFlowRerouteSpec extends HealthCheckSpecification { yFlow.waitForBeingInState(FlowState.UP, FLOW_CRUD_TIMEOUT) def yFlowPathAfterReroute = yFlow.retrieveAllEntityPaths() - def additionalSubFlowId = yFlow.subFlows.flowId.find { it != subFlowId } verifyAll { - assert yFlowPathAfterReroute.getSubFlowIsls(subFlowId, FORWARD) != yFlowPathBeforeReroute.getSubFlowIsls(subFlowId, FORWARD) - assert yFlowPathAfterReroute.getSubFlowIsls(subFlowId, REVERSE) != yFlowPathBeforeReroute.getSubFlowIsls(subFlowId, REVERSE) - assert yFlowPathAfterReroute.getSubFlowIsls(additionalSubFlowId, FORWARD) == yFlowPathBeforeReroute.getSubFlowIsls(additionalSubFlowId, FORWARD) - assert yFlowPathAfterReroute.getSubFlowIsls(additionalSubFlowId, REVERSE) == yFlowPathBeforeReroute.getSubFlowIsls(additionalSubFlowId, REVERSE) + yFlowPathAfterReroute.subFlowPaths.find { it.flowId == subFlowId }.path.forward != yFlowPathBeforeReroute.subFlowPaths.find { it.flowId == subFlowId }.path.forward + yFlowPathAfterReroute.subFlowPaths.find { it.flowId == subFlowId }.path.reverse != yFlowPathBeforeReroute.subFlowPaths.find { it.flowId == subFlowId }.path.reverse + yFlowPathAfterReroute.subFlowPaths.find { it.flowId != subFlowId }.path.forward == yFlowPathBeforeReroute.subFlowPaths.find { it.flowId != subFlowId }.path.forward + yFlowPathAfterReroute.subFlowPaths.find { it.flowId != subFlowId }.path.reverse == yFlowPathBeforeReroute.subFlowPaths.find { it.flowId != subFlowId }.path.reverse } }