From 241d75ec8fe2ae41ce5a5804ca78690338db8672 Mon Sep 17 00:00:00 2001 From: Yuliia Miroshnychenko Date: Fri, 17 Jan 2025 13:01:12 +0100 Subject: [PATCH] [TEST]: Improvement: Switches: New interaction approach: Phase3 --- .../helpers/SwitchHelper.groovy | 167 ----- .../helpers/model/SwitchExtended.groovy | 37 +- .../helpers/model/Switches.groovy | 16 + .../switches/SwitchMaintenanceSpec.groovy | 37 +- .../spec/switches/SwitchPortConfigSpec.groovy | 6 +- .../spec/switches/SwitchPropertiesSpec.groovy | 80 +-- .../spec/switches/SwitchSyncSpec.groovy | 247 ++++---- .../spec/switches/SwitchValidationSpec.groovy | 546 ++++++++--------- .../switches/SwitchValidationV2Spec.groovy | 575 +++++++++--------- 9 files changed, 773 insertions(+), 938 deletions(-) diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/SwitchHelper.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/SwitchHelper.groovy index a7a842455e..bc5b4f2d6b 100644 --- a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/SwitchHelper.groovy +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/SwitchHelper.groovy @@ -28,7 +28,6 @@ import org.openkilda.northbound.dto.v2.switches.LagPortRequest import org.openkilda.northbound.dto.v2.switches.LagPortResponse import org.openkilda.northbound.dto.v2.switches.MeterInfoDtoV2 import org.openkilda.northbound.dto.v2.switches.PortPropertiesDto -import org.openkilda.northbound.dto.v2.switches.SwitchDtoV2 import org.openkilda.northbound.dto.v2.switches.SwitchFlowsPerPortResponse import org.openkilda.northbound.dto.v2.switches.SwitchLocationDtoV2 import org.openkilda.northbound.dto.v2.switches.SwitchPatchDto @@ -56,12 +55,9 @@ import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Scope import org.springframework.stereotype.Component -import java.math.RoundingMode - import static groovyx.gpars.GParsPool.withPool import static org.hamcrest.MatcherAssert.assertThat import static org.hamcrest.Matchers.hasItem -import static org.hamcrest.Matchers.notNullValue import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.DELETE_LAG_LOGICAL_PORT import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.DELETE_MIRROR import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.OTHER @@ -165,16 +161,6 @@ class SwitchHelper { "${sw.nbFormat().hardware} ${sw.nbFormat().software}" } - @Memoized - static String getHwSwString(SwitchDto sw) { - "${sw.hardware} ${sw.software}" - } - - @Memoized - static String getHwSwString(SwitchDtoV2 sw) { - "${sw.hardware} ${sw.software}" - } - static List getTraffGens(Switch sw) { topology.get().activeTraffGens.findAll { it.switchConnected.dpId == sw.dpId } } @@ -414,39 +400,6 @@ class SwitchHelper { return northboundV2.get().getSwitchFlows(switchId, []).flowsByPort.keySet().asList() } - /** - * This method calculates expected burst for different types of switches. The common burst equals to - * `rate * burstCoefficient`. There are couple exceptions though:
- * Noviflow: Does not use our common burst coefficient and overrides it with its own coefficient (see static - * variables at the top of the class).
- * Centec: Follows our burst coefficient policy, except for restrictions for the minimum and maximum burst. - * In cases when calculated burst is higher or lower of the Centec max/min - the max/min burst value will be used - * instead. - * - * @param sw switchId where burst is being calculated. Needed to get the switch manufacturer and apply special - * calculations if required - * @param rate meter rate which is used to calculate burst - * @return the expected burst value for given switch and rate - */ - def getExpectedBurst(SwitchId sw, long rate) { - def descr = getDescription(sw).toLowerCase() - def hardware = northbound.get().getSwitch(sw).hardware - if (descr.contains("noviflow") || hardware =~ "WB5164") { - return (rate * NOVIFLOW_BURST_COEFFICIENT - 1).setScale(0, RoundingMode.CEILING) - } else if (descr.contains("centec")) { - def burst = (rate * burstCoefficient).toBigDecimal().setScale(0, RoundingMode.FLOOR) - if (burst <= CENTEC_MIN_BURST) { - return CENTEC_MIN_BURST - } else if (burst > CENTEC_MIN_BURST && burst <= CENTEC_MAX_BURST) { - return burst - } else { - return CENTEC_MAX_BURST - } - } else { - return (rate * burstCoefficient).round(0) - } - } - /** * Verifies that specified meter sections in the validation response are empty. * NOTE: will filter out default meters for 'proper' section, so that switch without flow meters, but only with @@ -469,24 +422,6 @@ class SwitchHelper { assertions.verify() } - static void verifyMeterSectionsAreEmpty(SwitchValidationV2ExtendedResult switchValidateInfo, - List sections = ["missing", "misconfigured", "proper", "excess"]) { - def assertions = new SoftAssertions() - if (switchValidateInfo.meters) { - sections.each { section -> - if (section == "proper") { - assertions.checkSucceeds { - assert switchValidateInfo.meters.proper.findAll { !it.defaultMeter }.empty - } - } else { - assertions.checkSucceeds { assert switchValidateInfo.meters."$section".empty } - } - } - } - assertions.verify() - } - - /** * Verifies that specified rule sections in the validation response are empty. * NOTE: will filter out default rules, except default flow rules(multiTable flow) @@ -511,54 +446,6 @@ class SwitchHelper { assertions.verify() } - static void verifyRuleSectionsAreEmpty(SwitchValidationV2ExtendedResult switchValidateInfo, - List sections = ["missing", "proper", "excess", "misconfigured"]) { - def assertions = new SoftAssertions() - sections.each { String section -> - if (section == "proper") { - assertions.checkSucceeds { - assert switchValidateInfo.rules.proper.findAll { - def cookie = new Cookie(it.cookie) - !cookie.serviceFlag && cookie.type != CookieType.SHARED_OF_FLOW - }.empty - } - } else { - assertions.checkSucceeds { assert switchValidateInfo.rules."$section".empty } - } - } - assertions.verify() - } - - /** - * Verifies that specified hexRule sections in the validation response are empty. - * NOTE: will filter out default rules, except default flow rules(multiTable flow) - * Default flow rules for the system looks like as a simple default rule. - * Based on that you have to use extra filter to detect these rules in - * missingHex/excessHex/misconfiguredHex sections. - */ - static void verifyHexRuleSectionsAreEmpty(SwitchValidationExtendedResult switchValidateInfo, - List sections = ["properHex", "excessHex", "missingHex", - "misconfiguredHex"]) { - def assertions = new SoftAssertions() - sections.each { String section -> - if (section == "properHex") { - def defaultCookies = switchValidateInfo.rules.proper.findAll { - def cookie = new Cookie(it) - cookie.serviceFlag || cookie.type == CookieType.SHARED_OF_FLOW - } - - def defaultHexCookies = [] - defaultCookies.each { defaultHexCookies.add(Long.toHexString(it)) } - assertions.checkSucceeds { - assert switchValidateInfo.rules.properHex.findAll { !(it in defaultHexCookies) }.empty - } - } else { - assertions.checkSucceeds { assert switchValidateInfo.rules."$section".empty } - } - } - assertions.verify() - } - static boolean isDefaultMeter(MeterInfoDto dto) { return MeterId.isMeterIdOfDefaultRule(dto.getMeterId()) } @@ -567,25 +454,6 @@ class SwitchHelper { return MeterId.isMeterIdOfDefaultRule(dto.getMeterId()) } - /** - * Verifies that actual and expected burst size are the same. - */ - static void verifyBurstSizeIsCorrect(Switch sw, Long expected, Long actual) { - if (sw.isWb5164()) { - assert Math.abs(expected - actual) <= expected * 0.01 - } else { - assert Math.abs(expected - actual) <= 1 - } - } - - static void verifyRateSizeIsCorrect(Switch sw, Long expected, Long actual) { - if (sw.isWb5164()) { - assert Math.abs(expected - actual) <= expected * 0.01 - } else { - assert Math.abs(expected - actual) <= 1 - } - } - static SwitchProperties getDummyServer42Props() { return new SwitchProperties(true, 33, "00:00:00:00:00:00", 1, null) } @@ -676,25 +544,6 @@ class SwitchHelper { reviveSwitch(sw, flResourceAddress, false) } - static void verifySectionInSwitchValidationInfo(SwitchValidationV2ExtendedResult switchValidateInfo, - List sections = ["groups", "meters", "logical_ports", "rules"]) { - sections.each { String section -> - assertThat(switchValidateInfo."$section", notNullValue()) - } - - } - - static void verifySectionsAsExpectedFields(SwitchValidationV2ExtendedResult switchValidateInfo, - List sections = ["groups", "meters", "logical_ports", "rules"]) { - boolean result = true; - sections.each { String section -> - if (!switchValidateInfo."$section".asExpected) { - result = false - } - } - assert result == switchValidateInfo.asExpected - } - static SwitchSyncResult synchronize(SwitchId switchId, boolean removeExcess=true) { return northbound.get().synchronizeSwitch(switchId, removeExcess) } @@ -755,15 +604,6 @@ class SwitchHelper { return Optional.ofNullable(validateAndCollectFoundDiscrepancies([switchToValidate]).get(switchToValidate)) } - static void synchronizeAndValidateRulesInstallation(Switch srcSwitch, Switch dstSwitch) { - synchronizeAndCollectFixedDiscrepancies([srcSwitch.dpId, dstSwitch.dpId]) - [srcSwitch, dstSwitch].each { sw -> - Wrappers.wait(RULES_INSTALLATION_TIME) { - validate(sw.dpId).verifyRuleSectionsAreEmpty() - } - } - } - static SwitchValidationV2ExtendedResult validate(SwitchId switchId, String include = null, String exclude = null) { return northboundV2.get().validateSwitch(switchId, include, exclude) } @@ -966,13 +806,6 @@ class SwitchHelper { return northboundV2.get().partialSwitchUpdate(switchId, updateDto) } - static boolean isServer42Supported(SwitchId switchId) { - def swProps = northbound.get().getSwitchProperties(switchId) - def featureToggles = northbound.get().getFeatureToggles() - def isServer42 = swProps.server42FlowRtt && featureToggles.server42FlowRtt - return isServer42 - } - static int randomVlan() { return randomVlan([]) } 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 858cfe4c68..124c14ddec 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 @@ -3,10 +3,12 @@ package org.openkilda.functionaltests.helpers.model import static groovyx.gpars.GParsPool.withPool import static org.hamcrest.MatcherAssert.assertThat import static org.hamcrest.Matchers.hasItem +import static org.hamcrest.Matchers.notNullValue import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.OTHER import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.RESET_SWITCH_MAINTENANCE import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.RESTORE_SWITCH_PROPERTIES import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.REVIVE_SWITCH +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.IslChangeType.FAILED import static org.openkilda.messaging.info.event.SwitchChangeType.ACTIVATED @@ -400,7 +402,8 @@ class SwitchExtended { * reinstalled according to config */ SwitchPropertiesDto updateProperties(SwitchPropertiesDto switchProperties) { - cleanupManager.addAction(OTHER, { northbound.updateSwitchProperties(sw.dpId, getCashedProps()) }) + def initialProps = getCashedProps() + cleanupManager.addAction(OTHER, { northbound.updateSwitchProperties(sw.dpId, initialProps) }) def response = northbound.updateSwitchProperties(sw.dpId, switchProperties) Wrappers.wait(RULES_INSTALLATION_TIME) { def actualHexCookie = [] @@ -512,6 +515,10 @@ class SwitchExtended { return northbound.deleteSwitch(sw.dpId, force) } + SwitchPropertiesDto getPropsV1() { + northbound.getSwitchProperties(sw.dpId) + } + boolean isS42FlowRttEnabled() { def swProps = northbound.getSwitchProperties(sw.dpId) def featureToggles = northbound.getFeatureToggles() @@ -600,6 +607,11 @@ class SwitchExtended { revive(flResourceAddress, false) } + void updateBurstSizeAndRate(Long meterId, Long newBurstSize, Long newRate) { + cleanupManager.addAction(SYNCHRONIZE_SWITCH, { synchronize() }) + lockKeeper.updateBurstSizeAndRate(sw, meterId, newBurstSize, newRate) + } + void verifyRelatedLinksState(IslChangeType expectedState) { def relatedLinks = northbound.getAllLinks().findAll { switchId in [it.source.switchId, it.destination.switchId] @@ -662,6 +674,11 @@ class SwitchExtended { database.getSwitch(sw.dpId).features } + void setFeaturesInDb(Set features) { + cleanupManager.addAction(OTHER, { database.setSwitchFeatures(sw.dpId, getDbFeatures())}) + database.setSwitchFeatures(sw.dpId, features) + } + boolean isVxlanFeatureEnabled() { !getDbFeatures().intersect([NOVIFLOW_PUSH_POP_VXLAN, KILDA_OVS_PUSH_POP_MATCH_VXLAN]).isEmpty() } @@ -856,4 +873,22 @@ class SwitchExtended { } assertions.verify() } + + static void verifySectionInSwitchValidationInfo(SwitchValidationV2ExtendedResult switchValidateInfo, + List sections = ["groups", "meters", "logical_ports", "rules"]) { + sections.each { String section -> + assertThat(switchValidateInfo."$section", notNullValue()) + } + } + + static void verifySectionsAsExpectedFields(SwitchValidationV2ExtendedResult switchValidateInfo, + List sections = ["groups", "meters", "logical_ports", "rules"]) { + boolean result = true; + sections.each { String section -> + if (!switchValidateInfo."$section".asExpected) { + result = false + } + } + assert result == switchValidateInfo.asExpected + } } 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 2c418e882b..19707500e5 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 @@ -11,6 +11,7 @@ import static org.springframework.beans.factory.config.ConfigurableBeanFactory.S import org.openkilda.functionaltests.helpers.factory.SwitchFactory import org.openkilda.functionaltests.model.switches.Manufacturer +import org.openkilda.model.SwitchFeature import org.openkilda.model.SwitchId import org.openkilda.northbound.dto.v1.switches.SwitchDto import org.openkilda.testing.model.topology.TopologyDefinition @@ -71,12 +72,21 @@ class Switches { switches.unique { it.hwSwString() } } + List notOF12Version() { + switches.findAll { it.ofVersion != "OF_12" } + } + Switches withS42Support(){ def swsProps = northboundV2.getAllSwitchProperties().switchProperties switches = switches.findAll { sw -> isS42Supported(swsProps.find { it.switchId == sw.switchId}) } return this } + Switches withoutLagSupport() { + switches = switches.findAll { !it.dbFeatures.contains(SwitchFeature.LAG) } + return this + } + SwitchExtended random() { assumeFalse(switches.isEmpty(), "No suiting switch found") switches.shuffled().first() @@ -93,6 +103,12 @@ class Switches { sw } + SwitchExtended findWithVxlanFeatureEnabled() { + def sw = switches.find { it.isVxlanFeatureEnabled() } + assumeTrue(sw as Boolean, "No suiting switch(vxlan-enabled) found") + sw + } + @Memoized private List collectSwitches() { List switchesDetails = northbound.allSwitches diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchMaintenanceSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchMaintenanceSpec.groovy index 9ccf2854f5..0c6878181e 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchMaintenanceSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchMaintenanceSpec.groovy @@ -34,40 +34,40 @@ class SwitchMaintenanceSpec extends HealthCheckSpecification { @Tags(SMOKE) def "Maintenance mode can be set/unset for a particular switch"() { given: "An active switch" - def sw = topology.activeSwitches.first() + def sw = switches.all().first() when: "Set maintenance mode for the switch" - def setMaintenance = switchHelper.setSwitchMaintenance(sw.dpId, true, false) + def setMaintenance = sw.setMaintenance(true, false) then: "Maintenance flag for the switch is really set" setMaintenance.underMaintenance - northbound.getSwitch(sw.dpId).underMaintenance + sw.getDetails().underMaintenance and: "Maintenance flag for all ISLs going through the switch is set as well" - northbound.getAllLinks().findAll { sw.dpId in [it.source, it.destination]*.switchId }.each { + northbound.getAllLinks().findAll { sw.switchId in [it.source, it.destination]*.switchId }.each { assert it.underMaintenance } and: "Cost for ISLs is not changed" - topology.islsForActiveSwitches.findAll { sw.dpId in [it.srcSwitch, it.dstSwitch]*.dpId }.each { + topology.islsForActiveSwitches.findAll { sw.switchId in [it.srcSwitch, it.dstSwitch]*.dpId }.each { assert database.getIslCost(it) == DEFAULT_COST assert database.getIslCost(it.reversed) == DEFAULT_COST } when: "Unset maintenance mode from the switch" - def unsetMaintenance = northbound.setSwitchMaintenance(sw.dpId, false, false) + def unsetMaintenance = sw.setMaintenance(false, false) then: "Maintenance flag for the switch is really unset" !unsetMaintenance.underMaintenance - !northbound.getSwitch(sw.dpId).underMaintenance + !sw.getDetails().underMaintenance and: "Maintenance flag for all ISLs going through the switch is unset as well" - northbound.getAllLinks().findAll { sw.dpId in [it.source, it.destination]*.switchId }.each { + northbound.getAllLinks().findAll { sw.switchId in [it.source, it.destination]*.switchId }.each { assert !it.underMaintenance } and: "Cost for ISLs is not changed" - topology.islsForActiveSwitches.findAll { sw.dpId in [it.srcSwitch, it.dstSwitch]*.dpId }.each { + topology.islsForActiveSwitches.findAll { sw.switchId in [it.srcSwitch, it.dstSwitch]*.dpId }.each { assert database.getIslCost(it) == DEFAULT_COST assert database.getIslCost(it.reversed) == DEFAULT_COST } @@ -92,6 +92,7 @@ class SwitchMaintenanceSpec extends HealthCheckSpecification { .each { islHelper.makePathIslsMorePreferable(flow1PathIsls, it) } and: "Create a Flow going through these switches" + def switchUnderTest = switches.all().findSpecific(swId) def flow1 = flowFactory.getRandom(switchPair) def flow1Path = flow1.retrieveAllEntityPaths() def flow1IntermediateSwIsl = flow1PathIsls.findAll { it in intermediateSwIsls || it.reversed in intermediateSwIsls } @@ -114,7 +115,7 @@ class SwitchMaintenanceSpec extends HealthCheckSpecification { assert flow2IntermediateSwIsl.intersect(flow1IntermediateSwIsl).isEmpty() when: "Set maintenance mode without flows evacuation flag for some intermediate switch involved in flow paths" - switchHelper.setSwitchMaintenance(swId, true, false) + switchUnderTest.setMaintenance(true, false) then: "Flows are not evacuated (rerouted) and have the same paths" timedLoop(3) { @@ -123,7 +124,7 @@ class SwitchMaintenanceSpec extends HealthCheckSpecification { } when: "Set maintenance mode again with flows evacuation flag for the same switch" - northbound.setSwitchMaintenance(swId, true, true) + switchUnderTest.setMaintenance(true, true) then: "Flows are evacuated (rerouted)" Wrappers.wait(PATH_INSTALLATION_TIME + WAIT_OFFSET) { @@ -176,7 +177,8 @@ class SwitchMaintenanceSpec extends HealthCheckSpecification { assert flowPath.getInvolvedSwitches().contains(intermediateSwId) when: "Set maintenance mode without flows evacuation flag for some intermediate switch involved in flow paths" - switchHelper.setSwitchMaintenance(intermediateSwId, true, false) + def switchUnderTest = switches.all().findSpecific(intermediateSwId) + switchUnderTest.setMaintenance(true, false) then: "Both Flow and Y-Flow are not evacuated (rerouted) and have the same paths" timedLoop(3) { @@ -185,7 +187,7 @@ class SwitchMaintenanceSpec extends HealthCheckSpecification { } when: "Set maintenance mode again with flows evacuation flag for the same switch" - northbound.setSwitchMaintenance(intermediateSwId, true, true) + switchUnderTest.setMaintenance(true, true) then: "Both Flow and Y-Flow are evacuated (rerouted)" Wrappers.wait(PATH_INSTALLATION_TIME + WAIT_OFFSET) { @@ -203,8 +205,9 @@ class SwitchMaintenanceSpec extends HealthCheckSpecification { @Tags(ISL_RECOVER_ON_FAIL) def "Link discovered by a switch under maintenance is marked as maintained"() { - given: "An active link" - def isl = topology.islsForActiveSwitches.first() + given: "An active switch with an active link" + def switchUnderTest = switches.all().random() + def isl = topology.getRelatedIsls(switchUnderTest.switchId).first() and: "Bring port down on the switch to fail the link" islHelper.breakIsl(isl) @@ -215,10 +218,10 @@ class SwitchMaintenanceSpec extends HealthCheckSpecification { !islUtils.getIslInfo(isl.reversed) when: "Set maintenance mode for the switch" - switchHelper.setSwitchMaintenance(isl.srcSwitch.dpId, true, false) + switchUnderTest.setMaintenance(true, false) and: "Bring port up to discover the deleted link" - antiflap.portUp(isl.srcSwitch.dpId, isl.srcPort) + switchUnderTest.getPort(isl.srcPort).up() then: "The link is discovered and marked as maintained" Wrappers.wait(discoveryInterval + WAIT_OFFSET) { diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchPortConfigSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchPortConfigSpec.groovy index 7349ae75aa..836c263ec2 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchPortConfigSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchPortConfigSpec.groovy @@ -68,14 +68,12 @@ class SwitchPortConfigSpec extends HealthCheckSpecification { // It is impossible to understand whether ISL-free port is UP/DOWN on OF_12 switches. // Such switches always have 'config: []'. //also, ban WB5164 due to #2636 - sw << getTopology().getActiveSwitches().findAll { it.ofVersion != "OF_12" && !it.wb5164 } - .unique { it.nbFormat().with { [it.hardware, it.software] } } + sw << switches.all().uniqueByHw().findAll { it.ofVersion != "OF_12" && !it.wb5164 } } List getUniqueIsls() { - def uniqueSwitches = getTopology().getActiveSwitches() - .unique { it.nbFormat().with { [it.hardware, it.software] } }*.dpId + def uniqueSwitches = switches.all().uniqueByHw().switchId def isls = topology.islsForActiveSwitches.collect { [it, it.reversed] }.flatten() return isls.unique { it.srcSwitch.dpId }.findAll { it.srcSwitch.dpId in uniqueSwitches } } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchPropertiesSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchPropertiesSpec.groovy index a86ebae89e..2b47314205 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchPropertiesSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchPropertiesSpec.groovy @@ -1,24 +1,29 @@ package org.openkilda.functionaltests.spec.switches -import groovy.transform.AutoClone +import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE_SWITCHES +import static org.openkilda.functionaltests.extension.tags.Tag.TOPOLOGY_DEPENDENT +import static org.openkilda.functionaltests.helpers.model.FlowEncapsulationType.TRANSIT_VLAN +import static org.openkilda.functionaltests.helpers.model.FlowEncapsulationType.VXLAN +import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.OTHER +import static org.openkilda.functionaltests.model.cleanup.CleanupAfter.CLASS +import static org.openkilda.testing.Constants.NON_EXISTENT_SWITCH_ID + import org.openkilda.functionaltests.HealthCheckSpecification import org.openkilda.functionaltests.error.switchproperties.SwitchPropertiesNotFoundExpectedError import org.openkilda.functionaltests.error.switchproperties.SwitchPropertiesNotUpdatedExpectedError import org.openkilda.functionaltests.extension.tags.Tags -import org.openkilda.model.FlowEncapsulationType -import org.openkilda.model.SwitchFeature +import org.openkilda.functionaltests.helpers.model.SwitchExtended +import org.openkilda.functionaltests.model.cleanup.CleanupManager import org.openkilda.northbound.dto.v1.switches.SwitchPropertiesDto + +import groovy.transform.AutoClone +import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.client.HttpClientErrorException import spock.lang.Narrative +import spock.lang.Shared import java.util.regex.Pattern -import static org.junit.jupiter.api.Assumptions.assumeTrue -import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE_SWITCHES -import static org.openkilda.functionaltests.extension.tags.Tag.TOPOLOGY_DEPENDENT -import static org.openkilda.model.SwitchFeature.KILDA_OVS_PUSH_POP_MATCH_VXLAN -import static org.openkilda.testing.Constants.NON_EXISTENT_SWITCH_ID - @Narrative("""Switch properties are created automatically once switch is connected to the controller and deleted once switch is deleted. Properties can be read/updated via API '/api/v1/switches/:switch-id/properties'. @@ -26,43 +31,46 @@ Main purpose of that is to understand which feature is supported by a switch(enc class SwitchPropertiesSpec extends HealthCheckSpecification { + @Shared + SwitchExtended switchUnderTest + + @Autowired @Shared + CleanupManager cleanupManager + + def setupSpec() { + switchUnderTest = switches.all().first() + def initialProps = switchUnderTest.getCashedProps() + cleanupManager.addAction(OTHER, { northbound.updateSwitchProperties(switchUnderTest.switchId, initialProps) }, CLASS) + } + @Tags([TOPOLOGY_DEPENDENT, SMOKE_SWITCHES]) def "Able to manipulate with switch properties"() { given: "A switch that supports VXLAN" - def sw = topology.activeSwitches.find { it.features.contains(SwitchFeature.NOVIFLOW_PUSH_POP_VXLAN) - || it.features.contains(KILDA_OVS_PUSH_POP_MATCH_VXLAN) } - assumeTrue(sw as boolean, "Wasn't able to find vxlan-enabled switch") - def initSwitchProperties = switchHelper.getCachedSwProps(sw.dpId) + def sw = switches.all().findWithVxlanFeatureEnabled() + def initSwitchProperties = sw.getProps() assert !initSwitchProperties.supportedTransitEncapsulation.empty //make sure that two endpoints have the same info - with(northboundV2.getAllSwitchProperties().switchProperties.find { it.switchId == sw.dpId }){ - supportedTransitEncapsulation.sort() == initSwitchProperties.supportedTransitEncapsulation.sort() - } + assert sw.getPropsV1().supportedTransitEncapsulation.sort() == initSwitchProperties.supportedTransitEncapsulation.sort() when: "Update switch properties" - SwitchPropertiesDto switchProperties = new SwitchPropertiesDto() def newTransitEncapsulation = (initSwitchProperties.supportedTransitEncapsulation.size() == 1) ? - [FlowEncapsulationType.TRANSIT_VLAN.toString().toLowerCase(), - FlowEncapsulationType.VXLAN.toString().toLowerCase()].sort() : - [FlowEncapsulationType.VXLAN.toString().toLowerCase()] - switchProperties.tap { + [TRANSIT_VLAN.toString(), VXLAN.toString().toLowerCase()].sort() : [VXLAN.toString().toLowerCase()] + + SwitchPropertiesDto switchProperties = initSwitchProperties.jacksonCopy().tap { supportedTransitEncapsulation = newTransitEncapsulation multiTable = true } - def updateSwPropertiesResponse = switchHelper.updateSwitchProperties(sw, switchProperties) + def updateSwPropertiesResponse = sw.updateProperties(switchProperties) then: "Correct response is returned" updateSwPropertiesResponse.supportedTransitEncapsulation.sort() == newTransitEncapsulation and: "Switch properties is really updated" - with(northbound.getSwitchProperties(sw.dpId)) { - it.supportedTransitEncapsulation.sort() == newTransitEncapsulation - } + sw.getPropsV1().supportedTransitEncapsulation.sort() == newTransitEncapsulation and: "Changes are shown in getAllSwitchProperties response" - with(northboundV2.getAllSwitchProperties().switchProperties.find { it.switchId == sw.dpId }){ - supportedTransitEncapsulation.sort() == newTransitEncapsulation - } + sw.getProps().supportedTransitEncapsulation.sort() == newTransitEncapsulation + } def "Informative error is returned when trying to get/update switch properties with non-existing id"() { @@ -75,7 +83,7 @@ class SwitchPropertiesSpec extends HealthCheckSpecification { when: "Try to update switch properties info for non-existing switch" def switchProperties = new SwitchPropertiesDto() switchProperties.tap { - supportedTransitEncapsulation = [FlowEncapsulationType.VXLAN.toString()] + supportedTransitEncapsulation = [VXLAN.toString()] multiTable = true } northbound.updateSwitchProperties(NON_EXISTENT_SWITCH_ID, switchProperties) @@ -85,13 +93,10 @@ class SwitchPropertiesSpec extends HealthCheckSpecification { new SwitchPropertiesNotFoundExpectedError(NON_EXISTENT_SWITCH_ID, ~/Failed to update switch properties./).matches(exc) } def "Informative error is returned when trying to update switch properties with incorrect information(#invalidType)"() { - given: "A switch" - def sw = topology.activeSwitches.first() - when: "Try to update switch properties info for non-existing switch" def switchProperties = new SwitchPropertiesDto() switchProperties.supportedTransitEncapsulation = supportedTransitEncapsulation - northbound.updateSwitchProperties(sw.dpId, switchProperties) + northbound.updateSwitchProperties(switchUnderTest.switchId, switchProperties) then: "Human readable error is returned" def exc = thrown(HttpClientErrorException) @@ -108,23 +113,20 @@ class SwitchPropertiesSpec extends HealthCheckSpecification { } def "Error is returned when trying to #data.desc"() { - given: "A switch" - def sw = topology.activeSwitches.first() - when: "Try to update switch properties with incorrect server 42 properties combination" def switchProperties = new SwitchPropertiesDto() - switchProperties.supportedTransitEncapsulation = [FlowEncapsulationType.TRANSIT_VLAN.toString()] + switchProperties.supportedTransitEncapsulation = [TRANSIT_VLAN.toString()] switchProperties.multiTable = true switchProperties.server42FlowRtt = data.server42FlowRtt switchProperties.server42Port = data.server42Port switchProperties.server42Vlan = data.server42Vlan switchProperties.server42MacAddress = data.server42MacAddress switchProperties.server42IslRtt = data.server42IslRtt - northbound.updateSwitchProperties(sw.dpId, switchProperties) + northbound.updateSwitchProperties(switchUnderTest.switchId, switchProperties) then: "Human readable error is returned" def exc = thrown(HttpClientErrorException) - new SwitchPropertiesNotUpdatedExpectedError(String.format(data.error, sw.dpId), + new SwitchPropertiesNotUpdatedExpectedError(String.format(data.error, switchUnderTest.switchId), data.description ?: ~/Failed to update switch properties./).matches(exc) where: diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchSyncSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchSyncSpec.groovy index d6ba5bb4bc..4fc35e40dd 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchSyncSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchSyncSpec.groovy @@ -6,7 +6,6 @@ import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE_SWITCHES import static org.openkilda.functionaltests.extension.tags.Tag.VIRTUAL import static org.openkilda.functionaltests.helpers.SwitchHelper.isDefaultMeter import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.SYNCHRONIZE_SWITCH -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.model.cookie.Cookie.VERIFICATION_BROADCAST_RULE_COOKIE import static org.openkilda.rulemanager.OfTable.EGRESS @@ -21,6 +20,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.helpers.model.FlowExtended +import org.openkilda.functionaltests.helpers.model.SwitchExtended import org.openkilda.functionaltests.model.cleanup.CleanupManager import org.openkilda.messaging.command.switches.DeleteRulesAction import org.openkilda.functionaltests.helpers.model.FlowEncapsulationType @@ -32,7 +32,6 @@ import org.openkilda.rulemanager.Instructions import org.openkilda.rulemanager.MeterFlag import org.openkilda.rulemanager.MeterSpeakerData import org.openkilda.rulemanager.OfVersion -import org.openkilda.testing.model.topology.TopologyDefinition.Switch import com.google.common.collect.Sets import org.apache.kafka.clients.producer.KafkaProducer @@ -65,10 +64,10 @@ class SwitchSyncSpec extends HealthCheckSpecification { def "Able to synchronize switch without any rule and meter discrepancies (removeExcess=#removeExcess)"() { given: "An active switch" - def sw = topology.activeSwitches.first() + def sw = switches.all().first() when: "Synchronize the switch that doesn't have any rule and meter discrepancies" - def syncResult = switchHelper.synchronize(sw.dpId, removeExcess) + def syncResult = sw.synchronize(removeExcess) then: "Operation is successful" syncResult.rules.proper.findAll { !new Cookie(it).serviceFlag }.size() == 0 @@ -97,69 +96,66 @@ class SwitchSyncSpec extends HealthCheckSpecification { def flow = flowFactory.getRandom(switchPair) and: "Drop all rules an meters from related switches (both default and non-default)" - List involvedSwitches = flow.retrieveAllEntityPaths().getInvolvedIsls() - .collect { [it.srcSwitch, it.dstSwitch] }.flatten().unique() as List - def involvedSwitchIds = involvedSwitches*.getDpId() + List involvedSwitches = flow.retrieveAllEntityPaths().getInvolvedSwitches() + .collect{ switches.all().findSpecific(it) } + def cookiesMap = involvedSwitches.collectEntries { sw -> - def defaultCookies = sw.defaultCookies - [sw.dpId, northbound.getSwitchRules(sw.dpId).flowEntries.findAll { - !(it.cookie in defaultCookies) - }*.cookie] + def defaultCookies = sw.collectDefaultCookies() + [sw.switchId, sw.rulesManager.getRules().findAll { !(it.cookie in defaultCookies) }*.cookie] } Map> metersMap = involvedSwitches.collectEntries { sw -> - [sw.dpId, northbound.getAllMeters(sw.dpId).meterEntries*.meterId] + [sw.switchId, sw.metersManager.getMeters()*.meterId] } involvedSwitches.each { sw -> - switchHelper.deleteSwitchRules(sw.dpId, DeleteRulesAction.DROP_ALL) - northbound.getAllMeters(sw.dpId).meterEntries.each { northbound.deleteMeter(sw.dpId, it.meterId) } + sw.rulesManager.delete(DeleteRulesAction.DROP_ALL) + metersMap[sw.switchId].each { sw.metersManager.delete(it) } } + Wrappers.wait(RULES_DELETION_TIME) { - def validationResultsMap = switchHelper.validateAndCollectFoundDiscrepancies(involvedSwitchIds) - involvedSwitches[1..-2].each { - assert validationResultsMap[it.dpId].rules.missing.size() == 2 + it.defaultCookies.size() - assert validationResultsMap[it.dpId].meters.missing.meterId.sort() == it.defaultMeters.sort() - } - switchPair.toList().each { - def swProps = switchHelper.getCachedSwProps(it.dpId) - def amountFlowRules = 2 //INGRESS_REVERSE, INGRESS_FORWARD - def amountMultiTableSharedRules = 1 - def amountS42Rules = swProps.server42FlowRtt ? 3 : 0 - /** - * s42Rules - * SERVER_42_FLOW_RTT_INGRESS_REVERSE, SERVER_42_FLOW_RTT_INPUT, SERVER_42_FLOW_RTT_TURNING_COOKIE, - * SERVER42_QINQ_OUTER_VLAN (for QinQ flows, should not present in this test) - * some rule is in defaultCookies (SERVER_42_FLOW_RTT_OUTPUT_VLAN_COOKIE) - */ - - def amountRules = amountFlowRules + amountS42Rules + amountMultiTableSharedRules + it.defaultCookies.size() - assert validationResultsMap[it.dpId].rules.missing.size() == amountRules - assert validationResultsMap[it.dpId].meters.missing.size() == 1 + it.defaultMeters.size() + involvedSwitches.each { sw -> + def validationResponse = sw.validate() + if (sw.switchId in switchPair.toList().dpId) { + /** + * s42Rules: SERVER_42_FLOW_RTT_INGRESS_REVERSE, SERVER_42_FLOW_RTT_INPUT, SERVER_42_FLOW_RTT_TURNING_COOKIE, + * SERVER42_QINQ_OUTER_VLAN (for QinQ flows, should not present in this test) + * some rule is in defaultCookies (SERVER_42_FLOW_RTT_OUTPUT_VLAN_COOKIE) + */ + def defaultS42Rules = sw.getProps().server42FlowRtt ? 1 : 0 // DROP_ALL rules were called + def amountRules = sw.collectFlowRelatedRulesAmount(flow) + sw.collectDefaultCookies().size() + defaultS42Rules + + assert validationResponse.rules.missing.size() == amountRules + assert validationResponse.meters.missing.size() == 1 + sw.collectDefaultMeters().size() + + } else { + assert validationResponse.rules.missing.size() == 2 + sw.collectDefaultCookies().size() + assert validationResponse.meters.missing.meterId.sort() == sw.collectDefaultMeters().sort() + } } } when: "Try to synchronize all switches" - def syncResultsMap = switchHelper.synchronizeAndCollectFixedDiscrepancies(involvedSwitchIds) - then: "System detects missing rules and meters (both default and flow-related), then installs them" - involvedSwitches.each { - assert syncResultsMap[it.dpId].rules.proper.size() == 0 - assert syncResultsMap[it.dpId].rules.excess.size() == 0 - assert syncResultsMap[it.dpId].rules.missing.containsAll(cookiesMap[it.dpId]) - assert syncResultsMap[it.dpId].rules.removed.size() == 0 - assert syncResultsMap[it.dpId].rules.installed.containsAll(cookiesMap[it.dpId]) - } - switchPair.toList().each { - assert syncResultsMap[it.dpId].meters.proper.size() == 0 - assert syncResultsMap[it.dpId].meters.excess.size() == 0 - assert syncResultsMap[it.dpId].meters.missing*.meterId == metersMap[it.dpId].sort() - assert syncResultsMap[it.dpId].meters.removed.size() == 0 - assert syncResultsMap[it.dpId].meters.installed*.meterId == metersMap[it.dpId].sort() + involvedSwitches.each { sw -> + def syncResponse = sw.synchronize() + assert syncResponse.rules.proper.size() == 0 + assert syncResponse.rules.excess.size() == 0 + assert syncResponse.rules.missing.containsAll(cookiesMap[sw.switchId]) + assert syncResponse.rules.removed.size() == 0 + assert syncResponse.rules.installed.containsAll(cookiesMap[sw.switchId]) + + if (sw.switchId in switchPair.toList().dpId) { + assert syncResponse.meters.proper.size() == 0 + assert syncResponse.meters.excess.size() == 0 + assert syncResponse.meters.missing*.meterId == metersMap[sw.switchId].sort() + assert syncResponse.meters.removed.size() == 0 + assert syncResponse.meters.installed*.meterId == metersMap[sw.switchId].sort() + } } and: "Switch validation doesn't complain about any missing rules and meters" Wrappers.wait(RULES_INSTALLATION_TIME) { - switchHelper.validateAndCollectFoundDiscrepancies(involvedSwitchIds).isEmpty() + involvedSwitches.each { sw -> !sw.validateAndCollectFoundDiscrepancies().isPresent() } } } @@ -167,19 +163,18 @@ class SwitchSyncSpec extends HealthCheckSpecification { given: "Flow with intermediate switches" def switchPair = switchPairs.all().nonNeighbouring().random() def flow = flowFactory.getRandom(switchPair) - def switchId = getSwitch(flow) - def ofVersion = topology.getActiveSwitches().find{it.getDpId() == switchId}.getOfVersion() - assert !switchHelper.synchronizeAndCollectFixedDiscrepancies(switchId).isPresent() + def sw = switches.all().findSpecific(getSwitchId(flow)) + def ofVersion = sw.getOfVersion() + assert !sw.synchronizeAndCollectFixedDiscrepancies().isPresent() and: "Force install excess rules and meters on switch" - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronize(switchId)}) + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {sw.synchronize()}) def producer = new KafkaProducer(producerProps) def excessRuleCookie = 1234567890L - def excessMeterId = ((MIN_FLOW_METER_ID..100) - - northbound.getAllMeters(switchId).meterEntries*.meterId).first() as Long - producer.send(new ProducerRecord(speakerTopic, switchId.toString(), buildMessage([ + def excessMeterId = ((MIN_FLOW_METER_ID..100) - sw.metersManager.getMeters().meterId).first() as Long + producer.send(new ProducerRecord(speakerTopic, sw.switchId.toString(), buildMessage([ FlowSpeakerData.builder() - .switchId(switchId) + .switchId(sw.switchId) .ofVersion(OfVersion.of(ofVersion)) .cookie(new Cookie(excessRuleCookie)) .table(table) @@ -187,19 +182,18 @@ class SwitchSyncSpec extends HealthCheckSpecification { .instructions(Instructions.builder().build()) .build(), MeterSpeakerData.builder() - .switchId(switchId) + .switchId(sw.switchId) .ofVersion(OfVersion.of(ofVersion)) .meterId(new MeterId(excessMeterId)) .rate(flow.maximumBandwidth) .burst(flow.maximumBandwidth) .flags(Sets.newHashSet(MeterFlag.KBPS, MeterFlag.BURST, MeterFlag.STATS)) .build()]).toJson())).get() - Wrappers.wait(RULES_INSTALLATION_TIME) { - !switchHelper.validate(switchId).getRules().getExcess().isEmpty() - } + + Wrappers.wait(RULES_INSTALLATION_TIME) { !sw.validate().getRules().getExcess().isEmpty() } when: "Try to synchronize the switch" - def syncResult = switchHelper.synchronize(switchId) + def syncResult = sw.synchronize() then: "System detects excess rules and meters" verifyAll (syncResult) { @@ -211,16 +205,17 @@ class SwitchSyncSpec extends HealthCheckSpecification { and: "Switch validation doesn't complain about excess rules and meters" Wrappers.wait(RULES_INSTALLATION_TIME) { - switchHelper.validate(switchId).isAsExpected() + sw.validate().isAsExpected() } where: - switchKind | getSwitch | table - "source" | { FlowExtended flowExtended -> flowExtended.source.switchId } | INPUT - "destination" | { FlowExtended flowExtended -> flowExtended.destination.switchId } | EGRESS - "transit" | { FlowExtended flowExtended -> return flowExtended.retrieveAllEntityPaths() - .flowPath.path.forward.getTransitInvolvedSwitches().shuffled().first() - } | TRANSIT + switchKind | getSwitchId | table + "source" | { FlowExtended flowExtended -> flowExtended.source.switchId } | INPUT + "destination" | { FlowExtended flowExtended -> flowExtended.destination.switchId } | EGRESS + "transit" | { FlowExtended flowExtended -> + return flowExtended.retrieveAllEntityPaths() + .flowPath.path.forward.getTransitInvolvedSwitches().shuffled().first() + } | TRANSIT } def "Able to synchronize switch with 'vxlan' rule(install missing rules and meters)"() { @@ -234,70 +229,64 @@ class SwitchSyncSpec extends HealthCheckSpecification { and: "Reproduce situation when switches have missing rules and meters" def flowInfoFromDb = flow.retrieveDetailsFromDB() - List involvedSwitches = flow.retrieveAllEntityPaths().getInvolvedIsls() - .collect { [it.srcSwitch, it.dstSwitch] }.flatten().unique() as List - def involvedSwitchIds = involvedSwitches*.getDpId() - def transitSwitchIds = involvedSwitches[1..-2]*.dpId + List involvedSwitches = flow.retrieveAllEntityPaths().getInvolvedSwitches() + .collect { switches.all().findSpecific(it) } + def transitSwitches = involvedSwitches[1..-2] + def terminalSwitches = involvedSwitches.findAll { it.switchId in switchPair.toList().dpId } + def cookiesMap = involvedSwitches.collectEntries { sw -> - def defaultCookies = sw.defaultCookies - [sw.dpId, northbound.getSwitchRules(sw.dpId).flowEntries.findAll { + def defaultCookies = sw.collectDefaultCookies() + [sw.switchId, sw.rulesManager.getRules().findAll { !(it.cookie in defaultCookies) && !new Cookie(it.cookie).serviceFlag }*.cookie] } def metersMap = involvedSwitches.collectEntries { sw -> - [sw.dpId, northbound.getAllMeters(sw.dpId).meterEntries.findAll { - it.meterId > MAX_SYSTEM_RULE_METER_ID - }*.meterId] + [sw.switchId, sw.metersManager.getCreatedMeterIds()] } - involvedSwitches.each { switchHelper.deleteSwitchRules(it.dpId, DeleteRulesAction.IGNORE_DEFAULTS) } - [switchPair.src, switchPair.dst].each { northbound.deleteMeter(it.dpId, metersMap[it.dpId][0]) } + involvedSwitches.each { sw -> sw.rulesManager.delete(DeleteRulesAction.IGNORE_DEFAULTS) } + terminalSwitches.each { sw -> sw.metersManager.delete(metersMap[sw.switchId][0]) } + Wrappers.wait(RULES_DELETION_TIME) { - def validationResultsMap = switchHelper.validateAndCollectFoundDiscrepancies(involvedSwitchIds) - involvedSwitches.each { - def swProps = switchHelper.getCachedSwProps(it.dpId) - def switchIdInSrcOrDst = (it.dpId in [switchPair.src.dpId, switchPair.dst.dpId]) - def defaultAmountOfFlowRules = 2 // ingress + egress - def amountOfServer42Rules = 0 - if(swProps.server42FlowRtt && it.dpId in [switchPair.src.dpId, switchPair.dst.dpId]) { - amountOfServer42Rules +=1 - it.dpId == switchPair.src.dpId && flow.source.vlanId && ++amountOfServer42Rules - it.dpId == switchPair.dst.dpId && flow.destination.vlanId && ++amountOfServer42Rules + involvedSwitches.each { sw -> + def validationResponse = sw.validate() + def rulesCount = sw.collectFlowRelatedRulesAmount(flow) + assert validationResponse.rules.missing.size() == rulesCount + if(sw in terminalSwitches) { + assert validationResponse.meters.missing.size() == 1 } - def rulesCount = defaultAmountOfFlowRules + amountOfServer42Rules + - (switchIdInSrcOrDst ? 1 : 0) - assert validationResultsMap[it.dpId].rules.missing.size() == rulesCount } - switchPair.toList().each { assert validationResultsMap[it.dpId].meters.missing.size() == 1 } + } } when: "Try to synchronize all switches" - def syncResultsMap = switchHelper.synchronizeAndCollectFixedDiscrepancies(involvedSwitchIds) - then: "System detects missing rules and meters, then installs them" - involvedSwitches.each { - assert syncResultsMap[it.dpId].rules.proper.findAll { !new Cookie(it).serviceFlag }.size() == 0 - assert syncResultsMap[it.dpId].rules.excess.size() == 0 - assert syncResultsMap[it.dpId].rules.missing.containsAll(cookiesMap[it.dpId]) - assert syncResultsMap[it.dpId].rules.removed.size() == 0 - assert syncResultsMap[it.dpId].rules.installed.containsAll(cookiesMap[it.dpId]) - } - switchPair.toList().each { - assert syncResultsMap[it.dpId].meters.proper.findAll { !it.defaultMeter }.size() == 0 - assert syncResultsMap[it.dpId].meters.excess.size() == 0 - assert syncResultsMap[it.dpId].meters.missing*.meterId == metersMap[it.dpId] - assert syncResultsMap[it.dpId].meters.removed.size() == 0 - assert syncResultsMap[it.dpId].meters.installed*.meterId == metersMap[it.dpId] + involvedSwitches.each { sw -> + def syncResponse = sw.synchronize() + assert syncResponse.rules.proper.findAll { !new Cookie(it).serviceFlag }.size() == 0 + assert syncResponse.rules.excess.size() == 0 + assert syncResponse.rules.missing.containsAll(cookiesMap[sw.switchId]) + assert syncResponse.rules.removed.size() == 0 + assert syncResponse.rules.installed.containsAll(cookiesMap[sw.switchId]) + if(sw in terminalSwitches) { + assert syncResponse.meters.proper.findAll { !it.defaultMeter }.size() == 0 + assert syncResponse.meters.excess.size() == 0 + assert syncResponse.meters.missing*.meterId == metersMap[sw.switchId] + assert syncResponse.meters.removed.size() == 0 + assert syncResponse.meters.installed*.meterId == metersMap[sw.switchId] + } } and: "Switch validation doesn't complain about missing rules and meters" Wrappers.wait(RULES_INSTALLATION_TIME) { - switchHelper.validateAndCollectFoundDiscrepancies(involvedSwitchIds).isEmpty() + involvedSwitches.each { sw -> + assert !sw.validateAndCollectFoundDiscrepancies().isPresent() + } } and: "Rules are synced correctly" // ingressRule should contain "pushVxlan" // egressRule should contain "tunnel-id" - with(northbound.getSwitchRules(switchPair.src.dpId).flowEntries) { rules -> + with(terminalSwitches.find { it.switchId == switchPair.src.dpId}.rulesManager.getRules()) { rules -> assert rules.find { it.cookie == flowInfoFromDb.forwardPath.cookie.value }.instructions.applyActions.pushVxlan @@ -306,7 +295,7 @@ class SwitchSyncSpec extends HealthCheckSpecification { }.match.tunnelId } - with(northbound.getSwitchRules(switchPair.dst.dpId).flowEntries) { rules -> + with(terminalSwitches.find { it.switchId == switchPair.dst.dpId}.rulesManager.getRules()) { rules -> assert rules.find { it.cookie == flowInfoFromDb.forwardPath.cookie.value }.match.tunnelId @@ -315,8 +304,8 @@ class SwitchSyncSpec extends HealthCheckSpecification { }.instructions.applyActions.pushVxlan } - transitSwitchIds.each { swId -> - with(northbound.getSwitchRules(swId).flowEntries) { rules -> + transitSwitches.each { sw -> + with(sw.rulesManager.getRules()) { rules -> assert rules.find { it.cookie == flowInfoFromDb.forwardPath.cookie.value }.match.tunnelId @@ -330,17 +319,15 @@ class SwitchSyncSpec extends HealthCheckSpecification { @Tags([VIRTUAL, LOW_PRIORITY]) def "Able to synchronize misconfigured default meter"() { given: "An active switch with valid default rules and one misconfigured default meter" - def sw = topology.activeSwitches.first() + def sw = switches.all().random() def broadcastCookieMeterId = MeterId.createMeterIdForDefaultRule(VERIFICATION_BROADCAST_RULE_COOKIE).getValue() - def meterToManipulate = northbound.getAllMeters(sw.dpId).meterEntries - .find{ it.meterId == broadcastCookieMeterId } + def meterToManipulate = sw.metersManager.getMeters().find{ it.meterId == broadcastCookieMeterId } def newBurstSize = meterToManipulate.burstSize + 100 def newRate = meterToManipulate.rate + 100 - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronize(sw.dpId)}) - lockKeeper.updateBurstSizeAndRate(sw, meterToManipulate.meterId, newBurstSize, newRate) + sw.updateBurstSizeAndRate(meterToManipulate.meterId, newBurstSize, newRate) Wrappers.wait(RULES_DELETION_TIME) { - def validateInfo = switchHelper.validate(sw.dpId) + def validateInfo = sw.validate() assert validateInfo.rules.missing.empty assert validateInfo.rules.misconfigured.empty assert validateInfo.meters.misconfigured.size() == 1 @@ -351,11 +338,11 @@ class SwitchSyncSpec extends HealthCheckSpecification { } when: "Synchronize switch" - switchHelper.synchronize(sw.dpId, false) + sw.synchronize(false) then: "The misconfigured meter is fixed and moved to the 'proper' section" Wrappers.wait(RULES_INSTALLATION_TIME) { - with(switchHelper.validate(sw.dpId)) { + with(sw.validate()) { it.meters.misconfigured.empty it.meters.proper.find { it.meterId == broadcastCookieMeterId }.burstSize == meterToManipulate.burstSize it.meters.proper.find { it.meterId == broadcastCookieMeterId }.rate == meterToManipulate.rate @@ -366,16 +353,14 @@ class SwitchSyncSpec extends HealthCheckSpecification { @Tags([VIRTUAL, SMOKE]) def "Able to synchronize misconfigured flow meter"() { given: "An active switch with flow on it" - def sw = topology.activeSwitches.first() + def sw = switches.all().random() def flow = flowFactory.getRandom(sw, sw) when: "Update flow's meter" - def flowMeterIdToManipulate = northbound.getAllMeters(sw.dpId).meterEntries.find { - it.meterId >= MIN_FLOW_METER_ID - } + def flowMeterIdToManipulate = sw.metersManager.getMeters().find { it.meterId >= MIN_FLOW_METER_ID } def newBurstSize = flowMeterIdToManipulate.burstSize + 100 def newRate = flowMeterIdToManipulate.rate + 100 - lockKeeper.updateBurstSizeAndRate(sw, flowMeterIdToManipulate.meterId, newBurstSize, newRate) + sw.updateBurstSizeAndRate(flowMeterIdToManipulate.meterId, newBurstSize, newRate) then: "Flow is not valid" def flowDiscrepancies = flow.validateAndCollectDiscrepancies().values() @@ -389,7 +374,7 @@ class SwitchSyncSpec extends HealthCheckSpecification { and: "Validate switch endpoint shows the updated meter as misconfigured" Wrappers.wait(RULES_DELETION_TIME) { - def validateInfo = switchHelper.validate(sw.dpId) + def validateInfo = sw.validate() assert validateInfo.meters.misconfigured.size() == 1 assert validateInfo.meters.misconfigured[0].discrepancies.burstSize == newBurstSize assert validateInfo.meters.misconfigured[0].expected.burstSize == flowMeterIdToManipulate.burstSize @@ -398,11 +383,11 @@ class SwitchSyncSpec extends HealthCheckSpecification { } when: "Synchronize switch" - switchHelper.synchronize(sw.dpId, false) + sw.synchronize(false) then: "The misconfigured meter was fixed and moved to the 'proper' section" Wrappers.wait(RULES_INSTALLATION_TIME) { - with(switchHelper.validate(sw.dpId)) { + with(sw.validate()) { it.meters.misconfigured.empty it.meters.proper.find { it.meterId == flowMeterIdToManipulate.meterId diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchValidationSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchValidationSpec.groovy index 62b27796e6..3103ab32c2 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchValidationSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchValidationSpec.groovy @@ -4,10 +4,10 @@ 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.TOPOLOGY_DEPENDENT import static org.openkilda.functionaltests.helpers.SwitchHelper.isDefaultMeter -import static org.openkilda.functionaltests.helpers.SwitchHelper.isServer42Supported import static org.openkilda.functionaltests.helpers.model.FlowEncapsulationType.VXLAN +import static org.openkilda.functionaltests.helpers.model.SwitchExtended.verifyMeterSectionsAreEmpty +import static org.openkilda.functionaltests.helpers.model.SwitchExtended.verifyRuleSectionsAreEmpty import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.SYNCHRONIZE_SWITCH -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.RULES_DELETION_TIME import static org.openkilda.testing.Constants.RULES_INSTALLATION_TIME @@ -16,26 +16,22 @@ import static org.openkilda.testing.tools.KafkaUtils.buildMessage import org.openkilda.functionaltests.HealthCheckSpecification import org.openkilda.functionaltests.extension.tags.Tags -import org.openkilda.functionaltests.helpers.SwitchHelper import org.openkilda.functionaltests.helpers.Wrappers import org.openkilda.functionaltests.helpers.factory.FlowFactory import org.openkilda.functionaltests.helpers.model.FlowDirection -import org.openkilda.functionaltests.helpers.model.SwitchRulesFactory +import org.openkilda.functionaltests.helpers.model.SwitchExtended import org.openkilda.functionaltests.model.cleanup.CleanupManager import org.openkilda.messaging.command.switches.DeleteRulesAction import org.openkilda.messaging.model.FlowDirectionType import org.openkilda.model.MeterId -import org.openkilda.model.SwitchId import org.openkilda.model.cookie.Cookie import org.openkilda.model.cookie.CookieBase.CookieType -import org.openkilda.northbound.dto.v1.switches.SwitchPropertiesDto import org.openkilda.rulemanager.FlowSpeakerData import org.openkilda.rulemanager.Instructions import org.openkilda.rulemanager.MeterFlag import org.openkilda.rulemanager.MeterSpeakerData import org.openkilda.rulemanager.OfTable import org.openkilda.rulemanager.OfVersion -import org.openkilda.testing.model.topology.TopologyDefinition.Switch import com.google.common.collect.Sets import org.apache.kafka.clients.producer.KafkaProducer @@ -73,9 +69,6 @@ class SwitchValidationSpec extends HealthCheckSpecification { @Autowired @Shared FlowFactory flowFactory - @Autowired - @Shared - SwitchRulesFactory switchRulesFactory def setupSpec() { deleteAnyFlowsLeftoversIssue5480() @@ -83,17 +76,17 @@ class SwitchValidationSpec extends HealthCheckSpecification { def "Able to validate and sync a terminating switch with proper rules and meters"() { given: "A flow" - def (Switch srcSwitch, Switch dstSwitch) = topology.activeSwitches.findAll { it.ofVersion != "OF_12" } + def (SwitchExtended srcSwitch, SwitchExtended dstSwitch) = switches.all().notOF12Version() def flow = flowFactory.getRandom(srcSwitch, dstSwitch) expect: "Validate switch for src and dst contains expected meters data in 'proper' section" - def srcSwitchValidateInfo = switchHelper.validateV1(srcSwitch.dpId) - def dstSwitchValidateInfo = switchHelper.validateV1(dstSwitch.dpId) - def srcSwitchCreatedCookies = getCookiesWithMeter(srcSwitch.dpId) - def dstSwitchCreatedCookies = getCookiesWithMeter(dstSwitch.dpId) + def srcSwitchValidateInfo = srcSwitch.validateV1() + def dstSwitchValidateInfo = dstSwitch.validateV1() + def srcSwitchCreatedCookies = srcSwitch.rulesManager.getRulesWithMeter().cookie + def dstSwitchCreatedCookies = dstSwitch.rulesManager.getRulesWithMeter().cookie - srcSwitchValidateInfo.meters.proper*.meterId.containsAll(getCreatedMeterIds(srcSwitch.dpId)) - dstSwitchValidateInfo.meters.proper*.meterId.containsAll(getCreatedMeterIds(dstSwitch.dpId)) + srcSwitchValidateInfo.meters.proper*.meterId.containsAll(srcSwitch.metersManager.getCreatedMeterIds()) + dstSwitchValidateInfo.meters.proper*.meterId.containsAll(dstSwitch.metersManager.getCreatedMeterIds()) srcSwitchValidateInfo.meters.proper*.cookie.containsAll(srcSwitchCreatedCookies) dstSwitchValidateInfo.meters.proper*.cookie.containsAll(dstSwitchCreatedCookies) @@ -103,22 +96,21 @@ class SwitchValidationSpec extends HealthCheckSpecification { [[srcSwitch, srcSwitchProperMeters], [dstSwitch, dstSwitchProperMeters]].each { sw, meters -> meters.each { - SwitchHelper.verifyRateSizeIsCorrect(sw, flow.maximumBandwidth, it.rate) + sw.verifyRateSizeIsCorrect(flow.maximumBandwidth, it.rate) assert it.flowId == flow.flowId assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) } } - Long srcSwitchBurstSize = switchHelper.getExpectedBurst(srcSwitch.dpId, flow.maximumBandwidth) - Long dstSwitchBurstSize = switchHelper.getExpectedBurst(dstSwitch.dpId, flow.maximumBandwidth) - switchHelper.verifyBurstSizeIsCorrect(srcSwitch, srcSwitchBurstSize, - srcSwitchProperMeters*.burstSize[0]) - switchHelper.verifyBurstSizeIsCorrect(dstSwitch, dstSwitchBurstSize, - dstSwitchProperMeters*.burstSize[0]) + Long srcSwitchBurstSize = srcSwitch.getExpectedBurst(flow.maximumBandwidth) + srcSwitch.verifyBurstSizeIsCorrect(srcSwitchBurstSize, srcSwitchProperMeters*.burstSize[0]) + + Long dstSwitchBurstSize = dstSwitch.getExpectedBurst(flow.maximumBandwidth) + dstSwitch.verifyBurstSizeIsCorrect(dstSwitchBurstSize, dstSwitchProperMeters*.burstSize[0]) and: "The rest fields in the 'meter' section are empty" - srcSwitchValidateInfo.verifyMeterSectionsAreEmpty(["missing", "misconfigured", "excess"]) - dstSwitchValidateInfo.verifyMeterSectionsAreEmpty(["missing", "misconfigured", "excess"]) + verifyMeterSectionsAreEmpty(srcSwitchValidateInfo, ["missing", "misconfigured", "excess"]) + verifyMeterSectionsAreEmpty(dstSwitchValidateInfo, ["missing", "misconfigured", "excess"]) and: "Created rules are stored in the 'proper' section" def createdCookies = srcSwitchCreatedCookies + dstSwitchCreatedCookies @@ -127,21 +119,21 @@ class SwitchValidationSpec extends HealthCheckSpecification { } and: "The rest fields in the 'rule' section are empty" - srcSwitchValidateInfo.verifyRuleSectionsAreEmpty(["missing", "excess"]) - dstSwitchValidateInfo.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyRuleSectionsAreEmpty(srcSwitchValidateInfo, ["missing", "excess"]) + verifyRuleSectionsAreEmpty(dstSwitchValidateInfo, ["missing", "excess"]) and: "Able to perform switch sync which does nothing" - !switchHelper.synchronizeAndCollectFixedDiscrepancies(srcSwitch.dpId).isPresent() + !srcSwitch.synchronizeAndCollectFixedDiscrepancies().isPresent() when: "Delete the flow" flow.delete() then: "Switch validate request returns only default rules information" Wrappers.wait(WAIT_OFFSET) { - def srcSwitchValidateInfoAfterDelete = switchHelper.validateV1(srcSwitch.dpId) - def dstSwitchValidateInfoAfterDelete = switchHelper.validateV1(dstSwitch.dpId) - srcSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() - dstSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() + def srcSwitchValidateInfoAfterDelete = srcSwitch.validateV1() + def dstSwitchValidateInfoAfterDelete = dstSwitch.validateV1() + verifyRuleSectionsAreEmpty(srcSwitchValidateInfoAfterDelete) + verifyRuleSectionsAreEmpty(dstSwitchValidateInfoAfterDelete) } } @@ -152,58 +144,58 @@ class SwitchValidationSpec extends HealthCheckSpecification { when: "Create an intermediate-switch flow" def flow = flowFactory.getRandom(switchPair) def flowPathInfo = flow.retrieveAllEntityPaths() - List involvedSwitches = flowPathInfo.getInvolvedIsls() - .collect { [it.srcSwitch, it.dstSwitch] }.flatten().unique() as List + List involvedSwitches = flowPathInfo.getInvolvedSwitches() + .collect{ switches.all().findSpecific(it) } then: "The intermediate switch does not contain any information about meter" - def switchToValidate = involvedSwitches[1..-2].find { !it.dpId.description.contains("OF_12") } - def intermediateSwitchValidateInfo = switchHelper.validateV1(switchToValidate.dpId) - intermediateSwitchValidateInfo.verifyMeterSectionsAreEmpty() + def switchToValidate = involvedSwitches[1..-2].find { !it.description.contains("OF_12") } + def intermediateSwitchValidateInfo = switchToValidate.validateV1() + verifyMeterSectionsAreEmpty(intermediateSwitchValidateInfo) and: "Rules are stored in the 'proper' section on the transit switch" intermediateSwitchValidateInfo.rules.proper.findAll { !new Cookie(it).serviceFlag }.size() == 2 - intermediateSwitchValidateInfo.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyRuleSectionsAreEmpty(intermediateSwitchValidateInfo, ["missing", "excess"]) and: "Able to perform switch sync which does nothing" - !switchHelper.synchronizeAndCollectFixedDiscrepancies(switchToValidate.dpId).isPresent() + !switchToValidate.synchronizeAndCollectFixedDiscrepancies().isPresent() when: "Delete the flow" flow.delete() then: "Check that the switch validate request returns empty sections" involvedSwitches.each { sw -> - def switchValidateInfo = switchHelper.validateV1(sw.dpId) - switchValidateInfo.verifyRuleSectionsAreEmpty() + def switchValidateInfo = sw.validateV1() + verifyRuleSectionsAreEmpty(switchValidateInfo) if (sw.description.contains("OF_13")) { - switchValidateInfo.verifyMeterSectionsAreEmpty() + verifyMeterSectionsAreEmpty(switchValidateInfo) } } } def "Able to validate switch with 'misconfigured' meters"() { when: "Create a flow" - def (Switch srcSwitch, Switch dstSwitch) = topology.activeSwitches.findAll { it.ofVersion != "OF_12" } + def (SwitchExtended srcSwitch, SwitchExtended dstSwitch) = switches.all().notOF12Version() def flow = flowFactory.getRandom(srcSwitch, dstSwitch) - def srcSwitchCreatedMeterIds = getCreatedMeterIds(srcSwitch.dpId) - def dstSwitchCreatedMeterIds = getCreatedMeterIds(dstSwitch.dpId) + def srcSwitchCreatedMeterIds = srcSwitch.metersManager.getCreatedMeterIds() + def dstSwitchCreatedMeterIds = dstSwitch.metersManager.getCreatedMeterIds() and: "Change bandwidth for the created flow directly in DB so that system thinks the installed meter is \ misconfigured" def newBandwidth = flow.maximumBandwidth + 100 /** at this point meter is set for given flow. Now update flow bandwidth directly via DB, it is done just for moving meter from the 'proper' section into the 'misconfigured'*/ - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronize(srcSwitch.dpId)}) - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronize(dstSwitch.dpId)}) + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {srcSwitch.synchronize()}) + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {dstSwitch.synchronize()}) flow.updateFlowBandwidthInDB(newBandwidth) //at this point existing meters do not correspond with the flow and: "Validate src and dst switches" - def srcSwitchValidateInfo = switchHelper.validateV1(srcSwitch.dpId) - def dstSwitchValidateInfo = switchHelper.validateV1(dstSwitch.dpId) + def srcSwitchValidateInfo = srcSwitch.validateV1() + def dstSwitchValidateInfo = dstSwitch.validateV1() then: "Meters info is moved into the 'misconfigured' section" - def srcSwitchCreatedCookies = getCookiesWithMeter(srcSwitch.dpId) - def dstSwitchCreatedCookies = getCookiesWithMeter(dstSwitch.dpId) + def srcSwitchCreatedCookies = srcSwitch.rulesManager.getRulesWithMeter().cookie + def dstSwitchCreatedCookies = dstSwitch.rulesManager.getRulesWithMeter().cookie srcSwitchValidateInfo.meters.misconfigured*.meterId.containsAll(srcSwitchCreatedMeterIds) dstSwitchValidateInfo.meters.misconfigured*.meterId.containsAll(dstSwitchCreatedMeterIds) @@ -214,18 +206,16 @@ misconfigured" [[srcSwitch, srcSwitchValidateInfo], [dstSwitch, dstSwitchValidateInfo]].each { sw, validation -> assert validation.meters.misconfigured.meterId.size() == 1 validation.meters.misconfigured.each { - SwitchHelper.verifyRateSizeIsCorrect(sw, flow.maximumBandwidth, it.rate) + sw.verifyRateSizeIsCorrect(flow.maximumBandwidth, it.rate) assert it.flowId == flow.flowId assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) } } - Long srcSwitchBurstSize = switchHelper.getExpectedBurst(srcSwitch.dpId, flow.maximumBandwidth) - Long dstSwitchBurstSize = switchHelper.getExpectedBurst(dstSwitch.dpId, flow.maximumBandwidth) - switchHelper.verifyBurstSizeIsCorrect(srcSwitch, srcSwitchBurstSize, - srcSwitchValidateInfo.meters.misconfigured*.burstSize[0]) - switchHelper.verifyBurstSizeIsCorrect(dstSwitch, dstSwitchBurstSize, - dstSwitchValidateInfo.meters.misconfigured*.burstSize[0]) + Long srcSwitchBurstSize = srcSwitch.getExpectedBurst(flow.maximumBandwidth) + Long dstSwitchBurstSize = dstSwitch.getExpectedBurst(flow.maximumBandwidth) + srcSwitch.verifyBurstSizeIsCorrect(srcSwitchBurstSize, srcSwitchValidateInfo.meters.misconfigured*.burstSize[0]) + dstSwitch.verifyBurstSizeIsCorrect(dstSwitchBurstSize, dstSwitchValidateInfo.meters.misconfigured*.burstSize[0]) and: "Reason is specified why meter is misconfigured" [srcSwitchValidateInfo, dstSwitchValidateInfo].each { @@ -236,28 +226,29 @@ misconfigured" } and: "The rest fields of 'meter' section are empty" - srcSwitchValidateInfo.verifyMeterSectionsAreEmpty(["proper", "missing", "excess"]) - dstSwitchValidateInfo.verifyMeterSectionsAreEmpty(["proper", "missing", "excess"]) + verifyMeterSectionsAreEmpty(srcSwitchValidateInfo, ["proper", "missing", "excess"]) + verifyMeterSectionsAreEmpty(dstSwitchValidateInfo, ["proper", "missing", "excess"]) and: "Created rules are still stored in the 'proper' section" def createdCookies = srcSwitchCreatedCookies + dstSwitchCreatedCookies - [[srcSwitch.dpId, srcSwitchValidateInfo], [dstSwitch.dpId, dstSwitchValidateInfo]].each { swId, info -> + [[srcSwitch.switchId, srcSwitchValidateInfo], [dstSwitch.switchId, dstSwitchValidateInfo]].each { swId, info -> assert info.rules.proper.containsAll(createdCookies), swId - info.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyRuleSectionsAreEmpty(info, ["missing", "excess"]) } and: "Flow validation shows discrepancies" def involvedSwitches = flow.retrieveAllEntityPaths().getInvolvedSwitches() + .collect { switches.all().findSpecific(it) } def totalSwitchRules = 0 def totalSwitchMeters = 0 - involvedSwitches.each { swId -> - totalSwitchRules += switchRulesFactory.get(swId).getRules().size() - totalSwitchMeters += northbound.getAllMeters(swId).meterEntries.size() + involvedSwitches.each { sw -> + totalSwitchRules += sw.rulesManager.getRules().size() + totalSwitchMeters += sw.metersManager.getMeters().size() } def flowValidateResponse = flow.validate() def expectedRulesCount = [ - flow.getFlowRulesCountBySwitch(FlowDirection.FORWARD, involvedSwitches.size(), isServer42Supported(srcSwitch.dpId)), - flow.getFlowRulesCountBySwitch(FlowDirection.REVERSE, involvedSwitches.size(), isServer42Supported(dstSwitch.dpId))] + flow.getFlowRulesCountBySwitch(FlowDirection.FORWARD, involvedSwitches.size(), srcSwitch.isS42FlowRttEnabled()), + flow.getFlowRulesCountBySwitch(FlowDirection.REVERSE, involvedSwitches.size(), dstSwitch.isS42FlowRttEnabled())] flowValidateResponse.eachWithIndex { direction, i -> assert direction.discrepancies.size() == 2 @@ -274,9 +265,9 @@ misconfigured" // that's why we use eachWithIndex and why we calculate burstSize for src/dst switches def sw = (i == 0) ? srcSwitch : dstSwitch def switchBurstSize = (i == 0) ? srcSwitchBurstSize : dstSwitchBurstSize - Long newBurstSize = switchHelper.getExpectedBurst(sw.dpId, newBandwidth) - switchHelper.verifyBurstSizeIsCorrect(sw, newBurstSize, burst.expectedValue.toLong()) - switchHelper.verifyBurstSizeIsCorrect(sw, switchBurstSize, burst.actualValue.toLong()) + Long newBurstSize = sw.getExpectedBurst(newBandwidth) + sw.verifyBurstSizeIsCorrect(newBurstSize, burst.expectedValue.toLong()) + sw.verifyBurstSizeIsCorrect(switchBurstSize, burst.actualValue.toLong()) assert direction.flowRulesTotal == ((FlowDirectionType.FORWARD.toString() == direction.direction) ? expectedRulesCount[0] : expectedRulesCount[1]) @@ -289,13 +280,13 @@ misconfigured" flow.updateFlowBandwidthInDB(flow.maximumBandwidth) then: "Misconfigured meters are moved into the 'proper' section" - def srcSwitchValidateInfoRestored = switchHelper.validateV1(srcSwitch.dpId) - def dstSwitchValidateInfoRestored = switchHelper.validateV1(dstSwitch.dpId) + def srcSwitchValidateInfoRestored = srcSwitch.validateV1() + def dstSwitchValidateInfoRestored = dstSwitch.validateV1() srcSwitchValidateInfoRestored.meters.proper*.meterId.containsAll(srcSwitchCreatedMeterIds) dstSwitchValidateInfoRestored.meters.proper*.meterId.containsAll(dstSwitchCreatedMeterIds) - srcSwitchValidateInfoRestored.verifyMeterSectionsAreEmpty(["missing", "misconfigured", "excess"]) - dstSwitchValidateInfoRestored.verifyMeterSectionsAreEmpty(["missing", "misconfigured", "excess"]) + verifyMeterSectionsAreEmpty(srcSwitchValidateInfoRestored, ["missing", "misconfigured", "excess"]) + verifyMeterSectionsAreEmpty(dstSwitchValidateInfoRestored, ["missing", "misconfigured", "excess"]) and: "Flow validation shows no discrepancies" flow.validateAndCollectDiscrepancies().isEmpty() @@ -305,33 +296,33 @@ misconfigured" then: "Check that the switch validate request returns empty sections" Wrappers.wait(WAIT_OFFSET) { - def srcSwitchValidateInfoAfterDelete = switchHelper.validateV1(srcSwitch.dpId) - def dstSwitchValidateInfoAfterDelete = switchHelper.validateV1(dstSwitch.dpId) - srcSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() - dstSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() + def srcSwitchValidateInfoAfterDelete = srcSwitch.validateV1() + verifyRuleSectionsAreEmpty(srcSwitchValidateInfoAfterDelete) + def dstSwitchValidateInfoAfterDelete = dstSwitch.validateV1() + verifyRuleSectionsAreEmpty(dstSwitchValidateInfoAfterDelete) } } def "Able to validate and sync a switch with missing ingress rule + meter"() { when: "Create a flow" - def (Switch srcSwitch, Switch dstSwitch) = topology.activeSwitches.findAll { it.ofVersion != "OF_12" } + def (SwitchExtended srcSwitch, SwitchExtended dstSwitch) = switches.all().notOF12Version() def flow = flowFactory.getRandom(srcSwitch, dstSwitch) - def srcSwitchCreatedMeterIds = getCreatedMeterIds(srcSwitch.dpId) - def dstSwitchCreatedMeterIds = getCreatedMeterIds(dstSwitch.dpId) + def srcSwitchCreatedMeterIds = srcSwitch.metersManager.getCreatedMeterIds() + def dstSwitchCreatedMeterIds = dstSwitch.metersManager.getCreatedMeterIds() and: "Remove created meter on the srcSwitch" - def forwardCookies = getCookiesWithMeter(srcSwitch.dpId) - def reverseCookies = getCookiesWithMeter(dstSwitch.dpId) - def sharedCookieOnSrcSw = switchRulesFactory.get(srcSwitch.dpId).getRules().findAll { + def forwardCookies = srcSwitch.rulesManager.getRulesWithMeter().cookie + def reverseCookies = dstSwitch.rulesManager.getRulesWithMeter().cookie + def sharedCookieOnSrcSw = srcSwitch.rulesManager.getRules().findAll { new Cookie(it.cookie).getType() in [CookieType.SHARED_OF_FLOW, CookieType.SERVER_42_FLOW_RTT_INGRESS] }?.cookie def untouchedCookiesOnSrcSw = (reverseCookies + sharedCookieOnSrcSw).sort() - def cookiesOnDstSw = switchRulesFactory.get(dstSwitch.dpId).getRules().cookie - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronize(srcSwitch.dpId)}) - northbound.deleteMeter(srcSwitch.dpId, srcSwitchCreatedMeterIds[0]) + def cookiesOnDstSw = dstSwitch.rulesManager.getRules().cookie + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {srcSwitch.synchronize()}) + srcSwitch.metersManager.delete(srcSwitchCreatedMeterIds[0]) then: "Meters info/rules are moved into the 'missing' section on the srcSwitch" - verifyAll(switchHelper.validateV1(srcSwitch.dpId)) { + verifyAll(srcSwitch.validateV1()) { it.rules.missing.sort() == forwardCookies it.rules.proper.findAll { !new Cookie(it).serviceFlag }.sort() == untouchedCookiesOnSrcSw //forward cookie's removed with meter @@ -339,19 +330,19 @@ misconfigured" it.meters.missing*.meterId == srcSwitchCreatedMeterIds it.meters.missing*.cookie == forwardCookies - Long srcSwitchBurstSize = switchHelper.getExpectedBurst(srcSwitch.dpId, flow.maximumBandwidth) + Long srcSwitchBurstSize = srcSwitch.getExpectedBurst(flow.maximumBandwidth) it.meters.missing.each { - SwitchHelper.verifyRateSizeIsCorrect(srcSwitch, flow.maximumBandwidth, it.rate) + srcSwitch.verifyRateSizeIsCorrect(flow.maximumBandwidth, it.rate) assert it.flowId == flow.flowId assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) - switchHelper.verifyBurstSizeIsCorrect(srcSwitch, srcSwitchBurstSize, it.burstSize) + srcSwitch.verifyBurstSizeIsCorrect(srcSwitchBurstSize, it.burstSize) } - it.verifyMeterSectionsAreEmpty(["proper", "misconfigured", "excess"]) - it.verifyRuleSectionsAreEmpty(["excess"]) + verifyMeterSectionsAreEmpty(it, ["proper", "misconfigured", "excess"]) + verifyRuleSectionsAreEmpty(it, ["excess"]) } and: "Meters info/rules are NOT moved into the 'missing' section on the dstSwitch" - verifyAll(switchHelper.validateV1(dstSwitch.dpId)) { + verifyAll(dstSwitch.validateV1()) { it.rules.proper.sort() == cookiesOnDstSw.sort() def properMeters = it.meters.proper.findAll({ dto -> !isDefaultMeter(dto) }) @@ -359,27 +350,27 @@ misconfigured" properMeters.cookie.size() == 1 properMeters*.cookie == reverseCookies - Long dstSwitchBurstSize = switchHelper.getExpectedBurst(dstSwitch.dpId, flow.maximumBandwidth) + Long dstSwitchBurstSize = dstSwitch.getExpectedBurst(flow.maximumBandwidth) properMeters.each { - SwitchHelper.verifyRateSizeIsCorrect(dstSwitch, flow.maximumBandwidth, it.rate) + dstSwitch.verifyRateSizeIsCorrect(flow.maximumBandwidth, it.rate) assert it.flowId == flow.flowId assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) - switchHelper.verifyBurstSizeIsCorrect(dstSwitch, dstSwitchBurstSize, it.burstSize) + dstSwitch.verifyBurstSizeIsCorrect(dstSwitchBurstSize, it.burstSize) } - it.verifyMeterSectionsAreEmpty(["missing", "misconfigured", "excess"]) - it.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyMeterSectionsAreEmpty(it, ["missing", "misconfigured", "excess"]) + verifyRuleSectionsAreEmpty(it, ["missing", "excess"]) } when: "Synchronize switch with missing rule and meter" - verifyAll(switchHelper.synchronize(srcSwitch.dpId, false)) { + verifyAll(srcSwitch.synchronize(false)) { it.rules.installed == forwardCookies it.meters.installed*.meterId == srcSwitchCreatedMeterIds as List } then: "Repeated validation shows no missing entities" - with(switchHelper.validateV1(srcSwitch.dpId)) { - it.verifyMeterSectionsAreEmpty(["missing", "misconfigured", "excess"]) - it.verifyRuleSectionsAreEmpty(["missing", "excess"]) + with(srcSwitch.validateV1()) { + verifyMeterSectionsAreEmpty(it, ["missing", "misconfigured", "excess"]) + verifyRuleSectionsAreEmpty(it, ["missing", "excess"]) } when: "Delete the flow" @@ -387,16 +378,16 @@ misconfigured" then: "Check that the switch validate request returns empty sections" Wrappers.wait(WAIT_OFFSET) { - def srcSwitchValidateInfoAfterDelete = switchHelper.validateV1(srcSwitch.dpId) - def dstSwitchValidateInfoAfterDelete = switchHelper.validateV1(dstSwitch.dpId) - srcSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() - dstSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() + def srcSwitchValidateInfoAfterDelete = srcSwitch.validateV1() + verifyRuleSectionsAreEmpty(srcSwitchValidateInfoAfterDelete) + def dstSwitchValidateInfoAfterDelete = dstSwitch.validateV1() + verifyRuleSectionsAreEmpty(dstSwitchValidateInfoAfterDelete) } } def "Able to validate and sync a switch with missing ingress rule (unmetered)"() { when: "Create a flow" - def (Switch srcSwitch, Switch dstSwitch) = topology.activeSwitches.findAll { it.ofVersion != "OF_12" } + def (SwitchExtended srcSwitch, SwitchExtended dstSwitch) = switches.all().notOF12Version() def flow = flowFactory.getBuilder(srcSwitch, dstSwitch) .withBandwidth(0) .withIgnoreBandwidth(true).build() @@ -406,32 +397,32 @@ misconfigured" def flowDBInfo = flow.retrieveDetailsFromDB() def ingressCookie = flowDBInfo.forwardPath.cookie.value def egressCookie = flowDBInfo.reversePath.cookie.value - switchHelper.deleteSwitchRules(srcSwitch.dpId, ingressCookie) + srcSwitch.rulesManager.delete(ingressCookie) then: "Ingress rule is moved into the 'missing' section on the srcSwitch" - def sharedCookieOnSrcSw = switchRulesFactory.get(srcSwitch.dpId).getRules().findAll { + def sharedCookieOnSrcSw = srcSwitch.rulesManager.getRules().findAll { new Cookie(it.cookie).getType() in [CookieType.SHARED_OF_FLOW, CookieType.SERVER_42_FLOW_RTT_INGRESS] }?.cookie def untouchedCookies = ([egressCookie] + sharedCookieOnSrcSw).sort() - verifyAll(switchHelper.validateV1(srcSwitch.dpId)) { + verifyAll(srcSwitch.validateV1()) { it.rules.missing == [ingressCookie] it.rules.proper.findAll { def cookie = new Cookie(it) !cookie.serviceFlag || cookie.type == CookieType.SHARED_OF_FLOW }.sort() == untouchedCookies - it.verifyMeterSectionsAreEmpty() - it.verifyRuleSectionsAreEmpty(["excess"]) + verifyMeterSectionsAreEmpty(it) + verifyRuleSectionsAreEmpty(it, ["excess"]) } when: "Synchronize switch with missing unmetered rule" - with(switchHelper.synchronize(srcSwitch.dpId, false)) { + with(srcSwitch.synchronize(false)) { rules.installed == [ingressCookie] } then: "Repeated validation shows no missing entities" - with(switchHelper.validateV1(srcSwitch.dpId)) { - it.verifyMeterSectionsAreEmpty() - it.verifyRuleSectionsAreEmpty(["missing", "excess"]) + with(srcSwitch.validateV1()) { + verifyMeterSectionsAreEmpty(it) + verifyRuleSectionsAreEmpty(it, ["missing", "excess"]) it.rules.proper.findAll { !new Cookie(it).serviceFlag }.sort() == (untouchedCookies + ingressCookie).sort() } @@ -440,10 +431,10 @@ misconfigured" then: "Check that the switch validate request returns empty sections" Wrappers.wait(WAIT_OFFSET) { - def srcSwitchValidateInfoAfterDelete = switchHelper.validateV1(srcSwitch.dpId) - def dstSwitchValidateInfoAfterDelete = switchHelper.validateV1(dstSwitch.dpId) - srcSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() - dstSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() + def srcSwitchValidateInfoAfterDelete = srcSwitch.validateV1() + verifyRuleSectionsAreEmpty(srcSwitchValidateInfoAfterDelete) + def dstSwitchValidateInfoAfterDelete = dstSwitch.validateV1() + verifyRuleSectionsAreEmpty(dstSwitchValidateInfoAfterDelete) } } @@ -455,28 +446,27 @@ misconfigured" def flow = flowFactory.getRandom(switchPair) when: "Delete created rules on the transit" - def involvedSwitchesIds = flow.retrieveAllEntityPaths().getInvolvedSwitches() - def transitSwId = involvedSwitchesIds[1] - switchHelper.deleteSwitchRules(transitSwId, DeleteRulesAction.IGNORE_DEFAULTS) + def involvedSwitches = flow.retrieveAllEntityPaths().getInvolvedSwitches() + .collect{ switches.all().findSpecific(it) } + def transitSw = involvedSwitches[1] + transitSw.rulesManager.delete(DeleteRulesAction.IGNORE_DEFAULTS) then: "Rule info is moved into the 'missing' section" - verifyAll(switchHelper.validateV1(transitSwId)) { + verifyAll(transitSw.validateV1()) { it.rules.missing.size() == 2 - it.rules.proper.findAll { - !new Cookie(it).serviceFlag - }.empty + it.rules.proper.findAll { !new Cookie(it).serviceFlag }.empty it.rules.excess.empty } when: "Synchronize the switch" - with(switchHelper.synchronize(transitSwId, false)) { + with(transitSw.synchronize(false)) { rules.installed.size() == 2 } then: "Repeated validation shows no discrepancies" - verifyAll(switchHelper.validateV1(transitSwId)) { + verifyAll(transitSw.validateV1()) { it.rules.proper.findAll { !new Cookie(it).serviceFlag }.size() == 2 - it.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyRuleSectionsAreEmpty(it, ["missing", "excess"]) } when: "Delete the flow" @@ -484,11 +474,11 @@ misconfigured" then: "Check that the switch validate request returns empty sections on all involved switches" Wrappers.wait(WAIT_OFFSET) { - involvedSwitchesIds.each { sw -> - def switchValidateInfo = switchHelper.validateV1(sw) - switchValidateInfo.verifyRuleSectionsAreEmpty() + involvedSwitches.each { sw -> + def switchValidateInfo = sw.validateV1() + verifyRuleSectionsAreEmpty(switchValidateInfo) if (!sw.description.contains("OF_12")) { - switchValidateInfo.verifyMeterSectionsAreEmpty() + verifyMeterSectionsAreEmpty(switchValidateInfo) } } } @@ -500,42 +490,45 @@ misconfigured" and: "Create an intermediate-switch flow" def flow = flowFactory.getRandom(switchPair) - def rulesOnSrc = switchRulesFactory.get(switchPair.src.dpId).getRules() - def rulesOnDst = switchRulesFactory.get(switchPair.dst.dpId).getRules() + def srcSwitch = switches.all().findSpecific(switchPair.src.dpId) + def dstSwitch = switches.all().findSpecific(switchPair.dst.dpId) + def rulesOnSrc = srcSwitch.rulesManager.getRules() + def rulesOnDst = dstSwitch.rulesManager.getRules() when: "Delete created rules on the srcSwitch" def egressCookie = flow.retrieveDetailsFromDB().reversePath.cookie.value - switchHelper.deleteSwitchRules(switchPair.src.dpId, egressCookie) + srcSwitch.rulesManager.delete(egressCookie) then: "Rule info is moved into the 'missing' section on the srcSwitch" - verifyAll(switchHelper.validateV1(switchPair.src.dpId)) { + verifyAll(srcSwitch.validateV1()) { it.rules.missing == [egressCookie] it.rules.proper.size() == rulesOnSrc.size() - 1 it.rules.excess.empty } and: "Rule info is NOT moved into the 'missing' section on the dstSwitch and transit switches" - def dstSwitchValidateInfo = switchHelper.validateV1(switchPair.dst.dpId) + def dstSwitchValidateInfo = dstSwitch.validateV1() dstSwitchValidateInfo.rules.proper.sort() == rulesOnDst*.cookie.sort() - dstSwitchValidateInfo.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyRuleSectionsAreEmpty(dstSwitchValidateInfo, ["missing", "excess"]) def involvedSwitchIds = flow.retrieveAllEntityPaths().getInvolvedSwitches() + .collect { switches.all().findSpecific(it) } def transitSwitches = involvedSwitchIds[1..-2].findAll { !it.description.contains("OF_12") } - transitSwitches.each { switchId -> - def transitSwitchValidateInfo = switchHelper.validateV1(switchId) + transitSwitches.each { sw -> + def transitSwitchValidateInfo = sw.validateV1() assert transitSwitchValidateInfo.rules.proper.findAll { !new Cookie(it).serviceFlag }.size() == 2 - transitSwitchValidateInfo.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyRuleSectionsAreEmpty(transitSwitchValidateInfo, ["missing", "excess"]) } when: "Synchronize the switch" - with(switchHelper.synchronize(switchPair.src.dpId, false)) { + with(srcSwitch.synchronize(false)) { rules.installed == [egressCookie] } then: "Repeated validation shows no discrepancies" - verifyAll(switchHelper.validateV1(switchPair.dst.dpId)) { + verifyAll(dstSwitch.validateV1()) { it.rules.proper.sort() == rulesOnDst*.cookie.sort() - it.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyRuleSectionsAreEmpty(it, ["missing", "excess"]) } when: "Delete the flow" @@ -543,10 +536,10 @@ misconfigured" then: "Check that the switch validate request returns empty sections on all involved switches" Wrappers.wait(WAIT_OFFSET) { - involvedSwitchIds.findAll { !it.description.contains("OF_12") }.each { switchId -> - def switchValidateInfo = switchHelper.validateV1(switchId) - switchValidateInfo.verifyRuleSectionsAreEmpty() - switchValidateInfo.verifyMeterSectionsAreEmpty() + involvedSwitchIds.findAll { !it.description.contains("OF_12") }.each { sw -> + def switchValidateInfo = sw.validateV1() + verifyRuleSectionsAreEmpty(switchValidateInfo) + verifyMeterSectionsAreEmpty(switchValidateInfo) } } } @@ -558,49 +551,52 @@ misconfigured" and: "Create an intermediate-switch flow" def flow = flowFactory.getRandom(switchPair) def involvedSwitches = flow.retrieveAllEntityPaths().getInvolvedSwitches() - def createdCookiesSrcSw = switchRulesFactory.get(switchPair.src.dpId).getRules().cookie - def createdCookiesDstSw = switchRulesFactory.get(switchPair.dst.dpId).getRules().cookie - def createdCookiesTransitSwitch = switchRulesFactory.get(involvedSwitches[1]).getRules().cookie + .collect { switches.all().findSpecific(it) } + def srcSwitch = involvedSwitches.find { it.switchId == switchPair.src.dpId } + def dstSwitch = involvedSwitches.find { it.switchId == switchPair.dst.dpId } + + def createdCookiesSrcSw = srcSwitch.rulesManager.getRules().cookie + def createdCookiesDstSw = dstSwitch.rulesManager.getRules().cookie + def createdCookiesTransitSwitch = involvedSwitches[1].rulesManager.getRules().cookie when: "Create excess rules on switches" 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(switchPair.src.dpId) - .meterEntries*.meterId).first() - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronize(switchPair.src.dpId)}) - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronize(switchPair.dst.dpId)}) - producer.send(new ProducerRecord(speakerTopic, switchPair.dst.dpId.toString(), buildMessage( + def excessMeterId = ((MIN_FLOW_METER_ID..100) - srcSwitch.metersManager.getMeters().meterId).first() + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {srcSwitch.synchronize()}) + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {dstSwitch.synchronize()}) + producer.send(new ProducerRecord(speakerTopic, dstSwitch.switchId.toString(), buildMessage( FlowSpeakerData.builder() - .switchId(switchPair.dst.dpId) - .ofVersion(OfVersion.of(switchPair.dst.ofVersion)) + .switchId(dstSwitch.switchId) + .ofVersion(OfVersion.of(dstSwitch.ofVersion)) .cookie(new Cookie(1L)) .table(OfTable.EGRESS) .priority(101) .instructions(Instructions.builder().build()) .build()).toJson())).get() involvedSwitches[1..-2].findAll { !it.description.contains("OF_12") }.each { transitSw -> - producer.send(new ProducerRecord(speakerTopic, transitSw.toString(), buildMessage( + producer.send(new ProducerRecord(speakerTopic, transitSw.switchId.toString(), buildMessage( FlowSpeakerData.builder() - .switchId(transitSw) - .ofVersion(OfVersion.of(switchPair.dst.ofVersion)) + .switchId(transitSw.switchId) + .ofVersion(OfVersion.of(transitSw.ofVersion)) .cookie(new Cookie(1L)) .table(OfTable.TRANSIT) .priority(102) .instructions(Instructions.builder().build()) .build()).toJson())).get() } - producer.send(new ProducerRecord(speakerTopic, switchPair.src.dpId.toString(), buildMessage([ + producer.send(new ProducerRecord(speakerTopic, srcSwitch.switchId.toString(), buildMessage([ FlowSpeakerData.builder() - .switchId(switchPair.src.dpId) - .ofVersion(OfVersion.of(switchPair.src.ofVersion)) + .switchId(srcSwitch.switchId) + .ofVersion(OfVersion.of(srcSwitch.ofVersion)) .cookie(new Cookie(1L)) .table(OfTable.INPUT) .priority(103) .instructions(Instructions.builder().build()) .build(), MeterSpeakerData.builder() - .switchId(switchPair.src.dpId) - .ofVersion(OfVersion.of(switchPair.src.ofVersion)) + .switchId(srcSwitch.switchId) + .ofVersion(OfVersion.of(srcSwitch.ofVersion)) .meterId(new MeterId(excessMeterId)) .rate(flow.getMaximumBandwidth()) .burst(flow.getMaximumBandwidth()) @@ -610,18 +606,18 @@ misconfigured" then: "Switch validation shows excess rules and store them in the 'excess' section" Wrappers.wait(WAIT_OFFSET) { - assert switchRulesFactory.get(switchPair.src.dpId).getRules().size() == createdCookiesSrcSw.size() + 1 + assert srcSwitch.rulesManager.getRules().size() == createdCookiesSrcSw.size() + 1 - involvedSwitches.findAll { !it.description.contains("OF_12") }.each { switchId -> - def involvedSwitchValidateInfo = switchHelper.validateV1(switchId) - if (switchId == switchPair.src.dpId) { + involvedSwitches.findAll { !it.description.contains("OF_12") }.each { sw -> + def involvedSwitchValidateInfo = sw.validateV1() + if (sw.switchId == switchPair.src.dpId) { assert involvedSwitchValidateInfo.rules.proper.sort() == createdCookiesSrcSw.sort() - } else if (switchId == switchPair.dst.dpId) { + } else if (sw.switchId == switchPair.dst.dpId) { assert involvedSwitchValidateInfo.rules.proper.sort() == createdCookiesDstSw.sort() } else { assert involvedSwitchValidateInfo.rules.proper.sort() == createdCookiesTransitSwitch.sort() } - involvedSwitchValidateInfo.verifyRuleSectionsAreEmpty(["missing"]) + verifyRuleSectionsAreEmpty(involvedSwitchValidateInfo, ["missing"]) assert involvedSwitchValidateInfo.rules.excess.size() == 1 assert involvedSwitchValidateInfo.rules.excess == [1L] @@ -632,42 +628,45 @@ misconfigured" and: "Excess meter is shown on the srcSwitch only" Long burstSize = flow.maximumBandwidth - def validateSwitchInfo = switchHelper.validateV1(switchPair.src.dpId) + def validateSwitchInfo = srcSwitch.validateV1() assert validateSwitchInfo.meters.excess.size() == 1 assert validateSwitchInfo.meters.excess.each { assert it.meterId == excessMeterId assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) - switchHelper.verifyRateSizeIsCorrect(switchPair.src, flow.maximumBandwidth, it.rate) - switchHelper.verifyBurstSizeIsCorrect(switchPair.src, burstSize, it.burstSize) + srcSwitch.verifyRateSizeIsCorrect(flow.maximumBandwidth, it.rate) + srcSwitch.verifyBurstSizeIsCorrect(burstSize, it.burstSize) } - involvedSwitches[1..-1].findAll { !it.description.contains("OF_12") }.each { switchId -> - assert switchHelper.validateV1(switchId).meters.excess.empty + involvedSwitches[1..-1].findAll { !it.description.contains("OF_12") }.each { sw -> + assert sw.validateV1().meters.excess.empty } when: "Try to synchronize every involved switch" - def syncResultsMap = switchHelper.synchronizeAndCollectFixedDiscrepancies(involvedSwitches) - then: "System deletes excess rules and meters" - involvedSwitches.findAll { !it.description.contains("OF_12") }.each { switchId -> - assert syncResultsMap[switchId].rules.excess.size() == 1 - assert syncResultsMap[switchId].rules.excess[0] == 1L - assert syncResultsMap[switchId].rules.removed.size() == 1 - assert syncResultsMap[switchId].rules.removed[0] == 1L + involvedSwitches.each { sw -> + def syncResponse = sw.synchronize() + if(!sw.description.contains("OF_12")) { + assert syncResponse.rules.excess.size() == 1 + assert syncResponse.rules.excess[0] == 1L + assert syncResponse.rules.removed.size() == 1 + assert syncResponse.rules.removed[0] == 1L + } + if(sw.switchId == switchPair.src.dpId ) { + assert syncResponse.meters.excess.size() == 1 + assert syncResponse.meters.excess.meterId[0] == excessMeterId + assert syncResponse.meters.removed.size() == 1 + assert syncResponse.meters.removed.meterId[0] == excessMeterId + } } - assert syncResultsMap[switchPair.src.dpId].meters.excess.size() == 1 - assert syncResultsMap[switchPair.src.dpId].meters.excess.meterId[0] == excessMeterId - assert syncResultsMap[switchPair.src.dpId].meters.removed.size() == 1 - assert syncResultsMap[switchPair.src.dpId].meters.removed.meterId[0] == excessMeterId when: "Delete the flow" flow.delete() then: "Check that the switch validate request returns empty sections on all involved switches" Wrappers.wait(WAIT_OFFSET) { - involvedSwitches.findAll { !it.description.contains("OF_12") }.each { switchId -> - def switchValidateInfo = switchHelper.validateV1(switchId) - switchValidateInfo.verifyRuleSectionsAreEmpty() - switchValidateInfo.verifyMeterSectionsAreEmpty() + involvedSwitches.findAll { !it.description.contains("OF_12") }.each { sw -> + def switchValidateInfo = sw.validateV1() + verifyRuleSectionsAreEmpty(switchValidateInfo) + verifyMeterSectionsAreEmpty(switchValidateInfo) } } @@ -685,67 +684,62 @@ misconfigured" and: "Remove required rules and meters from switches" def flowInfoFromDb = flow.retrieveDetailsFromDB() - List involvedSwitches = flow.retrieveAllEntityPaths().getInvolvedIsls() - .collect { [it.srcSwitch, it.dstSwitch] }.flatten().unique() as List - def transitSwitchIds = involvedSwitches[1..-2].dpId + List involvedSwitches = flow.retrieveAllEntityPaths().getInvolvedSwitches() + .collect { switches.all().findSpecific(it) } + def transitSwitch = involvedSwitches[1..-2] + def terminalSwitches = involvedSwitches.findAll { it.switchId in switchPair.toList().dpId } + def cookiesMap = involvedSwitches.collectEntries { sw -> - def defaultCookies = sw.defaultCookies - [sw.dpId, switchRulesFactory.get(sw.dpId).getRules().findAll { + def defaultCookies = sw.collectDefaultCookies() + [sw.switchId, sw.rulesManager.getRules().findAll { !(it.cookie in defaultCookies) && !new Cookie(it.cookie).serviceFlag }*.cookie] } def metersMap = involvedSwitches.collectEntries { sw -> - [sw.dpId, northbound.getAllMeters(sw.dpId).meterEntries.findAll { - it.meterId > MAX_SYSTEM_RULE_METER_ID - }*.meterId] + [sw.switchId, sw.metersManager.getCreatedMeterIds()] } expect: "Switch validation shows missing rules and meters on every related switch" - involvedSwitches.each { switchHelper.deleteSwitchRules(it.dpId, DeleteRulesAction.IGNORE_DEFAULTS) } - [switchPair.src, switchPair.dst].each { northbound.deleteMeter(it.dpId, metersMap[it.dpId][0]) } + involvedSwitches.each { it.rulesManager.delete(DeleteRulesAction.IGNORE_DEFAULTS) } + terminalSwitches.each { it.metersManager.delete(metersMap[it.switchId][0]) } + Wrappers.wait(RULES_DELETION_TIME) { - def validationResultsMap = involvedSwitches.collectEntries { [it.dpId, switchHelper.validateV1(it.dpId)] } - involvedSwitches.each { - def swProps = northbound.getSwitchProperties(it.dpId) - def switchIdInSrcOrDst = (it.dpId in [switchPair.src.dpId, switchPair.dst.dpId]) - def defaultAmountOfFlowRules = 2 // ingress + egress - def amountOfServer42Rules = 0 - if(swProps.server42FlowRtt && switchIdInSrcOrDst) { - amountOfServer42Rules +=1 - it.dpId == switchPair.src.dpId && flow.source.vlanId && ++amountOfServer42Rules - it.dpId == switchPair.dst.dpId && flow.destination.vlanId && ++amountOfServer42Rules + involvedSwitches.each { sw -> + def validateResponse = sw.validateV1() + def rulesCount = sw.collectFlowRelatedRulesAmount(flow) + + assert validateResponse.rules.missing.size() == rulesCount + assert validateResponse.rules.missingHex.size() == rulesCount + + if(sw in terminalSwitches) { + assert validateResponse.meters.missing.size() == 1 } - def rulesCount = defaultAmountOfFlowRules + amountOfServer42Rules + - (switchIdInSrcOrDst ? 1 : 0) - assert validationResultsMap[it.dpId].rules.missing.size() == rulesCount - assert validationResultsMap[it.dpId].rules.missingHex.size() == rulesCount } - [switchPair.src, switchPair.dst].each { assert validationResultsMap[it.dpId].meters.missing.size() == 1 } } when: "Try to synchronize all switches" - def syncResultsMap = switchHelper.synchronizeAndCollectFixedDiscrepancies(involvedSwitches*.getDpId()) - then: "System installs missing rules and meters" - involvedSwitches.each { - assert syncResultsMap[it.dpId].rules.proper.findAll { !new Cookie(it).serviceFlag }.size() == 0 - assert syncResultsMap[it.dpId].rules.excess.size() == 0 - assert syncResultsMap[it.dpId].rules.missing.containsAll(cookiesMap[it.dpId]) - assert syncResultsMap[it.dpId].rules.removed.size() == 0 - assert syncResultsMap[it.dpId].rules.installed.containsAll(cookiesMap[it.dpId]) - } - [switchPair.src, switchPair.dst].each { - assert syncResultsMap[it.dpId].meters.proper.findAll { !it.defaultMeter }.size() == 0 - assert syncResultsMap[it.dpId].meters.excess.size() == 0 - assert syncResultsMap[it.dpId].meters.missing*.meterId == metersMap[it.dpId] - assert syncResultsMap[it.dpId].meters.removed.size() == 0 - assert syncResultsMap[it.dpId].meters.installed*.meterId == metersMap[it.dpId] + involvedSwitches.each { sw -> + def syncResponse = sw.synchronize() + assert syncResponse.rules.proper.findAll { !new Cookie(it).serviceFlag }.size() == 0 + assert syncResponse.rules.excess.size() == 0 + assert syncResponse.rules.missing.containsAll(cookiesMap[sw.switchId]) + assert syncResponse.rules.removed.size() == 0 + assert syncResponse.rules.installed.containsAll(cookiesMap[sw.switchId]) + + if(sw in terminalSwitches) { + assert syncResponse.meters.proper.findAll { !it.defaultMeter }.size() == 0 + assert syncResponse.meters.excess.size() == 0 + assert syncResponse.meters.missing*.meterId == metersMap[sw.switchId] + assert syncResponse.meters.removed.size() == 0 + assert syncResponse.meters.installed*.meterId == metersMap[sw.switchId] + } } and: "Switch validation doesn't complain about missing rules and meters" Wrappers.wait(RULES_INSTALLATION_TIME) { - involvedSwitches.each { - def validationResult = switchHelper.validateV1(it.dpId) + involvedSwitches.each { sw -> + def validationResult = sw.validateV1() assert validationResult.rules.missing.size() == 0 assert validationResult.rules.missingHex.size() == 0 assert validationResult.meters.missing.size() == 0 @@ -755,7 +749,7 @@ misconfigured" and: "Rules are synced correctly" // ingressRule should contain "pushVxlan" // egressRule should contain "tunnel-id" - with(switchRulesFactory.get(switchPair.src.dpId).getRules()) { rules -> + with(terminalSwitches.find { it.switchId == switchPair.src.dpId }.rulesManager.getRules()) { rules -> assert rules.find { it.cookie == flowInfoFromDb.forwardPath.cookie.value }.instructions.applyActions.pushVxlan @@ -764,7 +758,7 @@ misconfigured" }.match.tunnelId } - with(switchRulesFactory.get(switchPair.dst.dpId).getRules()) { rules -> + with(terminalSwitches.find { it.switchId == switchPair.dst.dpId }.rulesManager.getRules()) { rules -> assert rules.find { it.cookie == flowInfoFromDb.forwardPath.cookie.value }.match.tunnelId @@ -773,8 +767,8 @@ misconfigured" }.instructions.applyActions.pushVxlan } - transitSwitchIds.each { swId -> - with(switchRulesFactory.get(swId).getRules()) { rules -> + transitSwitch.each { sw -> + with(sw.rulesManager.getRules()) { rules -> assert rules.find { it.cookie == flowInfoFromDb.forwardPath.cookie.value }.match.tunnelId @@ -791,15 +785,15 @@ misconfigured" def flow = flowFactory.getBuilder(swPair).withProtectedPath(true).build().create() def flowPathInfo = flow.retrieveAllEntityPaths() - def allSwitches = flowPathInfo.getInvolvedSwitches() - def rulesPerSwitch = allSwitches.collectEntries { swId -> - [swId, switchRulesFactory.get(swId).getRules().cookie.sort()] + def allSwitches = flowPathInfo.getInvolvedSwitches().collect{ switches.all().findSpecific(it) } + def rulesPerSwitch = allSwitches.collectEntries { sw -> + [sw.switchId, sw.rulesManager.getRules().cookie.sort()] } expect: "Upon validation all rules are stored in the 'proper' section" - allSwitches.each { swId -> - def rules = northbound.validateSwitchRules(swId) - assert rules.properRules.sort() == rulesPerSwitch[swId] + allSwitches.each { sw -> + def rules = sw.rulesManager.validate() + assert rules.properRules.sort() == rulesPerSwitch[sw.switchId] assert rules.missingRules.empty assert rules.excessRules.empty } @@ -807,39 +801,38 @@ misconfigured" when: "Delete rule of protected path on the srcSwitch (egress)" def protectedPath = flowPathInfo.flowPath.protectedPath.forward.nodes.nodes - def srcSwitchRules = switchRulesFactory.get(swPair.src.dpId).getRules().findAll { - !new Cookie(it.cookie).serviceFlag - } + def srcSwitch = allSwitches.find { it.switchId == swPair.src.dpId } + def srcSwitchRules =srcSwitch.rulesManager.getRules().findAll { !new Cookie(it.cookie).serviceFlag } def ruleToDelete = srcSwitchRules.find { //specifying protectedPath[0](src.inputPort) and protectedPath[1](src.outputPort) as protected path for FORWARD direction is used it.instructions?.applyActions?.flowOutput == protectedPath[0].portNo.toString() && it.match.inPort == protectedPath[1].portNo.toString() }.cookie - switchHelper.deleteSwitchRules(swPair.src.dpId, ruleToDelete) + srcSwitch.rulesManager.delete(ruleToDelete) then: "Deleted rule is moved to the 'missing' section on the srcSwitch" - verifyAll(switchHelper.validateV1(swPair.src.dpId)) { - it.rules.proper.sort() == rulesPerSwitch[swPair.src.dpId] - ruleToDelete + verifyAll(srcSwitch.validateV1()) { + it.rules.proper.sort() == (rulesPerSwitch[srcSwitch.switchId] - ruleToDelete).sort() it.rules.missing == [ruleToDelete] it.rules.excess.empty } and: "Rest switches are not affected by deleting the rule on the srcSwitch" - allSwitches.findAll { it != swPair.src.dpId }.each { swId -> - def validation = switchHelper.validateV1(swId) - assert validation.rules.proper.sort() == rulesPerSwitch[swId] + allSwitches.findAll { it != srcSwitch }.each { sw -> + def validation = sw.validateV1() + assert validation.rules.proper.sort() == rulesPerSwitch[sw.switchId] assert validation.rules.missing.empty assert validation.rules.excess.empty } when: "Synchronize switch with a missing protected path egress rule" - with(switchHelper.synchronize(swPair.src.dpId, false)) { + with(srcSwitch.synchronize(false)) { rules.installed == [ruleToDelete] } then: "Switch validation no longer shows missing rules" - verifyAll(switchHelper.validateV1(swPair.src.dpId)) { - it.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyAll(srcSwitch.validateV1()) { + verifyRuleSectionsAreEmpty(it, ["missing", "excess"]) it.rules.proper.sort() == rulesPerSwitch[swPair.src.dpId] } } @@ -847,36 +840,35 @@ misconfigured" def "Able to validate and sync a missing 'connected device' #data.descr rule"() { given: "A flow with enabled connected devices" def swPair = switchPairs.all().random() - Map initialProps = [swPair.src, swPair.dst] - .collectEntries { [(it): switchHelper.getCachedSwProps(it.getDpId())] } def flow = flowFactory.getBuilder(swPair) .withDetectedDevicesOnDst(true, true).build() .create() expect: "Switch validation puts connected device lldp rule into 'proper' section" - def deviceCookie = switchRulesFactory.get(flow.destination.switchId).getRules() - .find(data.cookieSearchClosure).cookie - with(switchHelper.validateV1(flow.destination.switchId)) { + def dstSwitch = switches.all().findSpecific(flow.destination.switchId) + def deviceCookie = dstSwitch.rulesManager.getRules().find(data.cookieSearchClosure).cookie + + with(dstSwitch.validateV1()) { it.rules.proper.contains(deviceCookie) } when: "Remove the connected device rule" - switchHelper.deleteSwitchRules(flow.destination.switchId, deviceCookie) + dstSwitch.rulesManager.delete(deviceCookie) then: "Switch validation puts connected device rule into 'missing' section" - verifyAll(switchHelper.validateV1(flow.destination.switchId)) { + verifyAll(dstSwitch.validateV1()) { !it.rules.proper.contains(deviceCookie) it.rules.missing.contains(deviceCookie) it.rules.missingHex.contains(Long.toHexString(deviceCookie)) } when: "Synchronize the switch" - with(switchHelper.synchronize(flow.destination.switchId, false)) { + with(dstSwitch.synchronize(false)) { it.rules.installed == [deviceCookie] } then: "Switch validation no longer shows any discrepancies in rules nor meters" - verifyAll(switchHelper.validateV1(flow.destination.switchId)) { + verifyAll(dstSwitch.validateV1()) { it.rules.proper.contains(deviceCookie) it.rules.missing.empty it.rules.missingHex.empty @@ -890,9 +882,9 @@ misconfigured" flow.delete() then: "Switch validation is empty" - verifyAll(switchHelper.validateV1(flow.destination.switchId)) { - it.verifyRuleSectionsAreEmpty() - it.verifyMeterSectionsAreEmpty() + verifyAll(dstSwitch.validateV1()) { + verifyRuleSectionsAreEmpty(it) + verifyMeterSectionsAreEmpty(it) } where: @@ -912,14 +904,4 @@ misconfigured" ] ] } - - List getCreatedMeterIds(SwitchId switchId) { - return northbound.getAllMeters(switchId).meterEntries.findAll { it.meterId > MAX_SYSTEM_RULE_METER_ID }*.meterId - } - - List getCookiesWithMeter(SwitchId switchId) { - return switchRulesFactory.get(switchId).getRules().findAll { - !new Cookie(it.cookie).serviceFlag && it.instructions.goToMeter - }*.cookie.sort() - } } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchValidationV2Spec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchValidationV2Spec.groovy index 883d4981a8..3ea65852b3 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchValidationV2Spec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchValidationV2Spec.groovy @@ -6,9 +6,11 @@ import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE_SWITCHES import static org.openkilda.functionaltests.extension.tags.Tag.TOPOLOGY_DEPENDENT import static org.openkilda.functionaltests.extension.tags.Tag.VIRTUAL import static org.openkilda.functionaltests.helpers.SwitchHelper.isDefaultMeter -import static org.openkilda.functionaltests.helpers.SwitchHelper.isServer42Supported +import static org.openkilda.functionaltests.helpers.model.SwitchExtended.verifyMeterSectionsAreEmpty +import static org.openkilda.functionaltests.helpers.model.SwitchExtended.verifyRuleSectionsAreEmpty +import static org.openkilda.functionaltests.helpers.model.SwitchExtended.verifySectionInSwitchValidationInfo +import static org.openkilda.functionaltests.helpers.model.SwitchExtended.verifySectionsAsExpectedFields import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.SYNCHRONIZE_SWITCH -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.RULES_DELETION_TIME import static org.openkilda.testing.Constants.RULES_INSTALLATION_TIME @@ -18,20 +20,17 @@ import static org.openkilda.testing.tools.KafkaUtils.buildMessage import org.openkilda.functionaltests.HealthCheckSpecification import org.openkilda.functionaltests.extension.tags.Tags import org.openkilda.functionaltests.helpers.DockerHelper -import org.openkilda.functionaltests.helpers.SwitchHelper import org.openkilda.functionaltests.helpers.Wrappers import org.openkilda.functionaltests.helpers.factory.FlowFactory import org.openkilda.functionaltests.helpers.model.ContainerName import org.openkilda.functionaltests.helpers.model.FlowDirection import org.openkilda.functionaltests.helpers.model.FlowEncapsulationType -import org.openkilda.functionaltests.helpers.model.SwitchRulesFactory +import org.openkilda.functionaltests.helpers.model.SwitchExtended import org.openkilda.functionaltests.model.cleanup.CleanupManager import org.openkilda.messaging.command.switches.DeleteRulesAction import org.openkilda.messaging.model.FlowDirectionType -import org.openkilda.messaging.payload.flow.DetectConnectedDevicesPayload import org.openkilda.model.MeterId import org.openkilda.model.SwitchFeature -import org.openkilda.model.SwitchId import org.openkilda.model.cookie.Cookie import org.openkilda.model.cookie.CookieBase.CookieType import org.openkilda.rulemanager.FlowSpeakerData @@ -40,7 +39,6 @@ import org.openkilda.rulemanager.MeterFlag import org.openkilda.rulemanager.MeterSpeakerData import org.openkilda.rulemanager.OfTable import org.openkilda.rulemanager.OfVersion -import org.openkilda.testing.model.topology.TopologyDefinition.Switch import com.google.common.collect.Sets import org.apache.kafka.clients.producer.KafkaProducer @@ -81,9 +79,6 @@ class SwitchValidationV2Spec extends HealthCheckSpecification { @Autowired @Shared FlowFactory flowFactory - @Autowired - @Shared - SwitchRulesFactory switchRulesFactory def setupSpec() { deleteAnyFlowsLeftoversIssue5480() @@ -91,17 +86,17 @@ class SwitchValidationV2Spec extends HealthCheckSpecification { def "Able to validate and sync a terminating switch with proper rules and meters"() { given: "A flow" - def (Switch srcSwitch, Switch dstSwitch) = topology.activeSwitches.findAll { it.ofVersion != "OF_12" } + def (SwitchExtended srcSwitch, SwitchExtended dstSwitch) = switches.all().notOF12Version() def flow = flowFactory.getRandom(srcSwitch, dstSwitch) expect: "Validate switch for src and dst contains expected meters data in 'proper' section" - def srcSwitchValidateInfo = switchHelper.validate(srcSwitch.dpId) - def dstSwitchValidateInfo = switchHelper.validate(dstSwitch.dpId) - def srcSwitchCreatedCookies = getCookiesWithMeter(srcSwitch.dpId) - def dstSwitchCreatedCookies = getCookiesWithMeter(dstSwitch.dpId) + def srcSwitchValidateInfo = srcSwitch.validate() + def dstSwitchValidateInfo = dstSwitch.validate() + def srcSwitchCreatedCookies = srcSwitch.rulesManager.getRulesWithMeter().cookie + def dstSwitchCreatedCookies = dstSwitch.rulesManager.getRulesWithMeter().cookie - srcSwitchValidateInfo.meters.proper*.meterId.containsAll(getCreatedMeterIds(srcSwitch.dpId)) - dstSwitchValidateInfo.meters.proper*.meterId.containsAll(getCreatedMeterIds(dstSwitch.dpId)) + srcSwitchValidateInfo.meters.proper*.meterId.containsAll(srcSwitch.metersManager.getCreatedMeterIds()) + dstSwitchValidateInfo.meters.proper*.meterId.containsAll(dstSwitch.metersManager.getCreatedMeterIds()) // due to the issue https://github.com/telstra/open-kilda/issues/5360 // srcSwitchValidateInfo.meters.proper*.cookie.containsAll(srcSwitchCreatedCookies) @@ -112,35 +107,33 @@ class SwitchValidationV2Spec extends HealthCheckSpecification { [[srcSwitch, srcSwitchProperMeters], [dstSwitch, dstSwitchProperMeters]].each { sw, meters -> meters.each { - SwitchHelper.verifyRateSizeIsCorrect(sw, flow.maximumBandwidth, it.rate) + sw.verifyRateSizeIsCorrect(flow.maximumBandwidth, it.rate) // assert it.flowId == flow.flowId due to the issue https://github.com/telstra/open-kilda/issues/5360 assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) } } - Long srcSwitchBurstSize = switchHelper.getExpectedBurst(srcSwitch.dpId, flow.maximumBandwidth) - Long dstSwitchBurstSize = switchHelper.getExpectedBurst(dstSwitch.dpId, flow.maximumBandwidth) - switchHelper.verifyBurstSizeIsCorrect(srcSwitch, srcSwitchBurstSize, - srcSwitchProperMeters*.burstSize[0]) - switchHelper.verifyBurstSizeIsCorrect(dstSwitch, dstSwitchBurstSize, - dstSwitchProperMeters*.burstSize[0]) + Long srcSwitchBurstSize = srcSwitch.getExpectedBurst(flow.maximumBandwidth) + Long dstSwitchBurstSize = dstSwitch.getExpectedBurst(flow.maximumBandwidth) + srcSwitch.verifyBurstSizeIsCorrect(srcSwitchBurstSize, srcSwitchProperMeters*.burstSize[0]) + dstSwitch.verifyBurstSizeIsCorrect(dstSwitchBurstSize, dstSwitchProperMeters*.burstSize[0]) and: "The rest fields in the 'meter' section are empty" - srcSwitchValidateInfo.verifyMeterSectionsAreEmpty(["missing", "misconfigured", "excess"]) - dstSwitchValidateInfo.verifyMeterSectionsAreEmpty(["missing", "misconfigured", "excess"]) + verifyMeterSectionsAreEmpty(srcSwitchValidateInfo, ["missing", "misconfigured", "excess"]) + verifyMeterSectionsAreEmpty(dstSwitchValidateInfo, ["missing", "misconfigured", "excess"]) and: "Created rules are stored in the 'proper' section" def createdCookies = srcSwitchCreatedCookies + dstSwitchCreatedCookies [srcSwitchValidateInfo, dstSwitchValidateInfo].each { - it.rules.proper.containsAll(createdCookies) + assert it.rules.proper.cookie.containsAll(createdCookies) } and: "The rest fields in the 'rule' section are empty" - srcSwitchValidateInfo.verifyRuleSectionsAreEmpty(["missing", "excess"]) - dstSwitchValidateInfo.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyRuleSectionsAreEmpty(srcSwitchValidateInfo, ["missing", "excess"]) + verifyRuleSectionsAreEmpty(dstSwitchValidateInfo, ["missing", "excess"]) and: "Able to perform switch sync which does nothing" - verifyAll(switchHelper.synchronize(srcSwitch.dpId, true)) { + verifyAll(srcSwitch.synchronize(true)) { it.rules.removed.empty it.rules.installed.empty it.meters.removed.empty @@ -152,10 +145,10 @@ class SwitchValidationV2Spec extends HealthCheckSpecification { then: "Switch validate request returns only default rules information" Wrappers.wait(WAIT_OFFSET) { - def srcSwitchValidateInfoAfterDelete = switchHelper.validate(srcSwitch.dpId) - def dstSwitchValidateInfoAfterDelete = switchHelper.validate(dstSwitch.dpId) - srcSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() - dstSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() + def srcSwitchValidateInfoAfterDelete = srcSwitch.validate() + verifyRuleSectionsAreEmpty(srcSwitchValidateInfoAfterDelete) + def dstSwitchValidateInfoAfterDelete = dstSwitch.validate() + verifyRuleSectionsAreEmpty(dstSwitchValidateInfoAfterDelete) } } @@ -167,20 +160,19 @@ class SwitchValidationV2Spec extends HealthCheckSpecification { when: "Create an intermediate-switch flow" def flow = flowFactory.getRandom(switchPair) def flowPathInfo = flow.retrieveAllEntityPaths() - def involvedSwitches = flowPathInfo.getInvolvedIsls() - .collect { [it.srcSwitch, it.dstSwitch] }.flatten().unique() as List + def involvedSwitches = flowPathInfo.getInvolvedSwitches().collect { switches.all().findSpecific(it) } then: "The intermediate switch does not contain any information about meter" - def switchToValidate = involvedSwitches[1..-2].find { !it.dpId.description.contains("OF_12") } - def intermediateSwitchValidateInfo = switchHelper.validate(switchToValidate.dpId) - intermediateSwitchValidateInfo.verifyMeterSectionsAreEmpty() + def switchToValidate = involvedSwitches[1..-2].find { !it.description.contains("OF_12") } + def intermediateSwitchValidateInfo = switchToValidate.validate() + verifyMeterSectionsAreEmpty(intermediateSwitchValidateInfo) and: "Rules are stored in the 'proper' section on the transit switch" intermediateSwitchValidateInfo.rules.proper.findAll { !new Cookie(it.cookie).serviceFlag }.size() == 2 - intermediateSwitchValidateInfo.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyRuleSectionsAreEmpty(intermediateSwitchValidateInfo, ["missing", "excess"]) and: "Able to perform switch sync which does nothing" - verifyAll(switchHelper.synchronize(switchToValidate.dpId, true)) { + verifyAll(switchToValidate.synchronize(true)) { it.rules.removed.empty it.rules.installed.empty it.meters.removed.empty @@ -192,38 +184,38 @@ class SwitchValidationV2Spec extends HealthCheckSpecification { then: "Check that the switch validate request returns empty sections" involvedSwitches.each { sw -> - def switchValidateInfo = switchHelper.validate(sw.dpId) - switchValidateInfo.verifyRuleSectionsAreEmpty() + def switchValidateInfo = sw.validate() + verifyRuleSectionsAreEmpty(switchValidateInfo) if (sw.description.contains("OF_13")) { - switchValidateInfo.verifyMeterSectionsAreEmpty() + verifyMeterSectionsAreEmpty(switchValidateInfo) } } } def "Able to validate switch with 'misconfigured' meters"() { when: "Create a flow" - def (Switch srcSwitch, Switch dstSwitch) = topology.activeSwitches.findAll { it.ofVersion != "OF_12" } + def (SwitchExtended srcSwitch, SwitchExtended dstSwitch) = switches.all().notOF12Version() def flow = flowFactory.getRandom(srcSwitch, dstSwitch) - def srcSwitchCreatedMeterIds = getCreatedMeterIds(srcSwitch.dpId) - def dstSwitchCreatedMeterIds = getCreatedMeterIds(dstSwitch.dpId) + def srcSwitchCreatedMeterIds = srcSwitch.metersManager.getCreatedMeterIds() + def dstSwitchCreatedMeterIds = dstSwitch.metersManager.getCreatedMeterIds() and: "Change bandwidth for the created flow directly in DB so that system thinks the installed meter is \ misconfigured" def newBandwidth = flow.maximumBandwidth + 100 - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronize(srcSwitch.dpId)}) - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronize(dstSwitch.dpId)}) + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {srcSwitch.synchronize()}) + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {dstSwitch.synchronize()}) /** at this point meter is set for given flow. Now update flow bandwidth directly via DB, it is done just for moving meter from the 'proper' section into the 'misconfigured'*/ flow.updateFlowBandwidthInDB(newBandwidth) //at this point existing meters do not correspond with the flow and: "Validate src and dst switches" - def srcSwitchValidateInfo = switchHelper.validate(srcSwitch.dpId) - def dstSwitchValidateInfo = switchHelper.validate(dstSwitch.dpId) + def srcSwitchValidateInfo = srcSwitch.validate() + def dstSwitchValidateInfo = dstSwitch.validate() then: "Meters info is moved into the 'misconfigured' section" - def srcSwitchCreatedCookies = getCookiesWithMeter(srcSwitch.dpId) - def dstSwitchCreatedCookies = getCookiesWithMeter(dstSwitch.dpId) + def srcSwitchCreatedCookies = srcSwitch.rulesManager.getRulesWithMeter().cookie + def dstSwitchCreatedCookies = dstSwitch.rulesManager.getRulesWithMeter().cookie srcSwitchValidateInfo.meters.misconfigured*.expected.meterId.containsAll(srcSwitchCreatedMeterIds) dstSwitchValidateInfo.meters.misconfigured*.expected.meterId.containsAll(dstSwitchCreatedMeterIds) @@ -235,18 +227,16 @@ misconfigured" [[srcSwitch, srcSwitchValidateInfo], [dstSwitch, dstSwitchValidateInfo]].each { sw, validation -> assert validation.meters.misconfigured.id.size() == 1 validation.meters.misconfigured.each { - SwitchHelper.verifyRateSizeIsCorrect(sw, flow.maximumBandwidth, it.discrepancies.rate) + sw.verifyRateSizeIsCorrect(flow.maximumBandwidth, it.discrepancies.rate) //assert it.expected.flowId == flow.flowId due to the issue https://github.com/telstra/open-kilda/issues/5360 assert ["KBPS", "BURST", "STATS"].containsAll(it.expected.flags) } } - Long srcSwitchBurstSize = switchHelper.getExpectedBurst(srcSwitch.dpId, flow.maximumBandwidth) - Long dstSwitchBurstSize = switchHelper.getExpectedBurst(dstSwitch.dpId, flow.maximumBandwidth) - switchHelper.verifyBurstSizeIsCorrect(srcSwitch, srcSwitchBurstSize, - srcSwitchValidateInfo.meters.misconfigured*.discrepancies.burstSize[0]) - switchHelper.verifyBurstSizeIsCorrect(dstSwitch, dstSwitchBurstSize, - dstSwitchValidateInfo.meters.misconfigured*.discrepancies.burstSize[0]) + Long srcSwitchBurstSize = srcSwitch.getExpectedBurst(flow.maximumBandwidth) + Long dstSwitchBurstSize = dstSwitch.getExpectedBurst(flow.maximumBandwidth) + srcSwitch.verifyBurstSizeIsCorrect(srcSwitchBurstSize, srcSwitchValidateInfo.meters.misconfigured*.discrepancies.burstSize[0]) + dstSwitch.verifyBurstSizeIsCorrect(dstSwitchBurstSize, dstSwitchValidateInfo.meters.misconfigured*.discrepancies.burstSize[0]) and: "Reason is specified why meter is misconfigured" [srcSwitchValidateInfo, dstSwitchValidateInfo].each { @@ -257,14 +247,14 @@ misconfigured" } and: "The rest fields of 'meter' section are empty" - srcSwitchValidateInfo.verifyMeterSectionsAreEmpty(["proper", "missing", "excess"]) - dstSwitchValidateInfo.verifyMeterSectionsAreEmpty(["proper", "missing", "excess"]) + verifyMeterSectionsAreEmpty(srcSwitchValidateInfo, ["proper", "missing", "excess"]) + verifyMeterSectionsAreEmpty(dstSwitchValidateInfo, ["proper", "missing", "excess"]) and: "Created rules are still stored in the 'proper' section" def createdCookies = srcSwitchCreatedCookies + dstSwitchCreatedCookies - [[srcSwitch.dpId, srcSwitchValidateInfo], [dstSwitch.dpId, dstSwitchValidateInfo]].each { swId, info -> + [[srcSwitch.switchId, srcSwitchValidateInfo], [dstSwitch.switchId, dstSwitchValidateInfo]].each { swId, info -> assert info.rules.proper*.cookie.containsAll(createdCookies), swId - info.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyRuleSectionsAreEmpty(info, ["missing", "excess"]) } and: "Flow validation shows discrepancies" @@ -276,8 +266,8 @@ misconfigured" totalSwitchMeters += northbound.getAllMeters(swId).meterEntries.size() } def expectedRulesCount = [ - flow.getFlowRulesCountBySwitch(FlowDirection.FORWARD, involvedSwitches.size(), isServer42Supported(srcSwitch.dpId)), - flow.getFlowRulesCountBySwitch(FlowDirection.REVERSE, involvedSwitches.size(), isServer42Supported(dstSwitch.dpId))] + flow.getFlowRulesCountBySwitch(FlowDirection.FORWARD, involvedSwitches.size(), srcSwitch.isS42FlowRttEnabled()), + flow.getFlowRulesCountBySwitch(FlowDirection.REVERSE, involvedSwitches.size(), dstSwitch.isS42FlowRttEnabled())] def flowValidateResponse = flow.validate() flowValidateResponse.eachWithIndex { direction, i -> @@ -295,9 +285,9 @@ misconfigured" // that's why we use eachWithIndex and why we calculate burstSize for src/dst switches def sw = (i == 0) ? srcSwitch : dstSwitch def switchBurstSize = (i == 0) ? srcSwitchBurstSize : dstSwitchBurstSize - Long newBurstSize = switchHelper.getExpectedBurst(sw.dpId, newBandwidth) - switchHelper.verifyBurstSizeIsCorrect(sw, newBurstSize, burst.expectedValue.toLong()) - switchHelper.verifyBurstSizeIsCorrect(sw, switchBurstSize, burst.actualValue.toLong()) + Long newBurstSize = sw.getExpectedBurst(newBandwidth) + sw.verifyBurstSizeIsCorrect(newBurstSize, burst.expectedValue.toLong()) + sw.verifyBurstSizeIsCorrect(switchBurstSize, burst.actualValue.toLong()) assert direction.flowRulesTotal == (FlowDirectionType.FORWARD.toString() == direction.direction ? expectedRulesCount[0] : expectedRulesCount[1]) @@ -310,13 +300,13 @@ misconfigured" flow.updateFlowBandwidthInDB(flow.maximumBandwidth) then: "Misconfigured meters are moved into the 'proper' section" - def srcSwitchValidateInfoRestored = switchHelper.validate(srcSwitch.dpId) - def dstSwitchValidateInfoRestored = switchHelper.validate(dstSwitch.dpId) + def srcSwitchValidateInfoRestored = srcSwitch.validate() + def dstSwitchValidateInfoRestored = dstSwitch.validate() srcSwitchValidateInfoRestored.meters.proper*.meterId.containsAll(srcSwitchCreatedMeterIds) dstSwitchValidateInfoRestored.meters.proper*.meterId.containsAll(dstSwitchCreatedMeterIds) - srcSwitchValidateInfoRestored.verifyMeterSectionsAreEmpty(["missing", "misconfigured", "excess"]) - dstSwitchValidateInfoRestored.verifyMeterSectionsAreEmpty(["missing", "misconfigured", "excess"]) + verifyMeterSectionsAreEmpty(srcSwitchValidateInfoRestored, ["missing", "misconfigured", "excess"]) + verifyMeterSectionsAreEmpty(dstSwitchValidateInfoRestored, ["missing", "misconfigured", "excess"]) and: "Flow validation shows no discrepancies" flow.validateAndCollectDiscrepancies().isEmpty() @@ -326,34 +316,34 @@ misconfigured" then: "Check that the switch validate request returns empty sections" Wrappers.wait(WAIT_OFFSET) { - def srcSwitchValidateInfoAfterDelete = switchHelper.validate(srcSwitch.dpId) - def dstSwitchValidateInfoAfterDelete = switchHelper.validate(dstSwitch.dpId) - srcSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() - dstSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() + def srcSwitchValidateInfoAfterDelete = srcSwitch.validate() + verifyRuleSectionsAreEmpty(srcSwitchValidateInfoAfterDelete) + def dstSwitchValidateInfoAfterDelete = dstSwitch.validate() + verifyRuleSectionsAreEmpty(dstSwitchValidateInfoAfterDelete) } } def "Able to validate and sync a switch with missing ingress rule + meter"() { when: "Create a flow" - def (Switch srcSwitch, Switch dstSwitch) = topology.activeSwitches.findAll { it.ofVersion != "OF_12" } + def (SwitchExtended srcSwitch, SwitchExtended dstSwitch) = switches.all().notOF12Version() def flow = flowFactory.getRandom(srcSwitch, dstSwitch) - def srcSwitchCreatedMeterIds = getCreatedMeterIds(srcSwitch.dpId) - def dstSwitchCreatedMeterIds = getCreatedMeterIds(dstSwitch.dpId) + def srcSwitchCreatedMeterIds = srcSwitch.metersManager.getCreatedMeterIds() + def dstSwitchCreatedMeterIds = dstSwitch.metersManager.getCreatedMeterIds() and: "Remove created meter on the srcSwitch" - def forwardCookies = getCookiesWithMeter(srcSwitch.dpId) - def reverseCookies = getCookiesWithMeter(dstSwitch.dpId) - def sharedCookieOnSrcSw = switchRulesFactory.get(srcSwitch.dpId).getRules().findAll { + def forwardCookies = srcSwitch.rulesManager.getRulesWithMeter().cookie + def reverseCookies = dstSwitch.rulesManager.getRulesWithMeter().cookie + def sharedCookieOnSrcSw = srcSwitch.rulesManager.getRules().findAll { new Cookie(it.cookie).getType() in [CookieType.SHARED_OF_FLOW, CookieType.SERVER_42_FLOW_RTT_INGRESS] }?.cookie def untouchedCookiesOnSrcSw = (reverseCookies + sharedCookieOnSrcSw).sort() - def cookiesOnDstSw = switchRulesFactory.get(dstSwitch.dpId).getRules()*.cookie - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronize(srcSwitch.dpId)}) - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronize(dstSwitch.dpId)}) - northbound.deleteMeter(srcSwitch.dpId, srcSwitchCreatedMeterIds[0]) + def cookiesOnDstSw = dstSwitch.rulesManager.getRules()*.cookie + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {srcSwitch.synchronize()}) + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {dstSwitch.synchronize()}) + srcSwitch.metersManager.delete(srcSwitchCreatedMeterIds[0]) then: "Meters info/rules are moved into the 'missing' section on the srcSwitch" - verifyAll(switchHelper.validate(srcSwitch.dpId)) { + verifyAll(srcSwitch.validate()) { it.rules.missing*.cookie.sort() == forwardCookies it.rules.proper*.cookie.findAll { !new Cookie(it).serviceFlag }.sort() == untouchedCookiesOnSrcSw //forward cookie's removed with meter @@ -361,19 +351,19 @@ misconfigured" it.meters.missing*.meterId == srcSwitchCreatedMeterIds //it.meters.missing*.cookie == forwardCookies due to the issue https://github.com/telstra/open-kilda/issues/5360 - Long srcSwitchBurstSize = switchHelper.getExpectedBurst(srcSwitch.dpId, flow.maximumBandwidth) + Long srcSwitchBurstSize = srcSwitch.getExpectedBurst(flow.maximumBandwidth) it.meters.missing.each { - SwitchHelper.verifyRateSizeIsCorrect(srcSwitch, flow.maximumBandwidth, it.rate) + srcSwitch.verifyRateSizeIsCorrect(flow.maximumBandwidth, it.rate) //assert it.flowId == flow.flowId due to the issue https://github.com/telstra/open-kilda/issues/5360 assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) - switchHelper.verifyBurstSizeIsCorrect(srcSwitch, srcSwitchBurstSize, it.burstSize) + srcSwitch.verifyBurstSizeIsCorrect(srcSwitchBurstSize, it.burstSize) } - it.verifyMeterSectionsAreEmpty(["proper", "misconfigured", "excess"]) - it.verifyRuleSectionsAreEmpty(["excess"]) + verifyMeterSectionsAreEmpty(it, ["proper", "misconfigured", "excess"]) + verifyRuleSectionsAreEmpty(it, ["excess"]) } and: "Meters info/rules are NOT moved into the 'missing' section on the dstSwitch" - verifyAll(switchHelper.validate(dstSwitch.dpId)) { + verifyAll(dstSwitch.validate()) { it.rules.proper*.cookie.sort() == cookiesOnDstSw.sort() def properMeters = it.meters.proper.findAll({ dto -> !isDefaultMeter(dto) }) @@ -381,27 +371,27 @@ misconfigured" properMeters.cookie.size() == 1 //properMeters*.cookie == reverseCookies due to https://github.com/telstra/open-kilda/issues/5360 - Long dstSwitchBurstSize = switchHelper.getExpectedBurst(dstSwitch.dpId, flow.maximumBandwidth) + Long dstSwitchBurstSize = dstSwitch.getExpectedBurst(flow.maximumBandwidth) properMeters.each { - SwitchHelper.verifyRateSizeIsCorrect(dstSwitch, flow.maximumBandwidth, it.rate) + dstSwitch.verifyRateSizeIsCorrect(flow.maximumBandwidth, it.rate) //assert it.flowId == flow.flowId due to https://github.com/telstra/open-kilda/issues/5360 assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) - switchHelper.verifyBurstSizeIsCorrect(dstSwitch, dstSwitchBurstSize, it.burstSize) + dstSwitch.verifyBurstSizeIsCorrect(dstSwitchBurstSize, it.burstSize) } - it.verifyMeterSectionsAreEmpty(["missing", "misconfigured", "excess"]) - it.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyMeterSectionsAreEmpty(it, ["missing", "misconfigured", "excess"]) + verifyRuleSectionsAreEmpty(it, ["missing", "excess"]) } when: "Synchronize switch with missing rule and meter" - verifyAll(switchHelper.synchronize(srcSwitch.dpId, false)) { + verifyAll(srcSwitch.synchronize(false)) { it.rules.installed == forwardCookies it.meters.installed*.meterId == srcSwitchCreatedMeterIds as List } then: "Repeated validation shows no missing entities" - with(switchHelper.validate(srcSwitch.dpId)) { - it.verifyMeterSectionsAreEmpty(["missing", "misconfigured", "excess"]) - it.verifyRuleSectionsAreEmpty(["missing", "excess"]) + with(srcSwitch.validate()) { + verifyMeterSectionsAreEmpty(it, ["missing", "misconfigured", "excess"]) + verifyRuleSectionsAreEmpty(it, ["missing", "excess"]) } when: "Delete the flow" @@ -409,16 +399,16 @@ misconfigured" then: "Check that the switch validate request returns empty sections" Wrappers.wait(WAIT_OFFSET) { - def srcSwitchValidateInfoAfterDelete = switchHelper.validate(srcSwitch.dpId) - def dstSwitchValidateInfoAfterDelete = switchHelper.validate(dstSwitch.dpId) - srcSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() - dstSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() + def srcSwitchValidateInfoAfterDelete = srcSwitch.validate() + verifyRuleSectionsAreEmpty(srcSwitchValidateInfoAfterDelete) + def dstSwitchValidateInfoAfterDelete = dstSwitch.validate() + verifyRuleSectionsAreEmpty(dstSwitchValidateInfoAfterDelete) } } def "Able to validate and sync a switch with missing ingress rule (unmetered)"() { when: "Create a flow" - def (Switch srcSwitch, Switch dstSwitch) = topology.activeSwitches.findAll { it.ofVersion != "OF_12" } + def (SwitchExtended srcSwitch, SwitchExtended dstSwitch) = switches.all().notOF12Version() def flow = flowFactory.getBuilder(srcSwitch, dstSwitch) .withBandwidth(0) .withIgnoreBandwidth(true).build() @@ -428,32 +418,32 @@ misconfigured" def flowDBInfo = flow.retrieveDetailsFromDB() def ingressCookie = flowDBInfo.forwardPath.cookie.value def egressCookie = flowDBInfo.reversePath.cookie.value - switchHelper.deleteSwitchRules(srcSwitch.dpId, ingressCookie) + srcSwitch.rulesManager.delete(ingressCookie) then: "Ingress rule is moved into the 'missing' section on the srcSwitch" - def sharedCookieOnSrcSw = switchRulesFactory.get(srcSwitch.dpId).getRules().findAll { + def sharedCookieOnSrcSw = srcSwitch.rulesManager.getRules().findAll { new Cookie(it.cookie).getType() in [CookieType.SHARED_OF_FLOW, CookieType.SERVER_42_FLOW_RTT_INGRESS] }?.cookie def untouchedCookies = ([egressCookie] + sharedCookieOnSrcSw).sort() - verifyAll(switchHelper.validate(srcSwitch.dpId)) { + verifyAll(srcSwitch.validate()) { it.rules.missing*.cookie == [ingressCookie] it.rules.proper*.cookie.findAll { def cookie = new Cookie(it) !cookie.serviceFlag || cookie.type == CookieType.SHARED_OF_FLOW }.sort() == untouchedCookies - it.verifyMeterSectionsAreEmpty() - it.verifyRuleSectionsAreEmpty(["excess"]) + verifyMeterSectionsAreEmpty(it) + verifyRuleSectionsAreEmpty(it, ["excess"]) } when: "Synchronize switch with missing unmetered rule" - with(switchHelper.synchronize(srcSwitch.dpId, false)) { + with(srcSwitch.synchronize(false)) { rules.installed == [ingressCookie] } then: "Repeated validation shows no missing entities" - with(switchHelper.validate(srcSwitch.dpId)) { - it.verifyMeterSectionsAreEmpty() - it.verifyRuleSectionsAreEmpty(["missing", "excess"]) + with(srcSwitch.validate()) { + verifyMeterSectionsAreEmpty(it) + verifyRuleSectionsAreEmpty(it, ["missing", "excess"]) it.rules.proper*.cookie.findAll { !new Cookie(it).serviceFlag }.sort() == (untouchedCookies + ingressCookie).sort() } @@ -462,10 +452,10 @@ misconfigured" then: "Check that the switch validate request returns empty sections" Wrappers.wait(WAIT_OFFSET) { - def srcSwitchValidateInfoAfterDelete = switchHelper.validate(srcSwitch.dpId) - def dstSwitchValidateInfoAfterDelete = switchHelper.validate(dstSwitch.dpId) - srcSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() - dstSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() + def srcSwitchValidateInfoAfterDelete = srcSwitch.validate() + verifyRuleSectionsAreEmpty(srcSwitchValidateInfoAfterDelete) + def dstSwitchValidateInfoAfterDelete = dstSwitch.validate() + verifyRuleSectionsAreEmpty(dstSwitchValidateInfoAfterDelete) } } @@ -477,27 +467,25 @@ misconfigured" def flow = flowFactory.getRandom(switchPair) when: "Delete created rules on the transit" - List involvedSwitches = flow.retrieveAllEntityPaths().getInvolvedIsls() - .collect { [it.srcSwitch, it.dstSwitch] }.flatten().unique() as List + def involvedSwitches = flow.retrieveAllEntityPaths().getInvolvedSwitches() + .collect{ switches.all().findSpecific(it) } def transitSw = involvedSwitches[1] - switchHelper.deleteSwitchRules(transitSw.dpId, DeleteRulesAction.IGNORE_DEFAULTS) + transitSw.rulesManager.delete(DeleteRulesAction.IGNORE_DEFAULTS) then: "Rule info is moved into the 'missing' section" - verifyAll(switchHelper.validate(transitSw.dpId)) { + verifyAll(transitSw.validate()) { it.rules.missing.size() == 2 - it.rules.proper*.cookie.findAll { - !new Cookie(it).serviceFlag - }.empty + it.rules.proper*.cookie.findAll { !new Cookie(it).serviceFlag }.empty it.rules.excess.empty } when: "Synchronize the switch" - with(switchHelper.synchronize(transitSw.dpId, false)) { + with(transitSw.synchronize(false)) { rules.installed.size() == 2 } then: "Repeated validation shows no discrepancies" - verifyAll(switchHelper.validate(transitSw.dpId)) { + verifyAll(transitSw.validate()) { it.rules.proper*.cookie.findAll { !new Cookie(it).serviceFlag }.size() == 2 - it.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyRuleSectionsAreEmpty(it, ["missing", "excess"]) } when: "Delete the flow" flow.delete() @@ -505,10 +493,10 @@ misconfigured" then: "Check that the switch validate request returns empty sections on all involved switches" Wrappers.wait(WAIT_OFFSET) { involvedSwitches.each { sw -> - def switchValidateInfo = switchHelper.validate(sw.dpId) - switchValidateInfo.verifyRuleSectionsAreEmpty() + def switchValidateInfo = sw.validate() + verifyRuleSectionsAreEmpty(switchValidateInfo) if (!sw.description.contains("OF_12")) { - switchValidateInfo.verifyMeterSectionsAreEmpty() + verifyMeterSectionsAreEmpty(switchValidateInfo) } } } @@ -517,45 +505,48 @@ misconfigured" def "Able to validate and sync a switch with missing egress rule"() { given: "Two active not neighboring switches" def switchPair = switchPairs.all().nonNeighbouring().random() + def srcSwitch = switches.all().findSpecific(switchPair.src.dpId) + def dstSwitch = switches.all().findSpecific(switchPair.dst.dpId) and: "Create an intermediate-switch flow" def flow = flowFactory.getRandom(switchPair) - def rulesOnSrc = northbound.getSwitchRules(switchPair.src.dpId).flowEntries - def rulesOnDst = northbound.getSwitchRules(switchPair.dst.dpId).flowEntries + def rulesOnSrc = srcSwitch.rulesManager.getRules() + def rulesOnDst = dstSwitch.rulesManager.getRules() when: "Delete created rules on the srcSwitch" def egressCookie = flow.retrieveDetailsFromDB().reversePath.cookie.value - switchHelper.deleteSwitchRules(switchPair.src.dpId, egressCookie) + srcSwitch.rulesManager.delete(egressCookie) then: "Rule info is moved into the 'missing' section on the srcSwitch" - verifyAll(switchHelper.validate(switchPair.src.dpId)) { + verifyAll(srcSwitch.validate()) { it.rules.missing*.cookie == [egressCookie] it.rules.proper.size() == rulesOnSrc.size() - 1 it.rules.excess.empty } and: "Rule info is NOT moved into the 'missing' section on the dstSwitch and transit switches" - def dstSwitchValidateInfo = switchHelper.validate(switchPair.dst.dpId) + def dstSwitchValidateInfo = dstSwitch.validate() dstSwitchValidateInfo.rules.proper*.cookie.sort() == rulesOnDst*.cookie.sort() - dstSwitchValidateInfo.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyRuleSectionsAreEmpty(dstSwitchValidateInfo, ["missing", "excess"]) def involvedSwitchIds = flow.retrieveAllEntityPaths().getInvolvedSwitches() + .collect { switches.all().findSpecific(it) } def transitSwitches = involvedSwitchIds[1..-2].findAll { !it.description.contains("OF_12") } - transitSwitches.each { switchId -> - def transitSwitchValidateInfo = switchHelper.validate(switchId) + transitSwitches.each { sw -> + def transitSwitchValidateInfo = sw.validate() assert transitSwitchValidateInfo.rules.proper*.cookie.findAll { !new Cookie(it).serviceFlag }.size() == 2 - transitSwitchValidateInfo.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyRuleSectionsAreEmpty(transitSwitchValidateInfo, ["missing", "excess"]) } when: "Synchronize the switch" - with(switchHelper.synchronize(switchPair.src.dpId, false)) { + with(srcSwitch.synchronize(false)) { rules.installed == [egressCookie] } then: "Repeated validation shows no discrepancies" - verifyAll(switchHelper.validate(switchPair.dst.dpId)) { + verifyAll(dstSwitch.validate()) { it.rules.proper*.cookie.sort() == rulesOnDst*.cookie.sort() - it.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyRuleSectionsAreEmpty(it, ["missing", "excess"]) } when: "Delete the flow" @@ -563,10 +554,10 @@ misconfigured" then: "Check that the switch validate request returns empty sections on all involved switches" Wrappers.wait(WAIT_OFFSET) { - involvedSwitchIds.findAll { !it.description.contains("OF_12") }.each { switchId -> - def switchValidateInfo = switchHelper.validate(switchId) - switchValidateInfo.verifyRuleSectionsAreEmpty() - switchValidateInfo.verifyMeterSectionsAreEmpty() + involvedSwitchIds.findAll { !it.description.contains("OF_12") }.each { sw -> + def switchValidateInfo = sw.validate() + verifyRuleSectionsAreEmpty(switchValidateInfo) + verifyMeterSectionsAreEmpty(switchValidateInfo) } } } @@ -578,48 +569,51 @@ misconfigured" and: "Create an intermediate-switch flow" def flow = flowFactory.getRandom(switchPair) def involvedSwitches = flow.retrieveAllEntityPaths().getInvolvedSwitches() - def createdCookiesSrcSw = switchRulesFactory.get(switchPair.src.dpId).getRules().cookie - def createdCookiesDstSw =switchRulesFactory.get(switchPair.dst.dpId).getRules().cookie - def createdCookiesTransitSwitch = switchRulesFactory.get(involvedSwitches[1]).getRules().cookie + .collect{ switches.all().findSpecific(it) } + def srcSwitch = involvedSwitches.find { it.switchId == switchPair.src.dpId } + def dstSwitch = involvedSwitches.find { it.switchId == switchPair.dst.dpId } + + def createdCookiesSrcSw = srcSwitch.rulesManager.getRules().cookie + def createdCookiesDstSw = dstSwitch.rulesManager.getRules().cookie + def createdCookiesTransitSwitch = involvedSwitches[1].rulesManager.getRules().cookie when: "Create excess rules on switches" 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(switchPair.src.dpId) - .meterEntries*.meterId).first() - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronizeAndCollectFixedDiscrepancies(involvedSwitches)}) - producer.send(new ProducerRecord(speakerTopic, switchPair.dst.dpId.toString(), buildMessage( + def excessMeterId = ((MIN_FLOW_METER_ID..100) - srcSwitch.metersManager.getMeters().meterId).first() + cleanupManager.addAction(SYNCHRONIZE_SWITCH, { involvedSwitches.each { it.synchronize() }}) + producer.send(new ProducerRecord(speakerTopic, dstSwitch.switchId.toString(), buildMessage( FlowSpeakerData.builder() - .switchId(switchPair.dst.dpId) - .ofVersion(OfVersion.of(switchPair.dst.ofVersion)) + .switchId(dstSwitch.switchId) + .ofVersion(OfVersion.of(dstSwitch.ofVersion)) .cookie(new Cookie(1L)) .table(OfTable.EGRESS) .priority(101) .instructions(Instructions.builder().build()) .build()).toJson())).get() involvedSwitches[1..-2].findAll { !it.description.contains("OF_12") }.each { transitSw -> - producer.send(new ProducerRecord(speakerTopic, transitSw.toString(), buildMessage( + producer.send(new ProducerRecord(speakerTopic, transitSw.switchId.toString(), buildMessage( FlowSpeakerData.builder() - .switchId(transitSw) - .ofVersion(OfVersion.of(switchPair.dst.ofVersion)) + .switchId(transitSw.switchId) + .ofVersion(OfVersion.of(transitSw.ofVersion)) .cookie(new Cookie(1L)) .table(OfTable.TRANSIT) .priority(102) .instructions(Instructions.builder().build()) .build()).toJson())).get() } - producer.send(new ProducerRecord(speakerTopic, switchPair.src.dpId.toString(), buildMessage([ + producer.send(new ProducerRecord(speakerTopic, srcSwitch.switchId.toString(), buildMessage([ FlowSpeakerData.builder() - .switchId(switchPair.src.dpId) - .ofVersion(OfVersion.of(switchPair.src.ofVersion)) + .switchId(srcSwitch.switchId) + .ofVersion(OfVersion.of(srcSwitch.ofVersion)) .cookie(new Cookie(1L)) .table(OfTable.INPUT) .priority(103) .instructions(Instructions.builder().build()) .build(), MeterSpeakerData.builder() - .switchId(switchPair.src.dpId) - .ofVersion(OfVersion.of(switchPair.src.ofVersion)) + .switchId(srcSwitch.switchId) + .ofVersion(OfVersion.of(srcSwitch.ofVersion)) .meterId(new MeterId(excessMeterId)) .rate(flow.getMaximumBandwidth()) .burst(flow.getMaximumBandwidth()) @@ -629,59 +623,63 @@ misconfigured" then: "Switch validation shows excess rules and store them in the 'excess' section" Wrappers.wait(WAIT_OFFSET) { - assert switchRulesFactory.get(switchPair.src.dpId).getRules().size() == createdCookiesSrcSw.size() + 1 - involvedSwitches.findAll { !it.description.contains("OF_12") }.each { switchId -> - def involvedSwitchValidateInfo = switchHelper.validate(switchId) - if (switchId == switchPair.src.dpId) { + assert srcSwitch.rulesManager.getRules().size() == createdCookiesSrcSw.size() + 1 + + involvedSwitches.findAll { !it.description.contains("OF_12") }.each { sw -> + def involvedSwitchValidateInfo = sw.validate() + if (sw.switchId == switchPair.src.dpId) { assert involvedSwitchValidateInfo.rules.proper*.cookie.sort() == createdCookiesSrcSw.sort() - } else if (switchId == switchPair.dst.dpId) { + } else if (sw.switchId == switchPair.dst.dpId) { assert involvedSwitchValidateInfo.rules.proper*.cookie.sort() == createdCookiesDstSw.sort() } else { assert involvedSwitchValidateInfo.rules.proper*.cookie.sort() == createdCookiesTransitSwitch.sort() } - involvedSwitchValidateInfo.verifyRuleSectionsAreEmpty(["missing"]) + verifyRuleSectionsAreEmpty(involvedSwitchValidateInfo, ["missing"]) assert involvedSwitchValidateInfo.rules.excess.size() == 1 assert involvedSwitchValidateInfo.rules.excess.cookie == [1L] } } and: "Excess meter is shown on the srcSwitch only" Long burstSize = flow.maximumBandwidth - def validateSwitchInfo = switchHelper.validate(switchPair.src.dpId) + def validateSwitchInfo = srcSwitch.validate() assert validateSwitchInfo.meters.excess.size() == 1 - assert validateSwitchInfo.meters.excess.each { + validateSwitchInfo.meters.excess.each { assert it.meterId == excessMeterId assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) - switchHelper.verifyRateSizeIsCorrect(switchPair.src, flow.maximumBandwidth, it.rate) - switchHelper.verifyBurstSizeIsCorrect(switchPair.src, burstSize, it.burstSize) + srcSwitch.verifyRateSizeIsCorrect(flow.maximumBandwidth, it.rate) + dstSwitch.verifyBurstSizeIsCorrect(burstSize, it.burstSize) } - involvedSwitches[1..-1].findAll { !it.description.contains("OF_12") }.each { switchId -> - assert switchHelper.validate(switchId).meters.excess.empty + involvedSwitches[1..-1].findAll { !it.description.contains("OF_12") }.each { sw -> + assert sw.validate().meters.excess.empty } when: "Try to synchronize every involved switch" - def syncResultsMap = involvedSwitches.collectEntries { switchId -> - [switchId, northbound.synchronizeSwitch(switchId, true)] - } then: "System deletes excess rules and meters" - involvedSwitches.findAll { !it.description.contains("OF_12") }.each { switchId -> - assert syncResultsMap[switchId].rules.excess.size() == 1 - assert syncResultsMap[switchId].rules.excess[0] == 1L - assert syncResultsMap[switchId].rules.removed.size() == 1 - assert syncResultsMap[switchId].rules.removed[0] == 1L + involvedSwitches.each { sw -> + def syncResponse = sw.synchronize() + if( !sw.description.contains("OF_12")){ + assert syncResponse.rules.excess.size() == 1 + assert syncResponse.rules.excess[0] == 1L + assert syncResponse.rules.removed.size() == 1 + assert syncResponse.rules.removed[0] == 1L + } + + if(sw == srcSwitch){ + assert syncResponse.meters.excess.size() == 1 + assert syncResponse.meters.excess.meterId[0] == excessMeterId + assert syncResponse.meters.removed.size() == 1 + assert syncResponse.meters.removed.meterId[0] == excessMeterId + } } - assert syncResultsMap[switchPair.src.dpId].meters.excess.size() == 1 - assert syncResultsMap[switchPair.src.dpId].meters.excess.meterId[0] == excessMeterId - assert syncResultsMap[switchPair.src.dpId].meters.removed.size() == 1 - assert syncResultsMap[switchPair.src.dpId].meters.removed.meterId[0] == excessMeterId when: "Delete the flow" flow.delete() then: "Check that the switch validate request returns empty sections on all involved switches" Wrappers.wait(WAIT_OFFSET) { - involvedSwitches.findAll { !it.description.contains("OF_12") }.each { switchId -> - def switchValidateInfo = switchHelper.validate(switchId) - switchValidateInfo.verifyRuleSectionsAreEmpty() - switchValidateInfo.verifyMeterSectionsAreEmpty() + involvedSwitches.findAll { !it.description.contains("OF_12") }.each { sw -> + def switchValidateInfo = sw.validate() + verifyRuleSectionsAreEmpty(switchValidateInfo) + verifyMeterSectionsAreEmpty(switchValidateInfo) } } @@ -701,72 +699,66 @@ misconfigured" and: "Remove required rules and meters from switches" def flowInfoFromDb = flow.retrieveDetailsFromDB() - List involvedSwitches = flow.retrieveAllEntityPaths().getInvolvedIsls() - .collect { [it.srcSwitch, it.dstSwitch] }.flatten().unique() as List - def transitSwitchIds = involvedSwitches[1..-2]*.dpId + def involvedSwitches = flow.retrieveAllEntityPaths().getInvolvedSwitches() + .collect { switches.all().findSpecific(it) } + def transitSwitch = involvedSwitches[1..-2] + def terminalSwitches = involvedSwitches.findAll { it.switchId in switchPair.toList().dpId } def cookiesMap = involvedSwitches.collectEntries { sw -> - def defaultCookies = sw.defaultCookies - [sw.dpId, switchRulesFactory.get(sw.dpId).getRules().findAll { + def defaultCookies = sw.collectDefaultCookies() + [sw.switchId, sw.rulesManager.getRules().findAll { !(it.cookie in defaultCookies) && !new Cookie(it.cookie).serviceFlag }*.cookie] } def metersMap = involvedSwitches.collectEntries { sw -> - [sw.dpId, northbound.getAllMeters(sw.dpId).meterEntries.findAll { - it.meterId > MAX_SYSTEM_RULE_METER_ID - }*.meterId] + [sw.switchId, sw.metersManager.getCreatedMeterIds()] } expect: "Switch validation shows missing rules and meters on every related switch" - involvedSwitches.each { switchHelper.deleteSwitchRules(it.dpId, DeleteRulesAction.IGNORE_DEFAULTS) } - [switchPair.src, switchPair.dst].each { northbound.deleteMeter(it.dpId, metersMap[it.dpId][0]) } + involvedSwitches.each { sw -> sw.rulesManager.delete(DeleteRulesAction.IGNORE_DEFAULTS) } + terminalSwitches.each { sw -> sw.metersManager.delete(metersMap[sw.switchId][0])} + Wrappers.wait(RULES_DELETION_TIME) { - def validationResultsMap = involvedSwitches.collectEntries { [it.dpId, switchHelper.validate(it.dpId)] } - involvedSwitches.each { - def swProps = northbound.getSwitchProperties(it.dpId) - def switchIdInSrcOrDst = (it.dpId in [switchPair.src.dpId, switchPair.dst.dpId]) - def defaultAmountOfFlowRules = 2 // ingress + egress - def amountOfServer42Rules = (switchIdInSrcOrDst && swProps.server42FlowRtt ? 1 : 0) - if (swProps.server42FlowRtt) { - if ((flow.destination.getSwitchId() == it.dpId && flow.destination.vlanId) || ( - flow.source.getSwitchId() == it.dpId && flow.source.vlanId)) - amountOfServer42Rules += 1 + involvedSwitches.each { sw -> + def validateResponse = sw.validate() + def rulesCount = sw.collectFlowRelatedRulesAmount(flow) + + assert validateResponse.rules.missing.size() == rulesCount + assert validateResponse.rules.missing.cookieHex.size() == rulesCount + + if(sw in terminalSwitches) { + assert validateResponse.meters.missing.size() == 1 } - def rulesCount = defaultAmountOfFlowRules + amountOfServer42Rules + - (switchIdInSrcOrDst ? 1 : 0) - assert validationResultsMap[it.dpId].rules.missing.size() == rulesCount - assert validationResultsMap[it.dpId].rules.missing.cookieHex.size() == rulesCount } - [switchPair.src, switchPair.dst].each { assert validationResultsMap[it.dpId].meters.missing.size() == 1 } } when: "Try to synchronize all switches" - def syncResultsMap = involvedSwitches.collectEntries { [it.dpId, northbound.synchronizeSwitch(it.dpId, false)] } - then: "System installs missing rules and meters" - involvedSwitches.each { - assert syncResultsMap[it.dpId].rules.proper.findAll { !new Cookie(it).serviceFlag }.size() == 0 - assert syncResultsMap[it.dpId].rules.excess.size() == 0 - assert syncResultsMap[it.dpId].rules.missing.containsAll(cookiesMap[it.dpId]) - assert syncResultsMap[it.dpId].rules.removed.size() == 0 - assert syncResultsMap[it.dpId].rules.installed.containsAll(cookiesMap[it.dpId]) - } - [switchPair.src, switchPair.dst].each { - assert syncResultsMap[it.dpId].meters.proper.findAll { !it.defaultMeter }.size() == 0 - assert syncResultsMap[it.dpId].meters.excess.size() == 0 - assert syncResultsMap[it.dpId].meters.missing*.meterId == metersMap[it.dpId] - assert syncResultsMap[it.dpId].meters.removed.size() == 0 - assert syncResultsMap[it.dpId].meters.installed*.meterId == metersMap[it.dpId] + involvedSwitches.each { sw -> + def syncResponse = sw.synchronize() + assert syncResponse.rules.proper.findAll { !new Cookie(it).serviceFlag }.size() == 0 + assert syncResponse.rules.excess.size() == 0 + assert syncResponse.rules.missing.containsAll(cookiesMap[sw.switchId]) + assert syncResponse.rules.removed.size() == 0 + assert syncResponse.rules.installed.containsAll(cookiesMap[sw.switchId]) + if(sw in terminalSwitches){ + assert syncResponse.meters.proper.findAll { !it.defaultMeter }.size() == 0 + assert syncResponse.meters.excess.size() == 0 + assert syncResponse.meters.missing*.meterId == metersMap[sw.switchId] + assert syncResponse.meters.removed.size() == 0 + assert syncResponse.meters.installed*.meterId == metersMap[sw.switchId] + } } + and: "Switch validation doesn't complain about missing rules and meters" Wrappers.wait(RULES_INSTALLATION_TIME) { - switchHelper.validateAndCollectFoundDiscrepancies(involvedSwitches*.getDpId()).isEmpty() + involvedSwitches.each { !it.validateAndCollectFoundDiscrepancies().isPresent() } } and: "Rules are synced correctly" // ingressRule should contain "pushVxlan" // egressRule should contain "tunnel-id" - with(switchRulesFactory.get(switchPair.src.dpId).getRules()) { rules -> + with(terminalSwitches.find { it.switchId in switchPair.src.dpId }.rulesManager.getRules()) { rules -> assert rules.find { it.cookie == flowInfoFromDb.forwardPath.cookie.value }.instructions.applyActions.pushVxlan @@ -774,7 +766,7 @@ misconfigured" it.cookie == flowInfoFromDb.reversePath.cookie.value }.match.tunnelId } - with(switchRulesFactory.get(switchPair.dst.dpId).getRules()) { rules -> + with(terminalSwitches.find { it.switchId in switchPair.dst.dpId }.rulesManager.getRules()) { rules -> assert rules.find { it.cookie == flowInfoFromDb.forwardPath.cookie.value }.match.tunnelId @@ -782,8 +774,8 @@ misconfigured" it.cookie == flowInfoFromDb.reversePath.cookie.value }.instructions.applyActions.pushVxlan } - transitSwitchIds.each { swId -> - with(switchRulesFactory.get(swId).getRules()) { rules -> + transitSwitch.each { sw -> + with(sw.rulesManager.getRules()) { rules -> assert rules.find { it.cookie == flowInfoFromDb.forwardPath.cookie.value }.match.tunnelId @@ -802,49 +794,49 @@ misconfigured" .create() def flowPathInfo = flow.retrieveAllEntityPaths() - def allSwitches = flowPathInfo.getInvolvedSwitches() + def allSwitches = flowPathInfo.getInvolvedSwitches().collect { switches.all().findSpecific(it) } + def srcSwitch = allSwitches.find { it.switchId == swPair.src.dpId } def rulesPerSwitch = allSwitches.collectEntries { - [it, switchRulesFactory.get(it).getRules().cookie.sort()] + [it.switchId, it.rulesManager.getRules().cookie.sort()] } expect: "Upon validation all rules are stored in the 'proper' section" - allSwitches.each { switchId -> - def rules = northbound.validateSwitchRules(switchId) - assert rules.properRules.sort() == rulesPerSwitch[switchId] + allSwitches.each { sw -> + def rules = sw.rulesManager.validate() + assert rules.properRules.sort() == rulesPerSwitch[sw.switchId] assert rules.missingRules.empty assert rules.excessRules.empty } when: "Delete rule of protected path on the srcSwitch (egress)" def protectedPath = flowPathInfo.flowPath.protectedPath.forward.nodes.nodes - def srcSwitchRules = northbound.getSwitchRules(swPair.src.dpId).flowEntries.findAll { - !new Cookie(it.cookie).serviceFlag - } + def srcSwitchRules = srcSwitch.rulesManager.getRules().findAll { !new Cookie(it.cookie).serviceFlag } def ruleToDelete = srcSwitchRules.find { //specifying protectedPath[0](src.inputPort) and protectedPath[1](src.outputPort) as protected path for FORWARD direction is used it.instructions?.applyActions?.flowOutput == protectedPath[0].portNo.toString() && it.match.inPort == protectedPath[1].portNo.toString() }.cookie - switchHelper.deleteSwitchRules(swPair.src.dpId, ruleToDelete) + srcSwitch.rulesManager.delete(ruleToDelete) + then: "Deleted rule is moved to the 'missing' section on the srcSwitch" - verifyAll(switchHelper.validate(swPair.src.dpId)) { - it.rules.proper*.cookie.sort() == rulesPerSwitch[swPair.src.dpId] - ruleToDelete + verifyAll(srcSwitch.validate()) { + it.rules.proper*.cookie.sort() == (rulesPerSwitch[swPair.src.dpId] - ruleToDelete).sort() it.rules.missing*.cookie == [ruleToDelete] it.rules.excess.empty } and: "Rest switches are not affected by deleting the rule on the srcSwitch" - allSwitches.findAll { it != swPair.src.dpId }.each { switchId -> - def validation = switchHelper.validate(switchId) - assert validation.rules.proper*.cookie.sort() == rulesPerSwitch[switchId] + allSwitches.findAll { it.switchId != srcSwitch.switchId }.each { sw -> + def validation = sw.validate() + assert validation.rules.proper*.cookie.sort() == rulesPerSwitch[sw.switchId] assert validation.rules.missing.empty assert validation.rules.excess.empty } when: "Synchronize switch with a missing protected path egress rule" - with(northbound.synchronizeSwitch(swPair.src.dpId, false)) { + with(srcSwitch.synchronize(false)) { rules.installed == [ruleToDelete] } then: "Switch validation no longer shows missing rules" - verifyAll(switchHelper.validate(swPair.src.dpId)) { - it.verifyRuleSectionsAreEmpty(["missing", "excess"]) - it.rules.proper*.cookie.sort() == rulesPerSwitch[swPair.src.dpId] + verifyAll(srcSwitch.validate()) { + verifyRuleSectionsAreEmpty(it, ["missing", "excess"]) + it.rules.proper*.cookie.sort() == rulesPerSwitch[srcSwitch.switchId] } } @@ -856,28 +848,29 @@ misconfigured" .create() expect: "Switch validation puts connected device lldp rule into 'proper' section" - def deviceCookie = switchRulesFactory.get(swPair.dst.dpId).getRules().find(data.cookieSearchClosure).cookie - with(switchHelper.validate(flow.destination.switchId)) { + def dstSwitch = switches.all().findSpecific(swPair.dst.dpId) + def deviceCookie = dstSwitch.rulesManager.getRules().find(data.cookieSearchClosure).cookie + with(dstSwitch.validate()) { it.rules.proper*.cookie.contains(deviceCookie) } when: "Remove the connected device rule" - switchHelper.deleteSwitchRules(flow.destination.switchId, deviceCookie) + dstSwitch.rulesManager.delete(deviceCookie) then: "Switch validation puts connected device rule into 'missing' section" - verifyAll(switchHelper.validate(flow.destination.switchId)) { + verifyAll(dstSwitch.validate()) { !it.rules.proper*.cookie.contains(deviceCookie) it.rules.missing*.cookie.contains(deviceCookie) it.rules.missing*.cookieHex.contains(Long.toHexString(deviceCookie).toUpperCase()) } when: "Synchronize the switch" - with(switchHelper.synchronize(flow.destination.switchId, false)) { + with(dstSwitch.synchronize(false)) { it.rules.installed == [deviceCookie] } then: "Switch validation no longer shows any discrepancies in rules nor meters" - verifyAll(switchHelper.validate(flow.destination.switchId)) { + verifyAll(dstSwitch.validate()) { it.rules.proper*.cookie.contains(deviceCookie) it.rules.missing.empty it.rules.excess.empty @@ -889,9 +882,9 @@ misconfigured" flow.delete() then: "Switch validation is empty" - verifyAll(switchHelper.validate(flow.destination.switchId)) { - it.verifyRuleSectionsAreEmpty() - it.verifyMeterSectionsAreEmpty() + verifyAll(dstSwitch.validate()) { + verifyRuleSectionsAreEmpty(it) + verifyMeterSectionsAreEmpty(it) } where: @@ -913,28 +906,29 @@ misconfigured" def "Able to filter results using request query, and asExpected field verification"() { given: "Create a flow" - def (Switch srcSwitch, Switch dstSwitch) = topology.activeSwitches.findAll { it.ofVersion != "OF_12" } + def (SwitchExtended srcSwitch, SwitchExtended dstSwitch) = switches.all().notOF12Version() def flow = flowFactory.getRandom(srcSwitch, dstSwitch) when: "Perform result filtering" - def srcSwitchValidateInfo = switchHelper.validate(srcSwitch.dpId, include, null) - def dstSwitchValidateInfo = switchHelper.validate(dstSwitch.dpId) + def srcSwitchValidateInfo = srcSwitch.validate(include, null) + def dstSwitchValidateInfo = dstSwitch.validate() and: "Result contains only included sections" - srcSwitchValidateInfo.verifySectionInSwitchValidationInfo(sectionsToVerifyPresence) - srcSwitchValidateInfo.verifySectionsAsExpectedFields(sectionsToVerifyPresence) - dstSwitchValidateInfo.verifySectionInSwitchValidationInfo(sectionsToVerifyPresence) - dstSwitchValidateInfo.verifySectionsAsExpectedFields(sectionsToVerifyPresence) + verifySectionInSwitchValidationInfo(srcSwitchValidateInfo, sectionsToVerifyPresence) + verifySectionsAsExpectedFields(srcSwitchValidateInfo, sectionsToVerifyPresence) + + verifySectionInSwitchValidationInfo(dstSwitchValidateInfo, sectionsToVerifyPresence) + verifySectionsAsExpectedFields(dstSwitchValidateInfo, sectionsToVerifyPresence) then: "Delete the flow" flow.delete() and: "Check that the switch validate request returns empty sections" Wrappers.wait(WAIT_OFFSET) { - def srcSwitchValidateInfoAfterDelete = switchHelper.validate(srcSwitch.dpId) - def dstSwitchValidateInfoAfterDelete = switchHelper.validate(dstSwitch.dpId) - srcSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() - dstSwitchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() + def srcSwitchValidateInfoAfterDelete = srcSwitch.validate() + verifyRuleSectionsAreEmpty(srcSwitchValidateInfoAfterDelete) + def dstSwitchValidateInfoAfterDelete = dstSwitch.validate() + verifyRuleSectionsAreEmpty(dstSwitchValidateInfoAfterDelete) } where: @@ -945,9 +939,7 @@ misconfigured" @Tags([VIRTUAL, LOW_PRIORITY]) def "Able to validate switch using #apiVersion API when GRPC is down"() { given: "Random switch without LAG feature enabled" - def aSwitch = topology.getSwitches().find { - !database.getSwitch(it.getDpId()).getFeatures().contains(SwitchFeature.LAG) - } + def aSwitch = switches.all().withoutLagSupport().random() and: "GRPC container is down" def dockerHelper = new DockerHelper(dockerHost) @@ -955,12 +947,11 @@ misconfigured" dockerHelper.pauseContainer(grpcContainerId) and: "Switch has a new feature artificially added directly to DB" - def originalFeatures = database.getSwitch(aSwitch.getDpId()).getFeatures() as Set - def newFeatures = originalFeatures + SwitchFeature.LAG - database.setSwitchFeatures(aSwitch.getDpId(), newFeatures) + def originalFeatures = aSwitch.getDbFeatures() as Set + aSwitch.setFeaturesInDb(originalFeatures + SwitchFeature.LAG) when: "Validate switch" - def validationResult = validate(aSwitch.getDpId()) + def validationResult = validate(aSwitch) then: "Validation is successful" validationResult.getLogicalPorts().getError() == @@ -968,21 +959,11 @@ misconfigured" cleanup: dockerHelper.resumeContainer(grpcContainerId) - database.setSwitchFeatures(aSwitch.getDpId(), originalFeatures) where: apiVersion | validate - "V2" | {switchHelper.validate(it)} - "V1" | {switchHelper.validateV1(it)} + "V2" | { it.validate() } + "V1" | { it.validateV1() } } - List getCreatedMeterIds(SwitchId switchId) { - return northbound.getAllMeters(switchId).meterEntries.findAll { it.meterId > MAX_SYSTEM_RULE_METER_ID }*.meterId - } - - List getCookiesWithMeter(SwitchId switchId) { - return switchRulesFactory.get(switchId).getRules().findAll { - !new Cookie(it.cookie).serviceFlag && it.instructions.goToMeter - }*.cookie.sort() - } }