From 21e30f0bc6270adf5f7c32a78084a3236b07f2ac Mon Sep 17 00:00:00 2001 From: Yuliia Miroshnychenko Date: Fri, 17 Jan 2025 12:56:09 +0100 Subject: [PATCH] [TEST]: Improvement: Switches: New interaction approach: Phase2 --- .../helpers/model/SwitchExtended.groovy | 41 +++++- .../helpers/model/Switches.groovy | 25 ++++ .../spec/switches/MultiFloodlightsSpec.groovy | 57 ++++---- .../spec/switches/PortHistorySpec.groovy | 5 +- .../spec/switches/PortPropertiesSpec.groovy | 10 +- .../spec/switches/SwitchActivationSpec.groovy | 126 ++++++++---------- .../spec/switches/SwitchDeleteSpec.groovy | 109 ++++++++------- .../spec/switches/SwitchFailuresSpec.groovy | 36 ++--- .../spec/switches/SwitchesFlowsV2Spec.groovy | 45 ++++--- 9 files changed, 251 insertions(+), 203 deletions(-) diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchExtended.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchExtended.groovy index 858cfe4c68a..7442b1b155d 100644 --- a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchExtended.groovy +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchExtended.groovy @@ -73,6 +73,8 @@ import org.openkilda.northbound.dto.v1.switches.SwitchPropertiesDto import org.openkilda.northbound.dto.v1.switches.SwitchSyncResult import org.openkilda.northbound.dto.v2.switches.LagPortResponse import org.openkilda.northbound.dto.v2.switches.MeterInfoDtoV2 +import org.openkilda.northbound.dto.v2.switches.SwitchConnectEntry +import org.openkilda.northbound.dto.v2.switches.SwitchConnectedDevicesResponse import org.openkilda.northbound.dto.v2.switches.SwitchFlowsPerPortResponse import org.openkilda.northbound.dto.v2.switches.SwitchLocationDtoV2 import org.openkilda.northbound.dto.v2.switches.SwitchPatchDto @@ -82,6 +84,7 @@ import org.openkilda.testing.service.floodlight.model.Floodlight import org.openkilda.testing.service.floodlight.model.FloodlightConnectMode import org.openkilda.testing.service.lockkeeper.LockKeeperService import org.openkilda.testing.service.lockkeeper.model.FloodlightResourceAddress +import org.openkilda.testing.service.lockkeeper.model.TrafficControlData import org.openkilda.testing.service.northbound.NorthboundService import org.openkilda.testing.service.northbound.NorthboundServiceV2 import org.openkilda.testing.service.northbound.payloads.SwitchValidationExtendedResult @@ -151,8 +154,8 @@ class SwitchExtended { @Override String toString() { - return String.format("Switch: %s, islPorts: %s, traffGen(s) port(s) %s, nbDetails: %s", - switchId, islPorts, traffGenPorts, nbDetails) + return String.format("Switch: %s, islPorts: %s, traffGen(s) port(s) %s, nbDetails: %s, FL regions: %s", + switchId, islPorts, traffGenPorts, nbDetails, regions) } @JsonIgnore @@ -175,6 +178,10 @@ class SwitchExtended { sw.ofVersion } + List getRegions(){ + sw.regions + } + /** * * Get list of switch ports excluding the ports which are busy with ISLs or s42. @@ -512,12 +519,24 @@ class SwitchExtended { return northbound.deleteSwitch(sw.dpId, force) } + SwitchConnectedDevicesResponse getConnectedDevices() { + northboundV2.getConnectedDevices(switchId) + } + boolean isS42FlowRttEnabled() { def swProps = northbound.getSwitchProperties(sw.dpId) def featureToggles = northbound.getFeatureToggles() swProps.server42FlowRtt && featureToggles.server42FlowRtt } + /*** + * getting switch connections details to floodlight + * @return list of floodlight connections + */ + List getConnectedFloodLights() { + northboundV2.getSwitchConnections(sw.dpId).connections + } + /*** * Floodlight interaction */ @@ -546,9 +565,10 @@ class SwitchExtended { * @param FL mode * @param waitForRelatedLinks make sure that all switch related ISLs are FAILED */ - List knockout(FloodlightConnectMode mode, boolean waitForRelatedLinks, double timeout = WAIT_OFFSET) { + List knockout(FloodlightConnectMode mode, boolean waitForRelatedLinks, + boolean waitForRelatedLinksWhenRecover = true, double timeout = WAIT_OFFSET) { def blockData = lockKeeper.knockoutSwitch(sw, mode) - cleanupManager.addAction(REVIVE_SWITCH, { revive(blockData, true) }, CleanupAfter.TEST) + cleanupManager.addAction(REVIVE_SWITCH, { revive(blockData, waitForRelatedLinksWhenRecover) }, CleanupAfter.TEST) Wrappers.wait(timeout) { assert northbound.getSwitch(sw.dpId).state == DEACTIVATED } @@ -565,6 +585,10 @@ class SwitchExtended { knockout(mode, false) } + List knockoutWithoutLinksCheckWhenRecover(FloodlightConnectMode mode) { + knockout(mode, false, false) + } + List knockout(List regions) { def blockData = lockKeeper.knockoutSwitch(sw, regions) cleanupManager.addAction(REVIVE_SWITCH, { revive(blockData, true) }, CleanupAfter.TEST) @@ -609,6 +633,15 @@ class SwitchExtended { relatedLinks.each { isl -> assert isl.state == expectedState } } + void shapeTraffic(TrafficControlData tcData) { + cleanupManager.addAction(OTHER, { cleanupTrafficShaperRules() }) + lockKeeper.shapeSwitchesTraffic([sw], tcData) + } + + void cleanupTrafficShaperRules() { + lockKeeper.cleanupTrafficShaperRules(sw.regions.flatten()) + } + void waitForS42FlowRttRulesSetup(boolean isS42ToggleOn = true) { SwitchPropertiesDto switchDetails = getProps() Wrappers.wait(RULES_INSTALLATION_TIME) { diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/Switches.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/Switches.groovy index 2c418e882b9..b1f158d437e 100644 --- a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/Switches.groovy +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/Switches.groovy @@ -7,6 +7,7 @@ import static org.openkilda.functionaltests.model.switches.Manufacturer.CENTEC import static org.openkilda.functionaltests.model.switches.Manufacturer.NOVIFLOW import static org.openkilda.functionaltests.model.switches.Manufacturer.OVS import static org.openkilda.functionaltests.model.switches.Manufacturer.WB5164 +import static org.openkilda.testing.service.floodlight.model.FloodlightConnectMode.RW import static org.springframework.beans.factory.config.ConfigurableBeanFactory.SCOPE_PROTOTYPE import org.openkilda.functionaltests.helpers.factory.SwitchFactory @@ -14,6 +15,7 @@ import org.openkilda.functionaltests.model.switches.Manufacturer import org.openkilda.model.SwitchId import org.openkilda.northbound.dto.v1.switches.SwitchDto import org.openkilda.testing.model.topology.TopologyDefinition +import org.openkilda.testing.service.floodlight.FloodlightsHelper import org.openkilda.testing.service.northbound.NorthboundService import org.openkilda.testing.service.northbound.NorthboundServiceV2 @@ -36,6 +38,8 @@ class Switches { @Qualifier("islandNbV2") NorthboundServiceV2 northboundV2 @Autowired + FloodlightsHelper flHelper + @Autowired SwitchFactory switchFactory List switches @@ -77,6 +81,27 @@ class Switches { return this } + + Switches withConnectedToExactlyNManagementFls(int flAmount) { + switches.findAll { flHelper.filterRegionsByMode(it.regions, RW).size() == flAmount } + return this + } + + Switches withConnectedToAtLeastNFls(int flAmount) { + switches.findAll { flHelper.filterRegionsByMode(it.sw.regions, RW).size() >= flAmount } + return this + } + + Switches withVxlanEnabled() { + switches.findAll { it.isVxlanEnabled() } + return this + } + + Switches withTraffGens() { + switches.findAll { !it.traffGenPorts.isEmpty() } + return this + } + SwitchExtended random() { assumeFalse(switches.isEmpty(), "No suiting switch found") switches.shuffled().first() diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/MultiFloodlightsSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/MultiFloodlightsSpec.groovy index 36535af2c2e..17faadde3f1 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/MultiFloodlightsSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/MultiFloodlightsSpec.groovy @@ -15,7 +15,6 @@ import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeUnit -import static org.junit.jupiter.api.Assumptions.assumeTrue import static org.openkilda.functionaltests.extension.tags.Tag.LOCKKEEPER import static org.openkilda.functionaltests.extension.tags.Tag.SWITCH_RECOVER_ON_FAIL import static org.openkilda.functionaltests.helpers.Wrappers.wait @@ -36,29 +35,30 @@ All switch floodlights can be checked via 'GET /api/v2/switches/{switchId}/conne class MultiFloodlightsSpec extends HealthCheckSpecification { @Shared ExecutorService executor = Executors.newFixedThreadPool(2) + @Tags([SWITCH_RECOVER_ON_FAIL]) def "Switch remains online only if at least one of multiple RW floodlights is available"() { given: "Switch simultaneously connected to 2 management floodlights" def cleanupActions = [] - def sw = topology.switches.find { flHelper.filterRegionsByMode(it.regions, RW).size() == 2 } - assumeTrue(sw.asBoolean(), "Require a switch with 2 active regions") + def sw = switches.all().withConnectedToExactlyNManagementFls(2).first() and: "Background observer monitoring the state of switch and its ISLs" - def relatedIsls = topology.getRelatedIsls(sw).collectMany { [it, it.reversed] } + def relatedIsls = topology.getRelatedIsls(sw.switchId).collectMany { [it, it.reversed] } + def islObserver = new LoopTask({ def soft = new SoftAssertions() relatedIsls.each { isl -> soft.checkSucceeds { assert northbound.getLink(isl).state == DISCOVERED } } soft.verify() }) - def swObserver = new LoopTask({ assert northbound.getSwitch(sw.dpId).state == ACTIVATED }) + def swObserver = new LoopTask({ assert sw.getDetails().state == ACTIVATED }) def islObserverFuture = executor.submit(islObserver, "ok") def swObserverFuture = executor.submit(swObserver, "ok") when: "Switch loses connection to one of the regions" - def knockout1 = lockKeeper.knockoutSwitch(sw, [flHelper.filterRegionsByMode(sw.regions, RW)[0]]) - cleanupActions << { lockKeeper.reviveSwitch(sw, knockout1) } + def knockout1 = sw.knockout([flHelper.filterRegionsByMode(sw.regions, RW)[0]]) + cleanupActions << { sw.revive(knockout1) } then: "Switch can still return its rules" - Wrappers.retry(2, 0.5) { !northbound.getSwitchRules(sw.dpId).flowEntries.empty } + Wrappers.retry(2, 0.5) { !sw.rulesManager.getRules().isEmpty() } when: "Broken connection gets fixed, but the second floodlight loses connection to kafka" cleanupActions.pop().call() //lockKeeper.reviveSwitch(sw, knockout1) @@ -67,42 +67,42 @@ class MultiFloodlightsSpec extends HealthCheckSpecification { cleanupActions << { lockKeeper.reviveFloodlight(sw.regions[1]) } then: "Switch can still return its rules" - Wrappers.retry(2, 0.5) { !northbound.getSwitchRules(sw.dpId).flowEntries.empty } + Wrappers.retry(2, 0.5) { !sw.rulesManager.getRules().isEmpty() } and: "All this time switch monitor saw switch as Active" swObserver.stop() swObserverFuture.get() when: "Switch loses connection to the last active region" - def knockout2 = lockKeeper.knockoutSwitch(sw, [flHelper.filterRegionsByMode(sw.regions, RW)[0]]) - cleanupActions << { lockKeeper.reviveSwitch(sw, knockout2) } + def knockout2 = sw.knockout([flHelper.filterRegionsByMode(sw.regions, RW)[0]]) + cleanupActions << { sw.revive(knockout2) } then: "Switch is marked as inactive" wait(WAIT_OFFSET) { - northbound.getSwitch(sw.dpId).state == DEACTIVATED + sw.getDetails().state == DEACTIVATED } when: "Try getting switch rules" - northbound.getSwitchRules(sw.dpId) + sw.rulesManager.getRules() then: "Human readable error is returned" def e = thrown(HttpClientErrorException) new SwitchNotFoundExpectedError( - "Switch $sw.dpId was not found", ~/The switch was not found when requesting a rules dump./).matches(e) + "Switch $sw.switchId was not found", ~/The switch was not found when requesting a rules dump./).matches(e) when: "Broken region restores connection to kafka" cleanupActions.pop().call() //lockKeeper.reviveFloodlight(sw.regions[1]) then: "Switch becomes activated and can respond" - wait(WAIT_OFFSET) { assert northbound.getSwitch(sw.dpId).state == ACTIVATED } - !northbound.getSwitchRules(sw.dpId).flowEntries.empty + wait(WAIT_OFFSET) { assert sw.getDetails().state == ACTIVATED } + !sw.rulesManager.getRules().isEmpty() when: "Switch restores connection to the other region" cleanupActions.pop().call() //lockKeeper.reviveSwitch(sw, knockout2) then: "Switch can still return its rules and remains active" - !northbound.getSwitchRules(sw.dpId).flowEntries.empty - northbound.getSwitch(sw.dpId).state == ACTIVATED + !sw.rulesManager.getRules().isEmpty() + sw.getDetails().state == ACTIVATED and: "ISL monitor reports that all switch-related ISLs have been Up all the time" islObserver.stop() @@ -117,28 +117,31 @@ class MultiFloodlightsSpec extends HealthCheckSpecification { @Tags([LOCKKEEPER, SWITCH_RECOVER_ON_FAIL]) def "System supports case when switch uses different networks to connect to FLs, i.e. has diff ips"() { given: "A switch with at least 2 regions available" - def sw = topology.activeSwitches.find { it.regions.size() >= 2 } - assumeTrue(sw.asBoolean(), "Couldn't find a switch with at least 2 regions available") + def switchToInteract = switches.all().withConnectedToAtLeastNFls(2).first() and: "The switch's ip in first region is different from other" - def originalSwIp = sw.nbFormat().getAddress() - def region = sw.getRegions().first() + def originalSwIp = switchToInteract.nbFormat().getAddress() + def region = switchToInteract.getRegions().first() lockKeeper.changeSwIp(region, originalSwIp, DUMMY_SW_IP_1) and: "The switch is currently disconnected" - def blockData = switchHelper.knockoutSwitch(sw, RW) + def blockData = lockKeeper.knockoutSwitch(switchToInteract.sw, RW) + wait(WAIT_OFFSET) { + assert switchToInteract.getDetails().state == DEACTIVATED + } when: "Test switch gets connected to both regions" - switchHelper.reviveSwitch(sw, blockData) + switchToInteract.revive(blockData) then: "Get switch connections API returns different ips for regions" - def connections = northboundV2.getSwitchConnections(sw.dpId) - connections.connections.each { + def connections = switchToInteract.getConnectedFloodLights() + connections.each { assert it.switchAddress.startsWith(DUMMY_SW_IP_1) == (it.regionName == region) } cleanup: region && lockKeeper.cleanupIpChanges(region) - sw && switchHelper.reviveSwitch(sw, switchHelper.knockoutSwitch(sw, RW)) + //this additional knockout is required to revive switch with the correct IP + switchToInteract && switchToInteract.revive(lockKeeper.knockoutSwitch(switchToInteract.sw, RW)) } } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/PortHistorySpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/PortHistorySpec.groovy index b1845187db6..e5889dbba54 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/PortHistorySpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/PortHistorySpec.groovy @@ -26,7 +26,6 @@ import static org.openkilda.functionaltests.helpers.model.PortHistoryEvent.ANTI_ import static org.openkilda.functionaltests.helpers.model.PortHistoryEvent.ANTI_FLAP_DEACTIVATED import static org.openkilda.functionaltests.helpers.model.PortHistoryEvent.ANTI_FLAP_PERIODIC_STATS import static org.openkilda.functionaltests.helpers.model.PortHistoryEvent.PORT_DOWN -import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.RESTORE_FEATURE_TOGGLE import static org.openkilda.testing.Constants.NON_EXISTENT_SWITCH_ID import static org.openkilda.testing.Constants.WAIT_OFFSET import static org.openkilda.testing.service.floodlight.model.FloodlightConnectMode.RW @@ -153,8 +152,8 @@ class PortHistorySpec extends HealthCheckSpecification { northboundV2.getPortHistory(isl.srcSwitch.dpId, isl.srcPort, timestampBefore, timestampAfter).size() == 4 and: "Deactivate the src switch" - def switchToDisconnect = isl.srcSwitch - switchHelper.knockoutSwitch(switchToDisconnect, RW) + def switchToDisconnect = switches.all().findSpecific(isl.srcSwitch.dpId) + switchToDisconnect.knockout(RW) then: "Port history on the src switch is still available" northboundV2.getPortHistory(isl.srcSwitch.dpId, isl.srcPort, timestampBefore, timestampAfter).size() == 4 diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/PortPropertiesSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/PortPropertiesSpec.groovy index 72c8bba6ce2..ecfccf11877 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/PortPropertiesSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/PortPropertiesSpec.groovy @@ -107,9 +107,9 @@ class PortPropertiesSpec extends HealthCheckSpecification { @Tags(ISL_RECOVER_ON_FAIL) def "System doesn't discover link when port discovery property is disabled"() { given: "A deleted link" - def sw = topology.activeSwitches.first() - def relatedIsls = topology.getRelatedIsls(sw) - def islToManipulate = relatedIsls.first() + def srcSw = switches.all().first() + def relatedIsls = topology.getRelatedIsls(srcSw.switchId) + def islToManipulate = relatedIsls.find{ it.srcSwitch.dpId == srcSw.switchId } def isRtl = [islToManipulate.srcSwitch, islToManipulate.dstSwitch] .any { it.features.contains(SwitchFeature.NOVIFLOW_COPY_FIELD) } @@ -134,8 +134,8 @@ class PortPropertiesSpec extends HealthCheckSpecification { } when: "Deactivate/activate src switch" - def blockData = switchHelper.knockoutSwitch(sw, RW) - switchHelper.reviveSwitch(sw, blockData) + def blockData = srcSw.knockout(RW) + srcSw.revive(blockData) then: "Link is still not detected" Wrappers.timedLoop(discoveryInterval) { diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchActivationSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchActivationSpec.groovy index a832c26f45e..28c6eedcfb8 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchActivationSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchActivationSpec.groovy @@ -6,8 +6,8 @@ import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE_SWITCHES import static org.openkilda.functionaltests.extension.tags.Tag.SWITCH_RECOVER_ON_FAIL import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.RESTORE_SWITCH_PROPERTIES import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.SYNCHRONIZE_SWITCH +import static org.openkilda.messaging.info.event.IslChangeType.DISCOVERED import static org.openkilda.messaging.info.event.SwitchChangeType.ACTIVATED -import static org.openkilda.messaging.info.event.SwitchChangeType.DEACTIVATED import static org.openkilda.model.MeterId.MAX_SYSTEM_RULE_METER_ID import static org.openkilda.model.MeterId.MIN_FLOW_METER_ID import static org.openkilda.testing.Constants.WAIT_OFFSET @@ -21,7 +21,6 @@ import org.openkilda.functionaltests.helpers.factory.FlowFactory import org.openkilda.functionaltests.helpers.Wrappers import org.openkilda.functionaltests.model.cleanup.CleanupManager import org.openkilda.messaging.command.switches.DeleteRulesAction -import org.openkilda.messaging.info.event.IslChangeType import org.openkilda.model.MeterId import org.openkilda.model.cookie.Cookie import org.openkilda.rulemanager.FlowSpeakerData @@ -57,25 +56,23 @@ class SwitchActivationSpec extends HealthCheckSpecification { def "Missing flow rules/meters are installed on a new switch before connecting to the controller"() { given: "A switch with missing flow rules/meters and not connected to the controller" def switchPair = switchPairs.all().neighbouring().random() + def srcSw = switches.all().findSpecific(switchPair.src.dpId) def flow = flowFactory.getRandom(switchPair) - def originalMeterIds = northbound.getAllMeters(switchPair.src.dpId).meterEntries*.meterId - assert originalMeterIds.size() == 1 + switchPair.src.defaultMeters.size() - def createdCookies = northbound.getSwitchRules(switchPair.src.dpId).flowEntries.findAll { - !new Cookie(it.cookie).serviceFlag - }*.cookie - def srcSwProps = switchHelper.getCachedSwProps(switchPair.src.dpId) - def amountOfServer42IngressRules = srcSwProps.server42FlowRtt ? 1 : 0 - def amountOfServer42SharedRules = srcSwProps.server42FlowRtt - && flow.source.vlanId ? 1 : 0 - def amountOfFlowRules = 3 + amountOfServer42IngressRules + amountOfServer42SharedRules + def originalMeterIds = srcSw.metersManager.getMeters().meterId + assert originalMeterIds.size() == 1 + srcSw.collectDefaultMeters().size() + + def createdCookies = srcSw.rulesManager.getRules() + .findAll { !new Cookie(it.cookie).serviceFlag }*.cookie + def amountOfFlowRules = srcSw.collectFlowRelatedRulesAmount(flow) assert createdCookies.size() == amountOfFlowRules def nonDefaultMeterIds = originalMeterIds.findAll({it > MAX_SYSTEM_RULE_METER_ID}) - northbound.deleteMeter(switchPair.src.dpId, nonDefaultMeterIds[0]) - switchHelper.deleteSwitchRules(switchPair.src.dpId, DeleteRulesAction.IGNORE_DEFAULTS) + srcSw.metersManager.delete(nonDefaultMeterIds[0]) + srcSw.rulesManager.delete(DeleteRulesAction.IGNORE_DEFAULTS) + Wrappers.wait(WAIT_OFFSET) { - with(switchHelper.validateAndCollectFoundDiscrepancies(switchPair.src.dpId).get()) { + with(srcSw.validate()) { it.rules.missing*.cookie.containsAll(createdCookies) it.rules.excess.empty it.rules.misconfigured.empty @@ -85,46 +82,46 @@ class SwitchActivationSpec extends HealthCheckSpecification { } } - def blockData = switchHelper.knockoutSwitch(switchPair.src, RW) + def blockData = srcSw.knockout(RW) when: "Connect the switch to the controller" - switchHelper.reviveSwitch(switchPair.src, blockData) + srcSw.revive(blockData) then: "Missing flow rules/meters were synced during switch activation" - !switchHelper.synchronizeAndCollectFixedDiscrepancies(switchPair.src.dpId).isPresent() + !srcSw.validateAndCollectFoundDiscrepancies().isPresent() } @Tags([SMOKE_SWITCHES, SWITCH_RECOVER_ON_FAIL]) def "Excess transitVlanRules/meters are synced from a new switch before connecting to the controller"() { given: "A switch with excess rules/meters and not connected to the controller" - def sw = topology.getActiveSwitches().first() + def sw = switches.all().first() def producer = new KafkaProducer(producerProps) //pick a meter id which is not yet used on src switch - def excessMeterId = ((MIN_FLOW_METER_ID..100) - northbound.getAllMeters(sw.dpId) - .meterEntries*.meterId).first() - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronizeAndCollectFixedDiscrepancies(sw.dpId)}) - producer.send(new ProducerRecord(speakerTopic, sw.dpId.toString(), buildMessage( + def excessMeterId = ((MIN_FLOW_METER_ID..100) - sw.metersManager.getMeters().meterId).first() + cleanupManager.addAction(SYNCHRONIZE_SWITCH, { sw.synchronizeAndCollectFixedDiscrepancies() }) + + producer.send(new ProducerRecord(speakerTopic, sw.switchId.toString(), buildMessage( FlowSpeakerData.builder() - .switchId(sw.dpId) + .switchId(sw.switchId) .ofVersion(OfVersion.of(sw.ofVersion)) .cookie(new Cookie(1)) .table(OfTable.EGRESS) .priority(100) .instructions(Instructions.builder().build()) .build()).toJson())).get() - producer.send(new ProducerRecord(speakerTopic, sw.dpId.toString(), buildMessage( + producer.send(new ProducerRecord(speakerTopic, sw.switchId.toString(), buildMessage( FlowSpeakerData.builder() - .switchId(sw.dpId) + .switchId(sw.switchId) .ofVersion(OfVersion.of(sw.ofVersion)) .cookie(new Cookie(2)) .table(OfTable.TRANSIT) .priority(100) .instructions(Instructions.builder().build()) .build()).toJson())).get() - producer.send(new ProducerRecord(speakerTopic, sw.dpId.toString(), buildMessage([ + producer.send(new ProducerRecord(speakerTopic, sw.switchId.toString(), buildMessage([ FlowSpeakerData.builder() - .switchId(sw.dpId) + .switchId(sw.switchId) .ofVersion(OfVersion.of(sw.ofVersion)) .cookie(new Cookie(3)) .table(OfTable.INPUT) @@ -132,7 +129,7 @@ class SwitchActivationSpec extends HealthCheckSpecification { .instructions(Instructions.builder().build()) .build(), MeterSpeakerData.builder() - .switchId(sw.dpId) + .switchId(sw.switchId) .ofVersion(OfVersion.of(sw.ofVersion)) .meterId(new MeterId(excessMeterId)) .rate(300) @@ -143,7 +140,7 @@ class SwitchActivationSpec extends HealthCheckSpecification { producer.flush() Wrappers.wait(WAIT_OFFSET) { - verifyAll(switchHelper.validateAndCollectFoundDiscrepancies(sw.dpId).get()) { + verifyAll(sw.validate()) { it.rules.excess.size() == 3 it.rules.misconfigured.empty it.rules.missing.empty @@ -153,46 +150,44 @@ class SwitchActivationSpec extends HealthCheckSpecification { } } - def blockData = switchHelper.knockoutSwitch(sw, RW) + def blockData = sw.knockout(RW) when: "Connect the switch to the controller" - switchHelper.reviveSwitch(sw, blockData) + sw.revive(blockData) then: "Excess meters/rules were synced during switch activation" - !switchHelper.synchronizeAndCollectFixedDiscrepancies(sw.dpId).isPresent() + !sw.synchronizeAndCollectFixedDiscrepancies().isPresent() } @Tags([SMOKE_SWITCHES, SWITCH_RECOVER_ON_FAIL]) def "Excess vxlanRules/meters are synced from a new switch before connecting to the controller"() { given: "A switch with excess rules/meters and not connected to the controller" - def sw = topology.getActiveSwitches().find { switchHelper.isVxlanEnabled(it.dpId) } - + def sw = switches.all().withVxlanEnabled().first() def producer = new KafkaProducer(producerProps) //pick a meter id which is not yet used on src switch - def excessMeterId = ((MIN_FLOW_METER_ID..100) - northbound.getAllMeters(sw.dpId) - .meterEntries*.meterId).first() - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronizeAndCollectFixedDiscrepancies(sw.dpId)}) - producer.send(new ProducerRecord(speakerTopic, sw.dpId.toString(), buildMessage( + def excessMeterId = ((MIN_FLOW_METER_ID..100) - sw.metersManager.getMeters().meterId).first() + cleanupManager.addAction(SYNCHRONIZE_SWITCH, { sw.synchronizeAndCollectFixedDiscrepancies()} ) + producer.send(new ProducerRecord(speakerTopic, sw.switchId.toString(), buildMessage( FlowSpeakerData.builder() - .switchId(sw.dpId) + .switchId(sw.switchId) .ofVersion(OfVersion.of(sw.ofVersion)) .cookie(buildCookie(1L)) .table(OfTable.EGRESS) .priority(100) .instructions(Instructions.builder().build()) .build()).toJson())).get() - producer.send(new ProducerRecord(speakerTopic, sw.dpId.toString(), buildMessage( + producer.send(new ProducerRecord(speakerTopic, sw.switchId.toString(), buildMessage( FlowSpeakerData.builder() - .switchId(sw.dpId) + .switchId(sw.switchId) .ofVersion(OfVersion.of(sw.ofVersion)) .cookie(buildCookie(2L)) .table(OfTable.TRANSIT) .priority(100) .instructions(Instructions.builder().build()) .build()).toJson())).get() - producer.send(new ProducerRecord(speakerTopic, sw.dpId.toString(), buildMessage([ + producer.send(new ProducerRecord(speakerTopic, sw.switchId.toString(), buildMessage([ FlowSpeakerData.builder() - .switchId(sw.dpId) + .switchId(sw.switchId) .ofVersion(OfVersion.of(sw.ofVersion)) .cookie(buildCookie(3L)) .table(OfTable.INPUT) @@ -200,7 +195,7 @@ class SwitchActivationSpec extends HealthCheckSpecification { .instructions(Instructions.builder().build()) .build(), MeterSpeakerData.builder() - .switchId(sw.dpId) + .switchId(sw.switchId) .ofVersion(OfVersion.of(sw.ofVersion)) .meterId(new MeterId(excessMeterId)) .rate(300) @@ -211,7 +206,7 @@ class SwitchActivationSpec extends HealthCheckSpecification { producer.flush() Wrappers.wait(WAIT_OFFSET) { - verifyAll(switchHelper.validateAndCollectFoundDiscrepancies(sw.dpId).get()) { + verifyAll(sw.validate()) { it.rules.excess.size() == 3 it.rules.missing.empty it.rules.misconfigured.empty @@ -221,52 +216,39 @@ class SwitchActivationSpec extends HealthCheckSpecification { } } - def blockData = switchHelper.knockoutSwitch(sw, RW) + def blockData = sw.knockout(RW) when: "Connect the switch to the controller" - switchHelper.reviveSwitch(sw, blockData) + sw.revive(blockData) then: "Excess meters/rules were synced during switch activation" - !switchHelper.synchronizeAndCollectFixedDiscrepancies(sw.dpId).isPresent() + !sw.synchronizeAndCollectFixedDiscrepancies().isPresent() } @Tags([SMOKE, SMOKE_SWITCHES, LOCKKEEPER, SWITCH_RECOVER_ON_FAIL]) def "New connected switch is properly discovered with related ISLs in a reasonable time"() { setup: "Disconnect one of the switches and remove it from DB. Pretend this switch never existed" - def sw = topology.activeSwitches.first() - def isls = topology.getRelatedIsls(sw) + def sw = switches.all().first() + def isls = topology.getRelatedIsls(sw.switchId) /*in case supportedTransitEncapsulation == ["transit_vlan", "vxlan"] then after removing/adding the same switch this fields will be changed (["transit_vlan"]) vxlan encapsulation is not set by default*/ - def initSwProps = switchHelper.getCachedSwProps(sw.dpId) - initSwProps.supportedTransitEncapsulation - def blockData = switchHelper.knockoutSwitch(sw, RW) - Wrappers.wait(WAIT_OFFSET + discoveryTimeout) { - assert northbound.getSwitch(sw.dpId).state == DEACTIVATED - def allIsls = northbound.getAllLinks() - isls.each { assert islUtils.getIslInfo(allIsls, it).get().actualState == IslChangeType.FAILED } - } + def initSwProps = sw.getProps() + def blockData = sw.knockout(RW, true) + isls.each { northbound.deleteLink(islUtils.toLinkParameters(it), true) } - cleanupManager.addAction(RESTORE_SWITCH_PROPERTIES, {switchHelper.updateSwitchProperties(sw, initSwProps)}) - Wrappers.retry(2) { northbound.deleteSwitch(sw.dpId, false) } + cleanupManager.addAction(RESTORE_SWITCH_PROPERTIES, { northbound.updateSwitchProperties(sw.switchId, initSwProps) }) + Wrappers.retry(2) { sw.delete() } when: "New switch connects" - lockKeeper.reviveSwitch(sw, blockData) + sw.revive(blockData, false) then: "Switch is activated" - def switchStatus - Wrappers.wait(WAIT_OFFSET / 2) { - switchStatus = northbound.getSwitch(sw.dpId).state - assert switchStatus == ACTIVATED - } + sw.getDetails().state == ACTIVATED and: "Related ISLs are discovered" Wrappers.wait(discoveryExhaustedInterval + WAIT_OFFSET / 2 + antiflapCooldown) { - def allIsls = northbound.getAllLinks() - isls.each { - assert islUtils.getIslInfo(allIsls, it).get().actualState == IslChangeType.DISCOVERED - assert islUtils.getIslInfo(allIsls, it.reversed).get().actualState == IslChangeType.DISCOVERED - } + sw.verifyRelatedLinksState(DISCOVERED) } } } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchDeleteSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchDeleteSpec.groovy index b1d9be90142..274b973162e 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchDeleteSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchDeleteSpec.groovy @@ -1,6 +1,5 @@ package org.openkilda.functionaltests.spec.switches -import static org.junit.jupiter.api.Assumptions.assumeTrue import static org.openkilda.functionaltests.extension.tags.Tag.LOW_PRIORITY import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE import static org.openkilda.functionaltests.extension.tags.Tag.SWITCH_RECOVER_ON_FAIL @@ -19,6 +18,7 @@ import org.openkilda.functionaltests.extension.tags.Tags import org.openkilda.functionaltests.helpers.Wrappers import org.openkilda.functionaltests.helpers.factory.FlowFactory import org.openkilda.functionaltests.model.cleanup.CleanupManager +import org.openkilda.testing.model.topology.TopologyDefinition.TraffGen import org.openkilda.testing.service.traffexam.TraffExamService import org.openkilda.testing.service.traffexam.model.ArpData import org.openkilda.testing.service.traffexam.model.LldpData @@ -44,7 +44,6 @@ class SwitchDeleteSpec extends HealthCheckSpecification { @Shared FlowFactory flowFactory - def "Unable to delete a nonexistent switch"() { when: "Try to delete a nonexistent switch" northbound.deleteSwitch(NON_EXISTENT_SWITCH_ID, false) @@ -58,51 +57,51 @@ class SwitchDeleteSpec extends HealthCheckSpecification { @Tags(SMOKE) def "Unable to delete an active switch"() { given: "An active switch" - def switchId = topology.getActiveSwitches()[0].dpId + def sw = switches.all().first() when: "Try to delete the switch" - northbound.deleteSwitch(switchId, false) + sw.delete(false) then: "Get 400 BadRequest error because the switch must be deactivated first" def exc = thrown(HttpClientErrorException) - new SwitchIsInIllegalStateExpectedError("Could not delete switch '$switchId': " + - "'Switch '$switchId' is in illegal state. " + - "Switch '$switchId' is in 'Active' state.'").matches(exc) + new SwitchIsInIllegalStateExpectedError("Could not delete switch '$sw.switchId': " + + "'Switch '$sw.switchId' is in illegal state. " + + "Switch '$sw.switchId' is in 'Active' state.'").matches(exc) } @Tags(SWITCH_RECOVER_ON_FAIL) def "Unable to delete an inactive switch with active ISLs"() { given: "An inactive switch with ISLs" - def sw = topology.getActiveSwitches()[0] - def swIsls = topology.getRelatedIsls(sw) - switchHelper.knockoutSwitch(sw, RW) + def sw = switches.all().first() + def swIsls = topology.getRelatedIsls(sw.switchId) + sw.knockout(RW) when: "Try to delete the switch" - northbound.deleteSwitch(sw.dpId, false) + sw.delete(false) then: "Get 400 BadRequest error because the switch has ISLs" def exc = thrown(HttpClientErrorException) - new SwitchIsInIllegalStateExpectedError("Could not delete switch '${sw.dpId}': " + - "'Switch '${sw.dpId}' is in illegal state. " + - "Switch '${sw.dpId}' has ${swIsls.size() * 2} active links. Unplug and remove them first.'").matches(exc) + new SwitchIsInIllegalStateExpectedError("Could not delete switch '${sw.switchId}': " + + "'Switch '${sw.switchId}' is in illegal state. " + + "Switch '${sw.switchId}' has ${swIsls.size() * 2} active links. Unplug and remove them first.'").matches(exc) } @Tags(SWITCH_RECOVER_ON_FAIL) def "Unable to delete an inactive switch with inactive ISLs (ISL ports are down)"() { given: "An inactive switch with ISLs" - def sw = topology.getActiveSwitches()[0] - def swIsls = topology.getRelatedIsls(sw) + def sw = switches.all().first() + def swIsls = topology.getRelatedIsls(sw.switchId) islHelper.breakIsls(swIsls) - switchHelper.knockoutSwitch(sw, RW, false) + sw.knockout(RW, false) when: "Try to delete the switch" - northbound.deleteSwitch(sw.dpId, false) + sw.delete(false) then: "Get 400 BadRequest error because the switch has ISLs" def exc = thrown(HttpClientErrorException) - new SwitchIsInIllegalStateExpectedError("Could not delete switch '${sw.dpId}': " + - "'Switch '${sw.dpId}' is in illegal state. " + - "Switch '${sw.dpId}' has ${swIsls.size() * 2} inactive links. Remove them first.'").matches(exc) + new SwitchIsInIllegalStateExpectedError("Could not delete switch '${sw.switchId}': " + + "'Switch '${sw.switchId}' is in illegal state. " + + "Switch '${sw.switchId}' has ${swIsls.size() * 2} inactive links. Remove them first.'").matches(exc) } @Tags(SWITCH_RECOVER_ON_FAIL) @@ -112,17 +111,17 @@ class SwitchDeleteSpec extends HealthCheckSpecification { flow.create() when: "Deactivate the switch" - def swToDeactivate = topology.switches.find { it.dpId == flow.source.switchId } - switchHelper.knockoutSwitch(swToDeactivate, RW) + def swToDeactivate = switches.all().findSpecific(flow.source.switchId) + swToDeactivate.knockout(RW) and: "Try to delete the switch" - northbound.deleteSwitch(flow.source.switchId, false) + swToDeactivate.delete(false) then: "Got 400 BadRequest error because the switch has the flow assigned" def exc = thrown(HttpClientErrorException) - new SwitchIsInIllegalStateExpectedError("Could not delete switch '${swToDeactivate.dpId}': " + - "'Switch '${swToDeactivate.dpId}' is in illegal state. " + - "Switch '${swToDeactivate.dpId}' has 1 assigned flows: [${flow.flowId}].'").matches(exc) + new SwitchIsInIllegalStateExpectedError("Could not delete switch '${swToDeactivate.switchId}': " + + "'Switch '${swToDeactivate.switchId}' is in illegal state. " + + "Switch '${swToDeactivate.switchId}' has 1 assigned flows: [${flow.flowId}].'").matches(exc) where: flowType | flow @@ -133,39 +132,38 @@ class SwitchDeleteSpec extends HealthCheckSpecification { @Tags(SWITCH_RECOVER_ON_FAIL) def "Able to delete an inactive switch without any ISLs"() { given: "An inactive switch without any ISLs" - def sw = topology.getActiveSwitches()[0] + def sw = switches.all().first() //need to restore supportedTransitEncapsulation field after deleting sw - def initSwProps = switchHelper.getCachedSwProps(sw.dpId) - def swIsls = topology.getRelatedIsls(sw) + def initSwProps = sw.getProps() + def swIsls = topology.getRelatedIsls(sw.switchId) // port down on all active ISLs on switch islHelper.breakIsls(swIsls) // delete all ISLs on switch swIsls.each { northbound.deleteLink(islUtils.toLinkParameters(it)) } // deactivate switch - switchHelper.knockoutSwitch(sw, RW) + sw.knockoutWithoutLinksCheckWhenRecover(RW) when: "Try to delete the switch" - cleanupManager.addAction(RESTORE_SWITCH_PROPERTIES, {switchHelper.updateSwitchProperties(sw, initSwProps)}) - def response = northbound.deleteSwitch(sw.dpId, false) + cleanupManager.addAction(RESTORE_SWITCH_PROPERTIES, { northbound.updateSwitchProperties(sw.switchId, initSwProps) }) + def response = sw.delete(false) then: "The switch is actually deleted" response.deleted - Wrappers.wait(WAIT_OFFSET) { assert !northbound.allSwitches.any { it.switchId == sw.dpId } } + Wrappers.wait(WAIT_OFFSET) { assert !northbound.allSwitches.find { it.switchId == sw.switchId } } } @Tags(SWITCH_RECOVER_ON_FAIL) def "Able to delete an inactive switch with connected devices"() { given: "An inactive switch without any ISLs but with connected devices" - assumeTrue(topology.activeTraffGens.size() > 0, "Require at least 1 switch with connected traffgen") - def tg = topology.activeTraffGens[0] - def sw = tg.switchConnected + def sw = switches.all().withTraffGens().first() + TraffGen tg = topology.getTraffGen(sw.switchId) //need to restore supportedTransitEncapsulation field after deleting sw - def initSwProps = switchHelper.getCachedSwProps(sw.dpId) - def swIsls = topology.getRelatedIsls(sw) + def initSwProps = sw.getCashedProps() + def swIsls = topology.getRelatedIsls(sw.switchId) // enable connected devices on switch - switchHelper.updateSwitchProperties(sw, initSwProps.jacksonCopy().tap { + sw.updateProperties(initSwProps.jacksonCopy().tap { it.switchLldp = true it.switchArp = true }) @@ -173,15 +171,15 @@ class SwitchDeleteSpec extends HealthCheckSpecification { // send LLDP and ARP packets def lldpData = LldpData.buildRandom() def arpData = ArpData.buildRandom() - cleanupManager.addAction(OTHER, {database.removeConnectedDevices(sw.dpId)}) - new ConnectedDevice(traffExamProvider.get(), topology.getTraffGen(sw.dpId), [777]).withCloseable { + cleanupManager.addAction(OTHER, {database.removeConnectedDevices(sw.switchId)}) + new ConnectedDevice(traffExamProvider.get(), tg, [777]).withCloseable { it.sendLldp(lldpData) it.sendArp(arpData) } // LLDP and ARP connected devices are recognized and saved" Wrappers.wait(WAIT_OFFSET) { - verifyAll(northboundV2.getConnectedDevices(sw.dpId).ports) { + verifyAll(sw.getConnectedDevices().ports) { it.size() == 1 it[0].portNumber == tg.switchPort it[0].lldp.size() == 1 @@ -194,31 +192,30 @@ class SwitchDeleteSpec extends HealthCheckSpecification { // delete all ISLs on switch swIsls.each { northbound.deleteLink(islUtils.toLinkParameters(it)) } // deactivate switch - switchHelper.knockoutSwitch(sw, RW) + sw.knockoutWithoutLinksCheckWhenRecover(RW) when: "Try to delete the switch" - cleanupManager.addAction(RESTORE_SWITCH_PROPERTIES, {switchHelper.updateSwitchProperties(sw, initSwProps)}) - def response = northbound.deleteSwitch(sw.dpId, false) + def response = sw.delete(false) then: "The switch is actually deleted" response.deleted - Wrappers.wait(WAIT_OFFSET) { assert !northbound.allSwitches.any { it.switchId == sw.dpId } } + Wrappers.wait(WAIT_OFFSET) { assert !northbound.allSwitches.find { it.switchId == sw.switchId } } } def "Able to delete an active switch with active ISLs if using force delete"() { given: "An active switch with active ISLs" - def sw = topology.getActiveSwitches()[0] + def sw = switches.all().first() //need to restore supportedTransitEncapsulation field after deleting sw - def initSwProps = switchHelper.getCachedSwProps(sw.dpId) - def swIsls = topology.getRelatedIsls(sw) + def initSwProps = sw.getProps() + def swIsls = topology.getRelatedIsls(sw.switchId) when: "Try to force delete the switch" - def response = northbound.deleteSwitch(sw.dpId, true) + def response = sw.delete(true) then: "The switch is actually deleted" response.deleted Wrappers.wait(WAIT_OFFSET) { - assert !northbound.allSwitches.any { it.switchId == sw.dpId } + assert !northbound.allSwitches.find{ it.switchId == sw.switchId } def links = northbound.getAllLinks() swIsls.each { @@ -229,18 +226,18 @@ class SwitchDeleteSpec extends HealthCheckSpecification { cleanup: "Restore the switch, ISLs and reset costs" // restore switch - def blockData = lockKeeper.knockoutSwitch(sw, RW) + def blockData = sw.knockout(RW) Wrappers.wait(WAIT_OFFSET) { flHelper.getFlsByMode(RW).each { fl -> - assert fl.floodlightService.getSwitches().every { it.switchId != sw.dpId } + assert !fl.floodlightService.getSwitches().find { it.switchId == sw.switchId } } } - switchHelper.reviveSwitch(sw, blockData) + sw.revive(blockData) // restore ISLs swIsls.each { antiflap.portDown(it.srcSwitch.dpId, it.srcPort) } TimeUnit.SECONDS.sleep(antiflapMin) islHelper.restoreIsls(swIsls) - initSwProps && switchHelper.updateSwitchProperties(sw, initSwProps) + initSwProps && northbound.updateSwitchProperties(sw.switchId, initSwProps) database.resetCosts(topology.isls) } } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchFailuresSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchFailuresSpec.groovy index 27e2b07eb5d..3f0fb08fd2d 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchFailuresSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchFailuresSpec.groovy @@ -4,10 +4,11 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue import static org.openkilda.functionaltests.extension.tags.Tag.LOCKKEEPER import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE_SWITCHES +import static org.openkilda.functionaltests.extension.tags.Tag.SWITCH_RECOVER_ON_FAIL +import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.OTHER import static org.openkilda.testing.Constants.PATH_INSTALLATION_TIME import static org.openkilda.testing.Constants.WAIT_OFFSET import static org.openkilda.testing.service.floodlight.model.FloodlightConnectMode.RW -import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.REVIVE_SWITCH import org.openkilda.functionaltests.HealthCheckSpecification import org.openkilda.functionaltests.error.flow.FlowNotValidatedExpectedError @@ -15,12 +16,11 @@ 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.FlowActionType +import org.openkilda.functionaltests.helpers.model.SwitchExtended import org.openkilda.functionaltests.model.cleanup.CleanupManager import org.openkilda.functionaltests.model.stats.Direction import org.openkilda.messaging.info.event.IslChangeType -import org.openkilda.messaging.info.event.SwitchChangeType import org.openkilda.messaging.payload.flow.FlowState -import org.openkilda.testing.model.topology.TopologyDefinition.Switch import org.openkilda.testing.service.lockkeeper.model.TrafficControlData import org.springframework.beans.factory.annotation.Autowired @@ -29,12 +29,12 @@ import spock.lang.Ignore import spock.lang.Narrative import spock.lang.Shared - @Narrative(""" This spec verifies different situations when Kilda switches suddenly disconnect from the controller. Note: For now it is only runnable on virtual env due to no ability to disconnect hardware switches """) +@Tags(SWITCH_RECOVER_ON_FAIL) class SwitchFailuresSpec extends HealthCheckSpecification { @Autowired @Shared @@ -49,19 +49,23 @@ class SwitchFailuresSpec extends HealthCheckSpecification { def isl = topology.getIslsForActiveSwitches().find { it.aswitch && it.dstSwitch } assumeTrue(isl.asBoolean(), "No a-switch ISL found for the test") def flow = flowFactory.getRandom(isl.srcSwitch, isl.dstSwitch) + def srcSw = switches.all().findSpecific(flow.source.switchId) + def dstSw = switches.all().findSpecific(flow.destination.switchId) when: "Two neighbouring switches of the flow go down simultaneously" - def srcBlockData = switchHelper.knockoutSwitch(isl.srcSwitch, RW) + def srcBlockData = srcSw.knockoutWithoutLinksCheckWhenRecover(RW) def timeSwitchesBroke = System.currentTimeMillis() - def dstBlockData = switchHelper.knockoutSwitch(isl.dstSwitch, RW) + def dstBlockData = dstSw.knockoutWithoutLinksCheckWhenRecover(RW) def untilIslShouldFail = { timeSwitchesBroke + discoveryTimeout * 1000 - System.currentTimeMillis() } and: "ISL between those switches looses connection" + cleanupManager.addAction(OTHER, {northbound.synchronizeSwitch(isl.srcSwitch.dpId, true)}) + cleanupManager.addAction(OTHER, {northbound.synchronizeSwitch(isl.dstSwitch.dpId, true)}) aSwitchFlows.removeFlows([isl.aswitch]) and: "Switches go back up" - lockKeeper.reviveSwitch(isl.srcSwitch, srcBlockData) - lockKeeper.reviveSwitch(isl.dstSwitch, dstBlockData) + lockKeeper.reviveSwitch(srcSw.sw, srcBlockData) + lockKeeper.reviveSwitch(dstSw.sw, dstBlockData) then: "ISL still remains up right before discovery timeout should end" sleep(untilIslShouldFail() - 2500) @@ -89,9 +93,11 @@ class SwitchFailuresSpec extends HealthCheckSpecification { given: "A flow" def swPair = switchPairs.all().nonNeighbouring().withAtLeastNPaths(2).random() def flow = flowFactory.getRandom(swPair) + def srcSw = switches.all().findSpecific(flow.source.switchId) + def dstSw = switches.all().findSpecific(flow.destination.switchId) when: "Current path breaks and reroute starts" - switchHelper.shapeSwitchesTraffic([swPair.dst], new TrafficControlData(3000)) + dstSw.shapeTraffic(new TrafficControlData(3000)) def islToBreak = flow.retrieveAllEntityPaths().getInvolvedIsls().first() antiflap.portDown(islToBreak.srcSwitch.dpId, islToBreak.srcPort) @@ -100,7 +106,7 @@ class SwitchFailuresSpec extends HealthCheckSpecification { def reroute = flow.retrieveFlowHistory().getEntriesByType(FlowActionType.REROUTE).first() assert reroute.payload.last().action == "Started validation of installed non ingress rules" } - lockKeeper.reviveSwitch(swPair.src, lockKeeper.knockoutSwitch(swPair.src, RW)) + srcSw.revive(lockKeeper.knockoutSwitch(swPair.src, RW)) then: "Flow reroute is successful" Wrappers.wait(PATH_INSTALLATION_TIME * 2) { //double timeout since rerouted is slowed by delay @@ -109,7 +115,7 @@ class SwitchFailuresSpec extends HealthCheckSpecification { } and: "Blinking switch has no rule anomalies" - !switchHelper.validateAndCollectFoundDiscrepancies(swPair.src.dpId).isPresent() + !srcSw.validateAndCollectFoundDiscrepancies().isPresent() and: "Flow validation is OK" flow.validateAndCollectDiscrepancies().isEmpty() @@ -117,10 +123,10 @@ class SwitchFailuresSpec extends HealthCheckSpecification { def "System can handle situation when switch reconnects while flow is being created"() { when: "Start creating a flow between switches and lose connection to src before rules are set" - def (Switch srcSwitch, Switch dstSwitch) = topology.activeSwitches + def (SwitchExtended srcSwitch, SwitchExtended dstSwitch) = switches.all().switches def flow = flowFactory.getBuilder(srcSwitch, dstSwitch).build().sendCreateRequest() sleep(50) - def blockData = switchHelper.knockoutSwitch(srcSwitch, RW) + def blockData = srcSwitch.knockout(RW) then: "Flow eventually goes DOWN" Wrappers.wait(WAIT_OFFSET) { @@ -136,7 +142,7 @@ class SwitchFailuresSpec extends HealthCheckSpecification { } and: "Dst switch validation shows no missing rules" - !switchHelper.validateAndCollectFoundDiscrepancies(dstSwitch.dpId).isPresent() + !dstSwitch.validateAndCollectFoundDiscrepancies().isPresent() when: "Try to validate flow" flow.validate() @@ -145,7 +151,7 @@ class SwitchFailuresSpec extends HealthCheckSpecification { def e = thrown(HttpClientErrorException) new FlowNotValidatedExpectedError(~/Could not validate flow: Flow $flow.flowId is in DOWN state/).matches(e) when: "Switch returns back UP" - switchHelper.reviveSwitch(srcSwitch, blockData) + srcSwitch.revive(blockData) then: "Flow is still down, because ISLs had not enough time to fail, so no ISLs are discovered and no reroute happen" flow.retrieveFlowStatus().status == FlowState.DOWN diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchesFlowsV2Spec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchesFlowsV2Spec.groovy index 97b129f0fbf..52bd0a4534d 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchesFlowsV2Spec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchesFlowsV2Spec.groovy @@ -100,9 +100,10 @@ class SwitchesFlowsV2Spec extends HealthCheckSpecification { }.flatten() def sharedEpPort = yFlow.sharedEndpoint.portNumber + def sharedSw = switches.all().findSpecific(switchTriplet.shared.dpId) when: "Get all flows on the switch ports used by subflow under test" - def response = switchHelper.getFlowsV2(switchTriplet.shared, usedPortsList) + def response = sharedSw.getFlowsV2(usedPortsList as List) then: "Each port in response has information about subflow" response.flowsByPort.every { @@ -117,13 +118,14 @@ class SwitchesFlowsV2Spec extends HealthCheckSpecification { def "System allows to get flows on particular ports on switch"() { given: "Y-Flow subflow which ends on switch that is not in the path of another sub-flow or regular flow" and: "List of the ports that subflow uses on switch, received from flow path" - def switchWithOnlyOneSubFlow = switchPair.dst == switchTriplet.ep1 ? switchTriplet.ep2 : switchTriplet.ep1 + def switchId = switchPair.dst == switchTriplet.ep1 ? switchTriplet.ep2.dpId : switchTriplet.ep1.dpId + def switchWithOnlyOneSubFlow = switches.all().findSpecific(switchId) def usedPortsList = yFlow.retrieveAllEntityPaths().subFlowPaths.find { it.flowId == yFlowSubFlow2Id } - .collect { it.path.forward.retrieveNodes().findAll { it.switchId == switchWithOnlyOneSubFlow.dpId }.portNo }.flatten() + .collect { it.path.forward.retrieveNodes().findAll { it.switchId == switchId }.portNo }.flatten() when: "Get all flows on the switch ports used by subflow under test" - def response = switchHelper.getFlowsV2(switchWithOnlyOneSubFlow, usedPortsList) + def response = switchWithOnlyOneSubFlow.getFlowsV2(usedPortsList as List) then: "Each port in response has information about the subflow" response.flowsByPort.every { @@ -134,22 +136,24 @@ class SwitchesFlowsV2Spec extends HealthCheckSpecification { def "System allows to get a flow that #switchRole switch"() { given: "Flow that #switchRole switch" when: "Get all flows going through the switch" - def flows = switchHelper.getFlowsV2(switchUnderTest, []) + def switchUnderTest = switches.all().findSpecific(switchId) + def flows = switchUnderTest.getFlowsV2() then: "The created flows (including both y-flow subflows) are in the response list from the switch" flows.flowsByPort.collectMany { it.value.flowId }.unique().sort() == [flowId, yFlowSubFlow1Id, yFlowSubFlow2Id].sort() where: - switchRole | switchUnderTest - "flows through" | switchFlowGoesThrough - "starts from" | switchPair.src - "ends on" | switchPair.dst + switchRole | switchId + "flows through" | switchFlowGoesThrough.dpId + "starts from" | switchPair.src.dpId + "ends on" | switchPair.dst.dpId } def "System allows to get a flow which protected path that goes through switch"() { given: "Flow which protected path goes through switch" when: "Get all flows going through the switch" - def flows = switchHelper.getFlowsV2(switchProtectedPathGoesThrough, []) + def switchUnderTest = switches.all().findSpecific(switchProtectedPathGoesThrough.dpId) + def flows = switchUnderTest.getFlowsV2() then: "The flow's protected path is in the response list from the switch" flows.flowsByPort.collectMany { it.value.flowId }.unique() == [flowId] @@ -158,33 +162,31 @@ class SwitchesFlowsV2Spec extends HealthCheckSpecification { @Tags([LOW_PRIORITY]) def "Mirror sink endpoint port is not listed in list of the ports used"() { given: "Switch with flow on it and a free port" - def switchUnderTest = switchPair.getDst() - def usedPortsList = switchHelper.getUsedPorts(switchUnderTest.dpId) - def freePort = (new ArrayList<>(1..1000).asList() - - usedPortsList - - topology.getBusyPortsForSwitch(switchUnderTest)).first() + def switchUnderTest = switches.all().findSpecific(switchPair.dst.dpId) + def usedPortsList = switchUnderTest.getUsedPorts() + def freePort = (new ArrayList<>(1..1000).asList() - usedPortsList - switchUnderTest.islPorts).first() when: "Create mirror point on switch with sink pointing to free port" flow.createMirrorPoint( - switchUnderTest.dpId, freePort, randomVlan(), + switchUnderTest.switchId, freePort, randomVlan(), FlowPathDirection.REVERSE ) then: "Mirror sink endpoint port is not listed in the ports list" - switchHelper.getFlowsV2(switchUnderTest, [freePort]).getFlowsByPort().isEmpty() + switchUnderTest.getFlowsV2([freePort]).getFlowsByPort().isEmpty() } @Tags([LOW_PRIORITY]) def "Empty list is returned if none of requested ports is busy with any flow"() { given: "Switch with flow on it and ports this flow uses" - def switchUnderTest = switchPair.dst - def usedPortsList = switchHelper.getUsedPorts(switchUnderTest.dpId) + def switchUnderTest = switches.all().findSpecific(switchPair.dst.dpId) + def usedPortsList = switchUnderTest.getUsedPorts() when: "Request flows on several unused ports" def unusedPortsList = new ArrayList<>(1..1000).asList() - usedPortsList then: "Response is empty, but without errors" - switchHelper.getFlowsV2(switchUnderTest, unusedPortsList.subList(0, 3)).getFlowsByPort().isEmpty() + switchUnderTest.getFlowsV2(unusedPortsList.subList(0, 3)).getFlowsByPort().isEmpty() } @Tags([LOW_PRIORITY]) @@ -193,9 +195,10 @@ class SwitchesFlowsV2Spec extends HealthCheckSpecification { def swT = switchTriplets.all(false, true) .withSpecificSingleSwitch(switchProtectedPathGoesThrough) def yFlow = yFlowFactory.getRandom(swT, false) + def switchUnderTest = switches.all().findSpecific(switchProtectedPathGoesThrough.dpId) when: "Request flows on switch" - def flows = switchHelper.getFlowsV2(switchProtectedPathGoesThrough, []) + def flows = switchUnderTest.getFlowsV2() then: "Ports used by subflows on the switch are in response" flows.flowsByPort.collectMany { it.value }*.flowId