From 44345ce2ebffb401037c994634a80cfbe6ef2848 Mon Sep 17 00:00:00 2001 From: Yuliia Miroshnychenko Date: Tue, 3 Dec 2024 16:27:16 +0100 Subject: [PATCH] [TEST]: Improvement: Switch: New interaction approach --- .../helpers/KildaProperties.groovy | 37 + .../helpers/factory/FlowFactory.groovy | 19 + .../helpers/factory/SwitchFactory.groovy | 48 + .../helpers/model/LagPort.groovy | 70 ++ .../helpers/model/PortExtended.groovy | 130 +++ .../helpers/model/SwitchExtended.groovy | 859 ++++++++++++++++++ .../helpers/model/SwitchMeters.groovy | 21 +- .../helpers/model/SwitchPair.groovy | 4 + .../helpers/model/SwitchPairs.groovy | 5 + .../helpers/model/SwitchRules.groovy | 66 +- .../helpers/model/Switches.groovy | 106 +++ .../model/switches/Manufacturer.groovy | 5 +- .../functionaltests/BaseSpecification.groovy | 6 + .../spec/grpc/GrpcCommonSpec.groovy | 6 +- .../spec/server42/Server42FlowRttSpec.groovy | 2 +- .../spec/server42/Server42IslRttSpec.groovy | 14 +- .../spec/switches/DefaultRulesSpec.groovy | 224 +++-- .../DefaultRulesValidationSpec.groovy | 22 +- .../spec/switches/FlowRulesSpec.groovy | 285 +++--- .../spec/switches/LagPortSpec.groovy | 490 +++++----- .../spec/switches/MetersSpec.groovy | 344 +++---- .../SwitchValidationSingleSwFlowSpec.groovy | 337 +++---- .../spec/switches/SwitchesSpec.groovy | 86 +- 23 files changed, 2152 insertions(+), 1034 deletions(-) create mode 100644 src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/KildaProperties.groovy create mode 100644 src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/factory/SwitchFactory.groovy create mode 100644 src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/LagPort.groovy create mode 100644 src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/PortExtended.groovy create mode 100644 src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchExtended.groovy create mode 100644 src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/Switches.groovy diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/KildaProperties.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/KildaProperties.groovy new file mode 100644 index 00000000000..4f860c9a14e --- /dev/null +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/KildaProperties.groovy @@ -0,0 +1,37 @@ +package org.openkilda.functionaltests.helpers + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Component + +@Component +class KildaProperties { + + public static int DISCOVERY_EXHAUSTED_INTERVAL + public static int ANTIFLAP_MIN + public static int ANTIFLAP_COOLDOWN + public static int DISCOVERY_TIMEOUT + public static double BURST_COEFFICIENT + public static String TOPO_DISCO_TOPIC + public static Properties PRODUCER_PROPS + + @Autowired + KildaProperties( @Value('${discovery.exhausted.interval}') int discoveryExhaustedInterval, + @Value('${antiflap.min}') int antiflapMin, + @Value('${antiflap.cooldown}') int antiflapCooldown, + @Value('${discovery.timeout}') int discoveryTimeout, + @Value('${burst.coefficient}') double burstCoefficient, + @Autowired @Qualifier("kafkaProducerProperties") Properties producerProps, + @Value("#{kafkaTopicsConfig.getTopoDiscoTopic()}") String topoDiscoTopic) { + + DISCOVERY_EXHAUSTED_INTERVAL = discoveryExhaustedInterval + ANTIFLAP_MIN = antiflapMin + ANTIFLAP_COOLDOWN = antiflapCooldown + DISCOVERY_TIMEOUT = discoveryTimeout + BURST_COEFFICIENT = burstCoefficient + TOPO_DISCO_TOPIC = topoDiscoTopic + PRODUCER_PROPS = producerProps + + } +} diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/factory/FlowFactory.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/factory/FlowFactory.groovy index b73ed504322..96b21adc2f8 100644 --- a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/factory/FlowFactory.groovy +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/factory/FlowFactory.groovy @@ -7,6 +7,7 @@ import static org.springframework.beans.factory.config.ConfigurableBeanFactory.S import org.openkilda.functionaltests.helpers.builder.FlowBuilder import org.openkilda.functionaltests.helpers.model.FlowExtended +import org.openkilda.functionaltests.helpers.model.SwitchExtended import org.openkilda.functionaltests.helpers.model.SwitchPair import org.openkilda.functionaltests.helpers.model.SwitchPortVlan import org.openkilda.functionaltests.model.cleanup.CleanupManager @@ -52,6 +53,14 @@ class FlowFactory { return new FlowBuilder(srcSwitch, dstSwitch, northbound, northboundV2, topology, cleanupManager, database, useTraffgenPorts, busyEndpoints) } + FlowBuilder getBuilder(SwitchExtended srcSwitch, SwitchExtended dstSwitch, boolean useTraffgenPorts = true, List busyEndpoints = []) { + getBuilder(srcSwitch.sw, dstSwitch.sw, useTraffgenPorts, busyEndpoints) + } + + FlowBuilder getSingleSwBuilder(SwitchExtended srcSwitch, boolean useTraffgenPorts = true, List busyEndpoints = []) { + getBuilder(srcSwitch.sw, srcSwitch.sw, useTraffgenPorts, busyEndpoints) + } + /* This method allows random Flow creation on specified switches and waits for it to become UP by default or to be in an expected state. @@ -66,6 +75,16 @@ class FlowFactory { return getBuilder(srcSwitch, dstSwitch, useTraffgenPorts, busyEndpoints).build().create(expectedFlowState) } + FlowExtended getRandom(SwitchExtended srcSwitch, SwitchExtended dstSwitch, boolean useTraffgenPorts = true, FlowState expectedFlowState = UP, + List busyEndpoints = []) { + getRandom(srcSwitch.sw, dstSwitch.sw) + } + + FlowExtended getSingleSwRandom(SwitchExtended srcSwitch, boolean useTraffgenPorts = true, FlowState expectedFlowState = UP, + List busyEndpoints = []) { + getRandom(srcSwitch.sw, srcSwitch.sw) + } + FlowExtended getRandomV1(Switch srcSwitch, Switch dstSwitch, boolean useTraffgenPorts = true, FlowState expectedFlowState = UP, List busyEndpoints = []) { return getBuilder(srcSwitch, dstSwitch, useTraffgenPorts, busyEndpoints).build().createV1(expectedFlowState) diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/factory/SwitchFactory.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/factory/SwitchFactory.groovy new file mode 100644 index 00000000000..6fef02e82c7 --- /dev/null +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/factory/SwitchFactory.groovy @@ -0,0 +1,48 @@ +package org.openkilda.functionaltests.helpers.factory + +import static org.springframework.beans.factory.config.ConfigurableBeanFactory.SCOPE_PROTOTYPE + +import org.openkilda.functionaltests.helpers.model.SwitchExtended +import org.openkilda.functionaltests.model.cleanup.CleanupManager +import org.openkilda.testing.model.topology.TopologyDefinition +import org.openkilda.testing.model.topology.TopologyDefinition.Switch +import org.openkilda.testing.service.database.Database +import org.openkilda.testing.service.lockkeeper.LockKeeperService +import org.openkilda.testing.service.northbound.NorthboundService +import org.openkilda.testing.service.northbound.NorthboundServiceV2 + +import groovy.util.logging.Slf4j +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.context.annotation.Scope +import org.springframework.stereotype.Component + +@Slf4j +@Component +@Scope(SCOPE_PROTOTYPE) +class SwitchFactory { + + @Autowired @Qualifier("islandNb") + NorthboundService northbound + @Autowired @Qualifier("islandNbV2") + NorthboundServiceV2 northboundV2 + @Autowired + TopologyDefinition topology + @Autowired + Database database + @Autowired + LockKeeperService lockKeeper + @Autowired + CleanupManager cleanupManager + + SwitchExtended get(Switch sw) { + List islPorts = [] + topology.getRelatedIsls(sw).each { + it?.srcSwitch?.dpId != sw.dpId ?: islPorts.add(it.srcPort) + it?.dstSwitch?.dpId != sw.dpId ?: islPorts.add(it.dstPort) + } + List traffGen = topology.traffGens.findAll{ it.switchConnected.dpId == sw.dpId }.switchPort + return new SwitchExtended(sw, islPorts, traffGen, + northbound, northboundV2, database, lockKeeper, cleanupManager) + } +} diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/LagPort.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/LagPort.groovy new file mode 100644 index 00000000000..961e29bc8be --- /dev/null +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/LagPort.groovy @@ -0,0 +1,70 @@ +package org.openkilda.functionaltests.helpers.model + +import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.DELETE_LAG_LOGICAL_PORT + +import org.openkilda.functionaltests.model.cleanup.CleanupManager +import org.openkilda.model.SwitchId +import org.openkilda.northbound.dto.v2.switches.LagPortRequest +import org.openkilda.testing.service.northbound.NorthboundServiceV2 + +import com.fasterxml.jackson.annotation.JsonIgnore +import groovy.transform.EqualsAndHashCode +import groovy.transform.ToString +import groovy.util.logging.Slf4j + +@Slf4j +@EqualsAndHashCode(excludes = 'northboundV2, cleanupManager') +@ToString(includeNames = true, excludes = 'northboundV2, cleanupManager') +class LagPort { + + SwitchId switchId + Set portNumbers + int logicalPortNumber + boolean lacpReply + + @JsonIgnore + NorthboundServiceV2 northboundV2 + @JsonIgnore + CleanupManager cleanupManager + + LagPort(SwitchId switchId, + Set portNumbers, + NorthboundServiceV2 northboundV2, + CleanupManager cleanupManager) { + this.switchId = switchId + this.portNumbers = portNumbers + this.northboundV2 = northboundV2 + this.cleanupManager = cleanupManager + } + + LagPort(SwitchId switchId, + int logicalPortNumber, + List portNumbers, + boolean lacpReply, + NorthboundServiceV2 northboundV2, + CleanupManager cleanupManager) { + this.switchId = switchId + this.logicalPortNumber = logicalPortNumber + this.lacpReply = lacpReply + this.portNumbers = portNumbers + this.northboundV2 = northboundV2 + this.cleanupManager = cleanupManager + } + + + LagPort create(boolean lacpReply = null) { + def lagDetails = northboundV2.createLagLogicalPort(switchId, new LagPortRequest(portNumbers , lacpReply)) + cleanupManager.addAction(DELETE_LAG_LOGICAL_PORT, { northboundV2.deleteLagLogicalPort(switchId, lagDetails.logicalPortNumber) }) + new LagPort(switchId, lagDetails.logicalPortNumber, lagDetails.portNumbers, lagDetails.lacpReply, northboundV2, cleanupManager) + } + + LagPort update(LagPortRequest updateRequest) { + def lagDetails = northboundV2.updateLagLogicalPort(switchId, logicalPortNumber, updateRequest) + new LagPort(switchId, lagDetails.logicalPortNumber, lagDetails.portNumbers, lagDetails.lacpReply, northboundV2, cleanupManager) + } + + LagPort delete() { + def lagDetails = northboundV2.deleteLagLogicalPort(switchId, logicalPortNumber) + new LagPort(switchId, lagDetails.logicalPortNumber, lagDetails.portNumbers, lagDetails.lacpReply, northboundV2, cleanupManager) + } +} diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/PortExtended.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/PortExtended.groovy new file mode 100644 index 00000000000..139347cb980 --- /dev/null +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/PortExtended.groovy @@ -0,0 +1,130 @@ +package org.openkilda.functionaltests.helpers.model + +import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.PORT_UP +import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.RESTORE_ISL +import static org.openkilda.functionaltests.model.cleanup.CleanupAfter.TEST +import static org.openkilda.testing.Constants.WAIT_OFFSET + +import org.openkilda.functionaltests.helpers.KildaProperties +import org.openkilda.functionaltests.helpers.Wrappers +import org.openkilda.functionaltests.helpers.thread.PortBlinker +import org.openkilda.functionaltests.model.cleanup.CleanupAfter +import org.openkilda.functionaltests.model.cleanup.CleanupManager +import org.openkilda.messaging.info.switches.PortDescription +import org.openkilda.model.SwitchId +import org.openkilda.northbound.dto.v2.switches.PortPropertiesDto +import org.openkilda.testing.model.topology.TopologyDefinition.Switch +import org.openkilda.testing.service.northbound.NorthboundService +import org.openkilda.testing.service.northbound.NorthboundServiceV2 + +import com.fasterxml.jackson.annotation.JsonIgnore +import groovy.transform.EqualsAndHashCode +import groovy.transform.ToString +import groovy.util.logging.Slf4j + +@Slf4j +@EqualsAndHashCode(excludes = 'northbound, northboundV2, cleanupManager') +@ToString(includeNames = true, excludes = 'northbound, northboundV2, cleanupManager') +class PortExtended { + + Switch sw + Integer port + + @JsonIgnore + NorthboundService northbound + @JsonIgnore + NorthboundServiceV2 northboundV2 + @JsonIgnore + CleanupManager cleanupManager + + Map, Long> history = [:] + + PortExtended(Switch sw, + Integer portNumber, + NorthboundService northbound, + NorthboundServiceV2 northboundV2, + CleanupManager cleanupManager) { + this.sw = sw + this.port = portNumber + this.northbound = northbound + this.northboundV2 = northboundV2 + this.cleanupManager = cleanupManager + } + + def up() { + def swPort = new Tuple2(sw.dpId, port) + def lastEvent = history.get(swPort) + if (lastEvent) { + Wrappers.silent { //Don't fail hard on this check. In rare cases we may miss the history entry + waitForStabilization(lastEvent) + } + history.remove(swPort) + } + northbound.portUp(sw.dpId, port) + } + + def safeUp() { + if (northbound.getPort(sw.dpId, port).getState().first() != "LIVE") { + up() + } + Wrappers.wait(WAIT_OFFSET) { + assert northbound.getActiveLinks().findAll { + it.source.switchId == sw.dpId && it.source.portNo == port || + it.destination.switchId == sw.dpId && it.destination.portNo == port + }.size() == 2 + } + } + + def down(CleanupAfter cleanupAfter = TEST, boolean isNotInScopeOfIslBreak = true) { + if (isNotInScopeOfIslBreak) { + cleanupManager.addAction(PORT_UP, { safeUp() }, cleanupAfter) + } + def response = northbound.portDown(sw.dpId, port) + sleep(KildaProperties.ANTIFLAP_MIN * 1000) + history.put(new Tuple2(sw.dpId, port), System.currentTimeMillis()) + response + } + + /** + * Wait till the current port is in a stable state (deactivated antiflap) by analyzing its history. + */ + void waitForStabilization(Long since = 0) { + // '* 2' it takes more time on a hardware env for link via 'a-switch' + Wrappers.wait(KildaProperties.ANTIFLAP_COOLDOWN + WAIT_OFFSET * 2) { + def history = northboundV2.getPortHistory(sw.dpId, port, since, null) + + if (!history.empty) { + def antiflapEvents = history.collect { PortHistoryEvent.valueOf(it.event) }.findAll { + it in [PortHistoryEvent.ANTI_FLAP_ACTIVATED, PortHistoryEvent.ANTI_FLAP_DEACTIVATED] + } + + if (!antiflapEvents.empty) { + assert antiflapEvents.last() == PortHistoryEvent.ANTI_FLAP_DEACTIVATED + } else { + false + } + } else { + false + } + } + } + + def setDiscovery(boolean expectedStatus) { + if (!expectedStatus) { + cleanupManager.addAction(RESTORE_ISL, { setDiscovery(true) }) + } + return northboundV2.updatePortProperties(sw.dpId, port, new PortPropertiesDto(discoveryEnabled: expectedStatus)) + } + + PortBlinker getBlinker(long interval, Properties producerProps) { + new PortBlinker(KildaProperties.PRODUCER_PROPS, KildaProperties.TOPO_DISCO_TOPIC, sw, port, interval) + } + + static def closeBlinker(PortBlinker blinker) { + blinker?.isRunning() && blinker.stop(true) + } + + PortDescription retrieveDetails() { + northbound.getPort(sw.dpId, port) + } +} 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 new file mode 100644 index 00000000000..858cfe4c68a --- /dev/null +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchExtended.groovy @@ -0,0 +1,859 @@ +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.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.messaging.info.event.IslChangeType.DISCOVERED +import static org.openkilda.messaging.info.event.IslChangeType.FAILED +import static org.openkilda.messaging.info.event.SwitchChangeType.ACTIVATED +import static org.openkilda.messaging.info.event.SwitchChangeType.DEACTIVATED +import static org.openkilda.model.MeterId.LACP_REPLY_METER_ID +import static org.openkilda.model.SwitchFeature.KILDA_OVS_PUSH_POP_MATCH_VXLAN +import static org.openkilda.model.SwitchFeature.KILDA_OVS_SWAP_FIELD +import static org.openkilda.model.SwitchFeature.NOVIFLOW_COPY_FIELD +import static org.openkilda.model.SwitchFeature.NOVIFLOW_PUSH_POP_VXLAN +import static org.openkilda.model.SwitchFeature.NOVIFLOW_SWAP_ETH_SRC_ETH_DST +import static org.openkilda.model.cookie.Cookie.ARP_INGRESS_COOKIE +import static org.openkilda.model.cookie.Cookie.ARP_INPUT_PRE_DROP_COOKIE +import static org.openkilda.model.cookie.Cookie.ARP_POST_INGRESS_COOKIE +import static org.openkilda.model.cookie.Cookie.ARP_POST_INGRESS_ONE_SWITCH_COOKIE +import static org.openkilda.model.cookie.Cookie.ARP_POST_INGRESS_VXLAN_COOKIE +import static org.openkilda.model.cookie.Cookie.ARP_TRANSIT_COOKIE +import static org.openkilda.model.cookie.Cookie.CATCH_BFD_RULE_COOKIE +import static org.openkilda.model.cookie.Cookie.DROP_RULE_COOKIE +import static org.openkilda.model.cookie.Cookie.DROP_SLOW_PROTOCOLS_LOOP_COOKIE +import static org.openkilda.model.cookie.Cookie.DROP_VERIFICATION_LOOP_RULE_COOKIE +import static org.openkilda.model.cookie.Cookie.LLDP_INGRESS_COOKIE +import static org.openkilda.model.cookie.Cookie.LLDP_INPUT_PRE_DROP_COOKIE +import static org.openkilda.model.cookie.Cookie.LLDP_POST_INGRESS_COOKIE +import static org.openkilda.model.cookie.Cookie.LLDP_POST_INGRESS_ONE_SWITCH_COOKIE +import static org.openkilda.model.cookie.Cookie.LLDP_POST_INGRESS_VXLAN_COOKIE +import static org.openkilda.model.cookie.Cookie.LLDP_TRANSIT_COOKIE +import static org.openkilda.model.cookie.Cookie.MULTITABLE_EGRESS_PASS_THROUGH_COOKIE +import static org.openkilda.model.cookie.Cookie.MULTITABLE_INGRESS_DROP_COOKIE +import static org.openkilda.model.cookie.Cookie.MULTITABLE_POST_INGRESS_DROP_COOKIE +import static org.openkilda.model.cookie.Cookie.MULTITABLE_PRE_INGRESS_PASS_THROUGH_COOKIE +import static org.openkilda.model.cookie.Cookie.MULTITABLE_TRANSIT_DROP_COOKIE +import static org.openkilda.model.cookie.Cookie.ROUND_TRIP_LATENCY_RULE_COOKIE +import static org.openkilda.model.cookie.Cookie.SERVER_42_FLOW_RTT_OUTPUT_VLAN_COOKIE +import static org.openkilda.model.cookie.Cookie.SERVER_42_FLOW_RTT_OUTPUT_VXLAN_COOKIE +import static org.openkilda.model.cookie.Cookie.SERVER_42_FLOW_RTT_TURNING_COOKIE +import static org.openkilda.model.cookie.Cookie.SERVER_42_FLOW_RTT_VXLAN_TURNING_COOKIE +import static org.openkilda.model.cookie.Cookie.SERVER_42_ISL_RTT_OUTPUT_COOKIE +import static org.openkilda.model.cookie.Cookie.SERVER_42_ISL_RTT_TURNING_COOKIE +import static org.openkilda.model.cookie.Cookie.VERIFICATION_BROADCAST_RULE_COOKIE +import static org.openkilda.model.cookie.Cookie.VERIFICATION_UNICAST_RULE_COOKIE +import static org.openkilda.model.cookie.Cookie.VERIFICATION_UNICAST_VXLAN_RULE_COOKIE +import static org.openkilda.model.cookie.CookieBase.CookieType.SERVER_42_ISL_RTT_INPUT +import static org.openkilda.testing.Constants.RULES_INSTALLATION_TIME +import static org.openkilda.testing.Constants.WAIT_OFFSET + +import org.openkilda.functionaltests.helpers.KildaProperties +import org.openkilda.functionaltests.helpers.Wrappers +import org.openkilda.functionaltests.model.cleanup.CleanupAfter +import org.openkilda.functionaltests.model.cleanup.CleanupManager +import org.openkilda.messaging.info.event.IslChangeType +import org.openkilda.messaging.info.event.IslInfoData +import org.openkilda.messaging.payload.flow.FlowPayload +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.model.cookie.PortColourCookie +import org.openkilda.model.cookie.ServiceCookie +import org.openkilda.model.cookie.ServiceCookie.ServiceCookieTag +import org.openkilda.northbound.dto.v1.switches.MeterInfoDto +import org.openkilda.northbound.dto.v1.switches.SwitchDto +import org.openkilda.northbound.dto.v1.switches.SwitchPropertiesDto +import org.openkilda.northbound.dto.v1.switches.SwitchSyncResult +import org.openkilda.northbound.dto.v2.switches.LagPortResponse +import org.openkilda.northbound.dto.v2.switches.MeterInfoDtoV2 +import org.openkilda.northbound.dto.v2.switches.SwitchFlowsPerPortResponse +import org.openkilda.northbound.dto.v2.switches.SwitchLocationDtoV2 +import org.openkilda.northbound.dto.v2.switches.SwitchPatchDto +import org.openkilda.testing.model.topology.TopologyDefinition.Switch +import org.openkilda.testing.service.database.Database +import org.openkilda.testing.service.floodlight.model.Floodlight +import org.openkilda.testing.service.floodlight.model.FloodlightConnectMode +import org.openkilda.testing.service.lockkeeper.LockKeeperService +import org.openkilda.testing.service.lockkeeper.model.FloodlightResourceAddress +import org.openkilda.testing.service.northbound.NorthboundService +import org.openkilda.testing.service.northbound.NorthboundServiceV2 +import org.openkilda.testing.service.northbound.payloads.SwitchValidationExtendedResult +import org.openkilda.testing.service.northbound.payloads.SwitchValidationV2ExtendedResult +import org.openkilda.testing.tools.SoftAssertions + +import com.fasterxml.jackson.annotation.JsonIdentityInfo +import com.fasterxml.jackson.annotation.JsonIgnore +import com.fasterxml.jackson.annotation.ObjectIdGenerators.PropertyGenerator +import groovy.transform.EqualsAndHashCode +import groovy.transform.Memoized +import groovy.transform.builder.Builder +import groovy.util.logging.Slf4j + +import java.math.RoundingMode + +@Slf4j +@EqualsAndHashCode(excludes = 'northbound, northboundV2, database, lockKeeper, cleanupManager') +@Builder +@JsonIdentityInfo(property = "name", generator = PropertyGenerator.class) +class SwitchExtended { + + //below values are manufacturer-specific and override default Kilda values on firmware level + static NOVIFLOW_BURST_COEFFICIENT = 1.005 // Driven by the Noviflow specification + static CENTEC_MIN_BURST = 1024 // Driven by the Centec specification + static CENTEC_MAX_BURST = 32000 // Driven by the Centec specification + + //Kilda allows user to pass reserved VLAN IDs 1 and 4095 if they want. + static final IntRange KILDA_ALLOWED_VLANS = 1..4095 + static final String LACP_COOKIE = Cookie.toString(DROP_SLOW_PROTOCOLS_LOOP_COOKIE) + + + Switch sw + List islPorts + List traffGenPorts + SwitchDto nbDetails + + @JsonIgnore + NorthboundService northbound + @JsonIgnore + NorthboundServiceV2 northboundV2 + @JsonIgnore + Database database + @JsonIgnore + LockKeeperService lockKeeper + @JsonIgnore + CleanupManager cleanupManager + + SwitchExtended(Switch sw, + List islPorts, + List traffGenPorts, + NorthboundService northbound, + NorthboundServiceV2 northboundV2, + Database database, + LockKeeperService lockKeeper, + CleanupManager cleanupManager) { + this.sw = sw + + this.islPorts = islPorts + this.traffGenPorts = traffGenPorts + this.northbound = northbound + this.northboundV2 = northboundV2 + this.database = database + this.lockKeeper = lockKeeper + this.cleanupManager = cleanupManager + } + + @Override + String toString() { + return String.format("Switch: %s, islPorts: %s, traffGen(s) port(s) %s, nbDetails: %s", + switchId, islPorts, traffGenPorts, nbDetails) + } + + @JsonIgnore + @Memoized + SwitchRules getRulesManager() { + return new SwitchRules(northbound, database, cleanupManager, sw.dpId) + } + + @JsonIgnore + @Memoized + SwitchMeters getMetersManager() { + return new SwitchMeters(northbound, database, sw.dpId) + } + + SwitchId getSwitchId() { + sw.dpId + } + + String getOfVersion() { + sw.ofVersion + } + + /** + * + * Get list of switch ports excluding the ports which are busy with ISLs or s42. + */ + @Memoized + List getPorts() { + List allPorts = sw.getAllPorts() + allPorts.removeAll(islPorts) + allPorts.removeAll([sw?.prop?.server42Port]) + allPorts.unique() + } + /*** + * + * @param useTraffgenPorts allows us to select random TraffGen port for further traffic verification + * @return random port for further interaction + */ + PortExtended getRandomPort(boolean useTraffgenPorts = true) { + List allPorts = useTraffgenPorts ? traffGenPorts : getPorts() + Integer portNo = allPorts.shuffled().first() + return new PortExtended(sw, portNo, northbound, northboundV2, cleanupManager) + } + + LagPort getLagPort(Set portNumbers) { + new LagPort(switchId, portNumbers, northboundV2, cleanupManager) + } + + @Memoized + PortExtended getPort(Integer portNo) { + return new PortExtended(sw, portNo, northbound, northboundV2, cleanupManager) + } + + String getDescription() { + nbFormat().description + } + + SwitchDto nbFormat() { + if (!nbDetails) { + nbDetails = northbound.getSwitch(sw.dpId) + } + return nbDetails + } + + @Memoized + boolean isVxlanEnabled() { + return getProps().supportedTransitEncapsulation + .contains(org.openkilda.model.FlowEncapsulationType.VXLAN.toString().toLowerCase()) + } + + String hwSwString() { + "${nbFormat().hardware} ${nbFormat().software}" + } + + boolean isCentec() { + nbFormat().manufacturer.toLowerCase().contains("centec") + } + + boolean isNoviflow() { + nbFormat().manufacturer.toLowerCase().contains("noviflow") + } + + boolean isVirtual() { + nbFormat().manufacturer.toLowerCase().contains("nicira") + } + + /** + * A hardware with 100GB ports. Due to its nature sometimes requires special actions from Kilda + */ + boolean isWb5164() { + nbFormat().hardware =~ "WB5164" + } + + static boolean isS42Supported(SwitchPropertiesDto swProps) { + swProps?.server42Port != null && swProps?.server42MacAddress != null && swProps?.server42Vlan != null + } + + List collectDefaultCookies() { + def swProps = northbound.getSwitchProperties(sw.dpId) + def multiTableRules = [] + def devicesRules = [] + def server42Rules = [] + def vxlanRules = [] + def lacpRules = [] + def toggles = northbound.getFeatureToggles() + multiTableRules = [MULTITABLE_PRE_INGRESS_PASS_THROUGH_COOKIE, MULTITABLE_INGRESS_DROP_COOKIE, + MULTITABLE_POST_INGRESS_DROP_COOKIE, MULTITABLE_EGRESS_PASS_THROUGH_COOKIE, + MULTITABLE_TRANSIT_DROP_COOKIE, LLDP_POST_INGRESS_COOKIE, LLDP_POST_INGRESS_ONE_SWITCH_COOKIE, + ARP_POST_INGRESS_COOKIE, ARP_POST_INGRESS_ONE_SWITCH_COOKIE] + def unifiedCookies = [DROP_RULE_COOKIE, VERIFICATION_BROADCAST_RULE_COOKIE, + VERIFICATION_UNICAST_RULE_COOKIE, DROP_VERIFICATION_LOOP_RULE_COOKIE] + + if (isVxlanFeatureEnabled()) { + multiTableRules.addAll([LLDP_POST_INGRESS_VXLAN_COOKIE, ARP_POST_INGRESS_VXLAN_COOKIE]) + vxlanRules << VERIFICATION_UNICAST_VXLAN_RULE_COOKIE + } + + if (swProps.switchLldp) { + devicesRules.addAll([LLDP_INPUT_PRE_DROP_COOKIE, LLDP_TRANSIT_COOKIE, LLDP_INGRESS_COOKIE]) + } + if (swProps.switchArp) { + devicesRules.addAll([ARP_INPUT_PRE_DROP_COOKIE, ARP_TRANSIT_COOKIE, ARP_INGRESS_COOKIE]) + } + + northbound.getSwitchFlows(sw.dpId).each { flow -> + [flow.source, flow.destination].findAll { ep -> ep.datapath == sw.dpId }.each { ep -> + multiTableRules.add(new PortColourCookie(CookieType.MULTI_TABLE_INGRESS_RULES, ep.portNumber).getValue()) + if (swProps.switchLldp || ep.detectConnectedDevices.lldp) { + devicesRules.add(new PortColourCookie(CookieType.LLDP_INPUT_CUSTOMER_TYPE, ep.portNumber).getValue()) + } + if (swProps.switchArp || ep.detectConnectedDevices.arp) { + devicesRules.add(new PortColourCookie(CookieType.ARP_INPUT_CUSTOMER_TYPE, ep.portNumber).getValue()) + } + } + } + + def relatedLinks= getRelatedLinks() + relatedLinks.each { + if (isVxlanFeatureEnabled()) { + multiTableRules.add(new PortColourCookie(CookieType.MULTI_TABLE_ISL_VXLAN_EGRESS_RULES, it.source.portNo).getValue()) + multiTableRules.add(new PortColourCookie(CookieType.MULTI_TABLE_ISL_VXLAN_TRANSIT_RULES, it.source.portNo).getValue()) + } + multiTableRules.add(new PortColourCookie(CookieType.MULTI_TABLE_ISL_VLAN_EGRESS_RULES, it.source.portNo).getValue()) + multiTableRules.add(new PortColourCookie(CookieType.PING_INPUT, it.source.portNo).getValue()) + } + + if ((toggles.server42IslRtt && isS42Supported(swProps) && (swProps.server42IslRtt == "ENABLED" || + swProps.server42IslRtt =="AUTO" && !sw.features.contains(NOVIFLOW_COPY_FIELD)))) { + devicesRules.add(SERVER_42_ISL_RTT_TURNING_COOKIE) + devicesRules.add(SERVER_42_ISL_RTT_OUTPUT_COOKIE) + relatedLinks.each { + devicesRules.add(new PortColourCookie(SERVER_42_ISL_RTT_INPUT, it.source.portNo).getValue()) + } + } + + if (swProps.server42FlowRtt) { + server42Rules << SERVER_42_FLOW_RTT_OUTPUT_VLAN_COOKIE + if (isVxlanFeatureEnabled()) { + server42Rules << SERVER_42_FLOW_RTT_OUTPUT_VXLAN_COOKIE + } + } + if (toggles.server42FlowRtt) { + if (getDbFeatures().contains(NOVIFLOW_SWAP_ETH_SRC_ETH_DST) || getDbFeatures().contains(KILDA_OVS_SWAP_FIELD)) { + server42Rules << SERVER_42_FLOW_RTT_TURNING_COOKIE + server42Rules << SERVER_42_FLOW_RTT_VXLAN_TURNING_COOKIE + } + } + + def lacpPorts = northboundV2.getLagLogicalPort(sw.dpId).findAll { it.lacpReply } + if (!lacpPorts.isEmpty()) { + lacpRules << new ServiceCookie(ServiceCookieTag.DROP_SLOW_PROTOCOLS_LOOP_COOKIE).getValue() + lacpPorts.each { + lacpRules << new PortColourCookie(CookieType.LACP_REPLY_INPUT, it.logicalPortNumber).getValue() + } + } + if (isNoviflow() && !isWb5164()) { + return ([CATCH_BFD_RULE_COOKIE, ROUND_TRIP_LATENCY_RULE_COOKIE] + + unifiedCookies + vxlanRules + multiTableRules + devicesRules + server42Rules + lacpRules) + } else if ((isNoviflow() || nbFormat().manufacturer == "E") && isWb5164()) { + return ([CATCH_BFD_RULE_COOKIE] + + unifiedCookies + vxlanRules + multiTableRules + devicesRules + server42Rules + lacpRules) + } else if (sw.ofVersion == "OF_12") { + return [VERIFICATION_BROADCAST_RULE_COOKIE] + } else { + return (unifiedCookies + vxlanRules + multiTableRules + devicesRules + server42Rules + lacpRules) + } + } + + List collectDefaultMeters() { + if (sw.ofVersion == "OF_12") { + return [] + } + def swProps = northbound.getSwitchProperties(sw.dpId) + List result = [] + result << MeterId.createMeterIdForDefaultRule(VERIFICATION_BROADCAST_RULE_COOKIE) //2 + result << MeterId.createMeterIdForDefaultRule(VERIFICATION_UNICAST_RULE_COOKIE) //3 + if (isVxlanFeatureEnabled()) { + result << MeterId.createMeterIdForDefaultRule(VERIFICATION_UNICAST_VXLAN_RULE_COOKIE) //7 + } + result << MeterId.createMeterIdForDefaultRule(LLDP_POST_INGRESS_COOKIE) //16 + result << MeterId.createMeterIdForDefaultRule(LLDP_POST_INGRESS_ONE_SWITCH_COOKIE) //18 + result << MeterId.createMeterIdForDefaultRule(ARP_POST_INGRESS_COOKIE) //22 + result << MeterId.createMeterIdForDefaultRule(ARP_POST_INGRESS_ONE_SWITCH_COOKIE) //24 + if (isVxlanFeatureEnabled()) { + result << MeterId.createMeterIdForDefaultRule(LLDP_POST_INGRESS_VXLAN_COOKIE) //17 + result << MeterId.createMeterIdForDefaultRule(ARP_POST_INGRESS_VXLAN_COOKIE) //23 + } + if (swProps.switchLldp) { + result << MeterId.createMeterIdForDefaultRule(LLDP_INPUT_PRE_DROP_COOKIE) //13 + result << MeterId.createMeterIdForDefaultRule(LLDP_TRANSIT_COOKIE) //14 + result << MeterId.createMeterIdForDefaultRule(LLDP_INGRESS_COOKIE) //15 + } + if (swProps.switchArp) { + result << MeterId.createMeterIdForDefaultRule(ARP_INPUT_PRE_DROP_COOKIE) //19 + result << MeterId.createMeterIdForDefaultRule(ARP_TRANSIT_COOKIE) //20 + result << MeterId.createMeterIdForDefaultRule(ARP_INGRESS_COOKIE) //21 + } + def lacpPorts = northboundV2.getLagLogicalPort(sw.dpId).findAll { + it.lacpReply + } + if (!lacpPorts.isEmpty()) { + result << MeterId.LACP_REPLY_METER_ID //31 + } + + return result*.getValue().sort() + } + + int collectFlowRelatedRulesAmount(FlowExtended flow) { + def swProps = getProps() + def isSwSrcOrDst = (sw.dpId in [flow.source.switchId, flow.destination.switchId]) + def defaultAmountOfFlowRules = 2 // ingress + egress + def amountOfServer42Rules = 0 + if(swProps.server42FlowRtt && isSwSrcOrDst) { + amountOfServer42Rules += 1 + sw.dpId == flow.source.switchId && flow.source.vlanId && ++amountOfServer42Rules + sw.dpId == flow.destination.switchId && flow.destination.vlanId && ++amountOfServer42Rules + } + + defaultAmountOfFlowRules + amountOfServer42Rules + (isSwSrcOrDst ? 1 : 0) + } + + + /** + * The same as direct northbound call, but additionally waits that default rules and default meters are indeed + * reinstalled according to config + */ + SwitchPropertiesDto updateProperties(SwitchPropertiesDto switchProperties) { + cleanupManager.addAction(OTHER, { northbound.updateSwitchProperties(sw.dpId, getCashedProps()) }) + def response = northbound.updateSwitchProperties(sw.dpId, switchProperties) + Wrappers.wait(RULES_INSTALLATION_TIME) { + def actualHexCookie = [] + for (long cookie : rulesManager.getRules().cookie) { + actualHexCookie.add(new Cookie(cookie).toString()) + } + def expectedHexCookie = [] + for (long cookie : collectDefaultCookies()) { + expectedHexCookie.add(new Cookie(cookie).toString()) + } + expectedHexCookie.forEach { item -> + assertThat sw.toString(), actualHexCookie, hasItem(item) + } + + + def actualDefaultMetersIds = metersManager.getMeters().meterId.findAll { + MeterId.isMeterIdOfDefaultRule((long) it) + } + assert actualDefaultMetersIds.sort() == collectDefaultMeters().sort() + } + return response + } + + SwitchPropertiesDto getProps() { + northboundV2.getAllSwitchProperties().switchProperties.find { it.switchId == sw.dpId } + } + + @Memoized + SwitchPropertiesDto getCashedProps() { + getProps() + } + + SwitchDto getDetails() { + northbound.getSwitch(sw.dpId) + } + + List getFlows() { + return northbound.getSwitchFlows(sw.dpId) + } + + List getFlows(Integer port) { + return northbound.getSwitchFlows(sw.dpId, port) + } + + SwitchFlowsPerPortResponse getFlowsV2(List portIds = []){ + return northboundV2.getSwitchFlows(new SwitchId(sw.dpId.id), portIds) + } + + List getUsedPorts() { + return northboundV2.getSwitchFlows(sw.dpId, []).flowsByPort.keySet().asList() + } + + SwitchValidationV2ExtendedResult validate(String include = null, String exclude = null) { + return northboundV2.validateSwitch(sw.dpId, include, exclude) + } + + SwitchValidationExtendedResult validateV1() { + return northbound.validateSwitch(sw.dpId) + } + + Optional validateAndCollectFoundDiscrepancies() { + SwitchValidationV2ExtendedResult validationResponse = northboundV2.validateSwitch(switchId) + return validationResponse.asExpected ? + Optional.empty() as Optional : Optional.of(validationResponse) + } + + SwitchSyncResult synchronize(boolean removeExcess = true) { + return northbound.synchronizeSwitch(sw.dpId, removeExcess) + } + + /** + * Synchronizes the switch and returns an optional SwitchSyncResult if the switch was in an unexpected state + * before the synchronization. + * @return optional SwitchSyncResult if the switch was in an unexpected state + * before the synchronization + */ + Optional synchronizeAndCollectFixedDiscrepancies() { + def syncResponse = synchronize(true) + boolean isAnyDiscrepancyFound = [syncResponse.rules.missing, + syncResponse.rules.misconfigured, + syncResponse.rules.excess, + syncResponse.meters.missing, + syncResponse.meters.misconfigured, + syncResponse.meters.excess].any { !it.isEmpty() } + return isAnyDiscrepancyFound ? Optional.of(syncResponse) : Optional.empty() as Optional + } + + + SwitchDto setMaintenance(boolean maintenance, boolean evacuate) { + cleanupManager.addAction(RESET_SWITCH_MAINTENANCE, { northbound.setSwitchMaintenance(sw.dpId, false, false) }) + northbound.setSwitchMaintenance(sw.dpId, maintenance, evacuate) + } + + def partialUpdate(SwitchPatchDto updateDto) { + def initialSettings = northbound.getSwitch(sw.dpId) + cleanupManager.addAction(RESTORE_SWITCH_PROPERTIES, { northboundV2.partialSwitchUpdate(sw.dpId, convertToUpdateRequest(initialSettings)) }) + return northboundV2.partialSwitchUpdate(sw.dpId, updateDto) + } + + List getAllLogicalPorts() { + northboundV2.getLagLogicalPort(switchId) + } + + List getRelatedLinks() { + northbound.getLinks(switchId, null, null, null) + } + + def delete(Boolean force = false) { + return northbound.deleteSwitch(sw.dpId, force) + } + + boolean isS42FlowRttEnabled() { + def swProps = northbound.getSwitchProperties(sw.dpId) + def featureToggles = northbound.getFeatureToggles() + swProps.server42FlowRtt && featureToggles.server42FlowRtt + } + + /*** + * Floodlight interaction + */ + + /** + * Waits for certain switch to appear/disappear from switch list in certain floodlights. + * Useful when knocking out switches + * + * @deprecated use 'northboundV2.getSwitchConnections(switchId)' instead + */ + @Deprecated + void waitForFlConnection(boolean shouldBePresent, List floodlights) { + Wrappers.wait(WAIT_OFFSET) { + withPool { + floodlights.eachParallel { + assert it.getFloodlightService().getSwitches()*.switchId.contains(sw.dpId) == shouldBePresent + } + } + } + } + + /** + * Disconnect a switch from controller either removing controller settings inside an OVS switch + * or blocking access to floodlight via iptables for a hardware switch. + * + * @param FL mode + * @param waitForRelatedLinks make sure that all switch related ISLs are FAILED + */ + List knockout(FloodlightConnectMode mode, boolean waitForRelatedLinks, double timeout = WAIT_OFFSET) { + def blockData = lockKeeper.knockoutSwitch(sw, mode) + cleanupManager.addAction(REVIVE_SWITCH, { revive(blockData, true) }, CleanupAfter.TEST) + Wrappers.wait(timeout) { + assert northbound.getSwitch(sw.dpId).state == DEACTIVATED + } + if (waitForRelatedLinks) { + Wrappers.wait(KildaProperties.DISCOVERY_TIMEOUT + timeout * 2) { + verifyRelatedLinksState(FAILED ) + } + } + + return blockData + } + + List knockout(FloodlightConnectMode mode) { + knockout(mode, false) + } + + List knockout(List regions) { + def blockData = lockKeeper.knockoutSwitch(sw, regions) + cleanupManager.addAction(REVIVE_SWITCH, { revive(blockData, true) }, CleanupAfter.TEST) + return blockData + } + + List knockoutFromStatsController(){ + def blockData = lockKeeper.knockoutSwitch(sw, FloodlightConnectMode.RO) + cleanupManager.addAction(REVIVE_SWITCH, { revive(blockData, true) }, CleanupAfter.TEST) + return blockData + } + + /** + * Connect a switch to controller either adding controller settings inside an OVS switch + * or setting proper iptables to allow access to floodlight for a hardware switch. + * + * @param flResourceAddress to register sw in the specific FL regions + * @param waitForRelatedLinks make sure that all switch related ISLs are DISCOVERED + */ + void revive(List flResourceAddress, boolean waitForRelatedLinks) { + lockKeeper.reviveSwitch(sw, flResourceAddress) + Wrappers.wait(WAIT_OFFSET) { + assert northbound.getSwitch(sw.dpId).state == ACTIVATED + } + if (waitForRelatedLinks) { + Wrappers.wait(KildaProperties.DISCOVERY_TIMEOUT + WAIT_OFFSET * 2) { + verifyRelatedLinksState(DISCOVERED) + } + } + } + + void revive(List flResourceAddress) { + revive(flResourceAddress, false) + } + + void verifyRelatedLinksState(IslChangeType expectedState) { + def relatedLinks = northbound.getAllLinks().findAll { + switchId in [it.source.switchId, it.destination.switchId] + } + assert relatedLinks.size() == islPorts.size() * 2 + + relatedLinks.each { isl -> assert isl.state == expectedState } + } + + void waitForS42FlowRttRulesSetup(boolean isS42ToggleOn = true) { + SwitchPropertiesDto switchDetails = getProps() + Wrappers.wait(RULES_INSTALLATION_TIME) { + def expectedRulesNumber = (isS42ToggleOn && switchDetails.server42FlowRtt) ? getExpectedS42RulesBasedOnVxlanSupport() : 0 + assert rulesManager.getServer42SwitchRelatedRules().size() == expectedRulesNumber + } + } + + int getExpectedS42RulesBasedOnVxlanSupport() { + //one rule per vlan/vxlan + isVxlanEnabled() ? 2 : 1 + } + + /** + * This method calculates expected burst for different types of switches. The common burst equals to + * `rate * BURST_COEFFICIENT`. 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 rate meter rate which is used to calculate burst + * Needed to get the switch manufacturer and apply special calculations if required + * @return the expected burst value for given switch and rate + */ + def getExpectedBurst(long rate) { + def burstCoefficient = KildaProperties.BURST_COEFFICIENT + if (isNoviflow() || isWb5164()) { + return (rate * NOVIFLOW_BURST_COEFFICIENT - 1).setScale(0, RoundingMode.CEILING) + } else if (isCentec()) { + 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) + } + } + + /*** + * Database interaction + */ + + @Memoized + Set getDbFeatures() { + database.getSwitch(sw.dpId).features + } + + boolean isVxlanFeatureEnabled() { + !getDbFeatures().intersect([NOVIFLOW_PUSH_POP_VXLAN, KILDA_OVS_PUSH_POP_MATCH_VXLAN]).isEmpty() + } + + static int randomVlan() { + return randomVlan([]) + } + + static int randomVlan(List exclusions) { + return (KILDA_ALLOWED_VLANS - exclusions).shuffled().first() + } + + static List availableVlanList(List exclusions) { + return (KILDA_ALLOWED_VLANS - exclusions) + } + + static SwitchPatchDto convertToUpdateRequest(SwitchDto swDetails) { + def pop = swDetails.pop ? swDetails.pop : "" + def location = new SwitchLocationDtoV2(swDetails.location.latitude, swDetails.location.longitude, "", "", "") + !swDetails.location.street ?: location.setStreet(swDetails.location.street) + !swDetails.location.city ?: location.setStreet(swDetails.location.city) + !swDetails.location.country ?: location.setStreet(swDetails.location.country) + return new SwitchPatchDto(pop, location) + + } + + void verifyLacpRulesAndMeters(List containsRules, List excludesRules, boolean isLacpMeterPresent) { + assert !validateAndCollectFoundDiscrepancies().isPresent() + + // check rules + def hexCookies = rulesManager.getRules().cookie.collect { Cookie.toString(it) } + assert hexCookies.containsAll(containsRules) + assert hexCookies.intersect(excludesRules).isEmpty() + + // check meters + def meters = metersManager.getMeters().meterId + if (isLacpMeterPresent) { + assert LACP_REPLY_METER_ID.value in meters + } else { + assert LACP_REPLY_METER_ID.value !in meters + } + } + + void verifyRateSizeIsCorrect(Long expected, Long actual) { + if (isWb5164()) { + verifyRateSizeOnWb5164(expected, actual) + } else { + assert Math.abs(expected - actual) <= 1 + } + } + + void verifyBurstSizeIsCorrect(Long expected, Long actual) { + if (isWb5164()) { + verifyBurstSizeOnWb5164(expected, actual) + } else { + assert Math.abs(expected - actual) <= 1 + } + } + + static List getLagCookies(List ports, boolean withLacpCookie) { + List portRelatedLagCookies = ports.logicalPortNumber.collect { portNumber -> + new PortColourCookie(CookieType.LACP_REPLY_INPUT, portNumber as int).toString() + } + !withLacpCookie ?: portRelatedLagCookies.add(LACP_COOKIE) + portRelatedLagCookies + } + + static void verifyBurstSizeOnWb5164(Long expected, Long actual) { + //...ValidationServiceImpl.E_SWITCH_METER_RATE_EQUALS_DELTA_COEFFICIENT = 0.01 + assert Math.abs(expected - actual) <= expected * 0.01 + } + + static void verifyRateSizeOnWb5164(Long expectedRate, Long actualRate) { + //...ValidationServiceImpl.E_SWITCH_METER_BURST_SIZE_EQUALS_DELTA_COEFFICIENT = 0.01 + assert Math.abs(expectedRate - actualRate) <= expectedRate * 0.01 + } + + static boolean isDefaultMeter(MeterInfoDto dto) { + return MeterId.isMeterIdOfDefaultRule(dto.getMeterId()) + } + + static boolean isDefaultMeter(MeterInfoDtoV2 dto) { + return MeterId.isMeterIdOfDefaultRule(dto.getMeterId()) + } + + /** + * Verifies that specified rule 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 missing/excess/misconfigured sections. + */ + static void verifyRuleSectionsAreEmpty(SwitchValidationExtendedResult 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.serviceFlag && cookie.type != CookieType.SHARED_OF_FLOW + }.empty + } + } else { + assertions.checkSucceeds { assert switchValidateInfo.rules."$section".empty } + } + } + 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 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 + * default meters in 'proper' section is considered 'empty' + */ + static void verifyMeterSectionsAreEmpty(SwitchValidationExtendedResult 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() + } + + 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 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() + } +} diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchMeters.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchMeters.groovy index 53b31e42ed4..dffb73f0435 100644 --- a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchMeters.groovy +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchMeters.groovy @@ -1,12 +1,17 @@ package org.openkilda.functionaltests.helpers.model +import static org.openkilda.model.MeterId.MAX_SYSTEM_RULE_METER_ID +import org.openkilda.messaging.info.meter.MeterEntry import org.openkilda.model.FlowMeter import org.openkilda.model.SwitchId -import org.openkilda.northbound.dto.v2.haflows.HaFlow +import org.openkilda.northbound.dto.v1.switches.DeleteMeterResult import org.openkilda.testing.service.database.Database import org.openkilda.testing.service.northbound.NorthboundService +import groovy.transform.ToString + +@ToString(includeNames = true, excludes = 'northboundService, database') class SwitchMeters { NorthboundService northboundService Database database @@ -25,7 +30,19 @@ class SwitchMeters { .findAll {it.getSwitchId() == switchId} } + List getMeters() { + northboundService.getAllMeters(switchId).meterEntries + } + + List getCreatedMeterIds() { + return getMeters().findAll { it.meterId > MAX_SYSTEM_RULE_METER_ID }*.meterId + } + void delete(FlowMeter flowMeter) { - northboundService.deleteMeter(switchId, flowMeter.getMeterId().getValue()) + delete(flowMeter.getMeterId().getValue()) + } + + DeleteMeterResult delete(Long meterId) { + northboundService.deleteMeter(switchId, meterId) } } diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchPair.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchPair.groovy index e985846ed61..d7e4ce1a09c 100644 --- a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchPair.groovy +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchPair.groovy @@ -88,6 +88,10 @@ class SwitchPair { return !(src.getTraffGens().isEmpty() || dst.getTraffGens().isEmpty()) } + static Closure NOT_WB_ENDPOINTS = { + SwitchPair swP-> !swP.src.wb5164 && !swP.dst.wb5164 + } + List retrieveAvailablePaths(){ convertToPathNodePayload(paths).collect{ new Path(it, topologyDefinition) diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchPairs.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchPairs.groovy index 10a9eff4d2c..30f25d04d06 100644 --- a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchPairs.groovy +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchPairs.groovy @@ -246,6 +246,11 @@ class SwitchPairs { return this } + SwitchPairs withoutWBSwitch() { + switchPairs = switchPairs.findAll { SwitchPair.NOT_WB_ENDPOINTS(it) } + return this + } + private void assertAllSwitchPairsAreNeighbouring() { assert switchPairs.size() == this.neighbouring().getSwitchPairs().size(), "This method is applicable only to the neighbouring switch pairs" diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchRules.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchRules.groovy index 711012e695d..aac119f71b0 100644 --- a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchRules.groovy +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/SwitchRules.groovy @@ -1,17 +1,27 @@ package org.openkilda.functionaltests.helpers.model +import static org.openkilda.model.cookie.Cookie.* +import static org.openkilda.model.cookie.CookieBase.CookieType.* + import org.openkilda.functionaltests.model.cleanup.CleanupManager +import org.openkilda.messaging.command.switches.DeleteRulesAction +import org.openkilda.messaging.command.switches.InstallRulesAction import org.openkilda.model.FlowEncapsulationType import org.openkilda.model.FlowMeter import org.openkilda.model.SwitchId import org.openkilda.model.cookie.Cookie import org.openkilda.model.cookie.CookieBase.CookieType import org.openkilda.northbound.dto.v1.flows.PathDiscrepancyDto +import org.openkilda.northbound.dto.v1.switches.RulesSyncResult +import org.openkilda.northbound.dto.v1.switches.RulesValidationResult import org.openkilda.testing.service.database.Database import org.openkilda.testing.service.northbound.NorthboundService import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.SYNCHRONIZE_SWITCH +import groovy.transform.ToString + +@ToString(includeNames = true, excludes = 'northboundService, database, cleanupManager') class SwitchRules { NorthboundService northboundService Database database @@ -40,15 +50,43 @@ class SwitchRules { return getRules().findAll {it.getInstructions().getGoToMeter() == flowMeter.getMeterId().getValue()} } + List install(InstallRulesAction installAction) { + northboundService.installSwitchRules(switchId, installAction) + } + + RulesValidationResult validate() { + northboundService.validateSwitchRules(switchId) + } + + RulesSyncResult synchronize() { + northboundService.synchronizeSwitchRules(switchId) + } + void delete(FlowRuleEntity flowEntry) { delete(flowEntry.getCookie()) } - void delete(long cookie) { + List delete(long cookie) { cleanupManager.addAction(SYNCHRONIZE_SWITCH, {northboundService.synchronizeSwitch(switchId, true)}) northboundService.deleteSwitchRules(switchId, cookie) } + List delete(DeleteRulesAction deleteAction) { + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {northboundService.synchronizeSwitch(switchId, true)}) + return northboundService.deleteSwitchRules(switchId, deleteAction) + } + + List delete(Integer inPort, Integer inVlan, String encapsulationType, + Integer outPort) { + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {northboundService.synchronizeSwitch(switchId, true)}) + return northboundService.deleteSwitchRules(switchId, inPort, inVlan, encapsulationType, outPort) + } + + List delete(int priority) { + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {northboundService.synchronizeSwitch(switchId, true)}) + return northboundService.deleteSwitchRules(switchId, priority) + } + static Set missingRuleCookieIds(Collection missingRules) { return missingRules.collect {new Long((it.getRule() =~ COOKIE_ID_IN_RULE_DISCREPANCY_STRING_REGEX)[0])} } @@ -60,26 +98,36 @@ class SwitchRules { static long getPingRuleCookie(String encapsulationType) { if (FlowEncapsulationType.TRANSIT_VLAN.toString().equalsIgnoreCase(encapsulationType)) { - return Cookie.VERIFICATION_UNICAST_RULE_COOKIE + return VERIFICATION_UNICAST_RULE_COOKIE } else if (FlowEncapsulationType.VXLAN.toString().equalsIgnoreCase(encapsulationType)) { - return Cookie.VERIFICATION_UNICAST_VXLAN_RULE_COOKIE + return VERIFICATION_UNICAST_VXLAN_RULE_COOKIE } else { throw new IllegalArgumentException("Unknown encapsulation " + encapsulationType) } } + List getRulesWithMeter() { + return getRules().findAll { + !new Cookie(it.cookie).serviceFlag && it.instructions.goToMeter + } + } + List getRulesByCookieType(CookieType cookieType) { getRules().findAll { new Cookie(it.cookie).getType() == cookieType } } - List getServer42FlowRules() { - getRules().findAll { new Cookie(it.cookie).getType() in [CookieType.SERVER_42_FLOW_RTT_INPUT, - CookieType.SERVER_42_FLOW_RTT_INGRESS] } + List getServer42FlowRelatedRules() { + getRules().findAll { new Cookie(it.cookie).getType() in [SERVER_42_FLOW_RTT_INPUT, SERVER_42_FLOW_RTT_INGRESS] } + } + + List getServer42ISLRelatedRules() { + getRules().findAll { (new Cookie(it.cookie).getType() in [SERVER_42_ISL_RTT_INPUT] || + it.cookie in [SERVER_42_ISL_RTT_TURNING_COOKIE, SERVER_42_ISL_RTT_OUTPUT_COOKIE]) } } - List getServer42ISLRules() { - getRules().findAll { (new Cookie(it.cookie).getType() in [CookieType.SERVER_42_ISL_RTT_INPUT] || - it.cookie in [Cookie.SERVER_42_ISL_RTT_TURNING_COOKIE, Cookie.SERVER_42_ISL_RTT_OUTPUT_COOKIE]) } + List getServer42SwitchRelatedRules() { + getRules().findAll { it.cookie in [SERVER_42_FLOW_RTT_OUTPUT_VLAN_COOKIE, + SERVER_42_FLOW_RTT_OUTPUT_VXLAN_COOKIE] } } List getRules() { 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 new file mode 100644 index 00000000000..2c418e882b9 --- /dev/null +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/model/Switches.groovy @@ -0,0 +1,106 @@ +package org.openkilda.functionaltests.helpers.model + +import static org.junit.jupiter.api.Assumptions.assumeFalse +import static org.junit.jupiter.api.Assumptions.assumeTrue +import static org.openkilda.functionaltests.helpers.model.SwitchExtended.isS42Supported +import static org.openkilda.functionaltests.model.switches.Manufacturer.CENTEC +import static org.openkilda.functionaltests.model.switches.Manufacturer.NOVIFLOW +import static org.openkilda.functionaltests.model.switches.Manufacturer.OVS +import static org.openkilda.functionaltests.model.switches.Manufacturer.WB5164 +import static org.springframework.beans.factory.config.ConfigurableBeanFactory.SCOPE_PROTOTYPE + +import org.openkilda.functionaltests.helpers.factory.SwitchFactory +import org.openkilda.functionaltests.model.switches.Manufacturer +import org.openkilda.model.SwitchId +import org.openkilda.northbound.dto.v1.switches.SwitchDto +import org.openkilda.testing.model.topology.TopologyDefinition +import org.openkilda.testing.service.northbound.NorthboundService +import org.openkilda.testing.service.northbound.NorthboundServiceV2 + +import groovy.transform.Memoized +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.context.annotation.Scope +import org.springframework.stereotype.Component + +@Component +@Scope(SCOPE_PROTOTYPE) +class Switches { + + @Autowired + TopologyDefinition topology + @Autowired + @Qualifier("islandNb") + NorthboundService northbound + @Autowired + @Qualifier("islandNbV2") + NorthboundServiceV2 northboundV2 + @Autowired + SwitchFactory switchFactory + + List switches + + Switches all() { + switches = collectSwitches() + return this + } + + Switches withManufacturer(Manufacturer type) { + switch (type) { + case WB5164: + switches = switches.findAll { it.isWb5164() } + break + case NOVIFLOW: + switches = switches.findAll { it.isNoviflow() && !it.isWb5164() } + break + case CENTEC: + switches = switches.findAll { it.isCentec() } + break + case OVS: + switches = switches.findAll { it.isVirtual() } + break + } + return this + } + + List unique() { + switches.unique { it.getDescription() } + } + + List uniqueByHw() { + switches.unique { it.hwSwString() } + } + + Switches withS42Support(){ + def swsProps = northboundV2.getAllSwitchProperties().switchProperties + switches = switches.findAll { sw -> isS42Supported(swsProps.find { it.switchId == sw.switchId}) } + return this + } + + SwitchExtended random() { + assumeFalse(switches.isEmpty(), "No suiting switch found") + switches.shuffled().first() + } + + SwitchExtended first() { + assumeFalse(switches.isEmpty(), "No suiting switch found") + switches.first() + } + + SwitchExtended findSpecific(SwitchId switchId) { + def sw = switches.find { it.switchId == switchId } + assumeTrue(sw as Boolean, "No suiting switch found") + sw + } + + @Memoized + private List collectSwitches() { + List switchesDetails = northbound.allSwitches + switches = topology.activeSwitches.collect { + def sw = switchFactory.get(it) + sw.setNbDetails(switchesDetails.find { sw.switchId == it.switchId }) + return sw + } + return switches + } +} diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/model/switches/Manufacturer.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/model/switches/Manufacturer.groovy index cfe07aec6e4..1ad381328d7 100644 --- a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/model/switches/Manufacturer.groovy +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/model/switches/Manufacturer.groovy @@ -1,6 +1,7 @@ package org.openkilda.functionaltests.model.switches -import org.openkilda.testing.model.topology.TopologyDefinition + +import org.openkilda.testing.model.topology.TopologyDefinition.Switch enum Manufacturer { OVS("nicira"), @@ -14,7 +15,7 @@ enum Manufacturer { this.descriptionPart = descriptionPart } - boolean isSwitchMatch(TopologyDefinition.Switch aSwitch) { + boolean isSwitchMatch(Switch aSwitch) { return aSwitch.nbFormat().hardware =~ descriptionPart || aSwitch.nbFormat().manufacturer.toLowerCase().contains(descriptionPart) } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/BaseSpecification.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/BaseSpecification.groovy index 0dfd063f47c..7b65fc586a1 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/BaseSpecification.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/BaseSpecification.groovy @@ -1,12 +1,14 @@ package org.openkilda.functionaltests import org.openkilda.functionaltests.helpers.IslHelper +import org.openkilda.functionaltests.helpers.factory.SwitchFactory import org.openkilda.functionaltests.helpers.model.ASwitchFlows import org.openkilda.functionaltests.helpers.model.ASwitchPorts import org.openkilda.functionaltests.helpers.model.KildaConfiguration import org.openkilda.functionaltests.helpers.model.FeatureToggles import org.openkilda.functionaltests.helpers.model.SwitchPairs import org.openkilda.functionaltests.helpers.model.SwitchTriplets +import org.openkilda.functionaltests.helpers.model.Switches import org.openkilda.functionaltests.model.cleanup.CleanupManager import static groovyx.gpars.GParsPool.withPool @@ -63,6 +65,8 @@ class BaseSpecification extends Specification { @Autowired @Shared SwitchHelper switchHelper @Autowired @Shared + SwitchFactory switchFactory + @Autowired @Shared PortAntiflapHelper antiflap //component overrides getting existing flows per topology lab(flow, y-flow, ha_flow) @Autowired @Shared @Qualifier("islandNbV2") @@ -70,6 +74,8 @@ class BaseSpecification extends Specification { @Autowired @Shared StatsHelper statsHelper @Autowired @Shared + Switches switches + @Autowired @Shared SwitchPairs switchPairs @Autowired @Shared SwitchTriplets switchTriplets diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/grpc/GrpcCommonSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/grpc/GrpcCommonSpec.groovy index b8d97c8650e..566d5adda52 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/grpc/GrpcCommonSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/grpc/GrpcCommonSpec.groovy @@ -28,9 +28,9 @@ class GrpcCommonSpec extends GrpcBaseSpecification { sw << getNoviflowSwitches() } - def "Able to get switch packet in out stats on the #switches.hwSwString (#switches.description) switch"() { + def "Able to get switch packet in out stats on the #sw.hwSwString (#sw.description) switch"() { when: "Get switch packet in out stats" - def response = grpc.getPacketInOutStats(switches.address) + def response = grpc.getPacketInOutStats(sw.address) then: "Response is not null and needed fields are returned" with(response) { @@ -42,7 +42,7 @@ class GrpcCommonSpec extends GrpcBaseSpecification { } where: - switches << getNoviflowSwitches() + sw << getNoviflowSwitches() } @Ignore("https://github.com/telstra/open-kilda/issues/3901") diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/server42/Server42FlowRttSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/server42/Server42FlowRttSpec.groovy index b6c0a34c033..b161cf9053a 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/server42/Server42FlowRttSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/server42/Server42FlowRttSpec.groovy @@ -149,7 +149,7 @@ class Server42FlowRttSpec extends HealthCheckSpecification { * (if there are 10 flows on port number 5, then there will be installed one INPUT rule); * - SERVER_42_FLOW_RTT_INGRESS is installed for each flow. */ - assert switchRulesFactory.get(sw.dpId).getServer42FlowRules().cookie.size() == 4 + assert switchRulesFactory.get(sw.dpId).getServer42FlowRelatedRules().cookie.size() == 4 } } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/server42/Server42IslRttSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/server42/Server42IslRttSpec.groovy index 09632f9a6d1..4fc4ccbd8a1 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/server42/Server42IslRttSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/server42/Server42IslRttSpec.groovy @@ -166,7 +166,7 @@ class Server42IslRttSpec extends HealthCheckSpecification { and: "ISL RTT rules are not installed for the new link because it is the same as moved(portNumber)" wait(RULES_INSTALLATION_TIME) { // newIsl.srcSwitch == isl.srcSwitch - assert switchRulesFactory.get(newIsl.srcSwitch.dpId).getServer42ISLRules().size() == + assert switchRulesFactory.get(newIsl.srcSwitch.dpId).getServer42ISLRelatedRules().size() == (northbound.getLinks(newIsl.srcSwitch.dpId, null, null, null).size() - 1 + 2) // -1 = moved link, 2 = SERVER_42_ISL_RTT_TURNING_COOKIE + SERVER_42_ISL_RTT_OUTPUT_COOKIE } @@ -299,7 +299,7 @@ class Server42IslRttSpec extends HealthCheckSpecification { then: "IslRtt rules are installed on the switch" FlowRuleEntity s42IslRttTurningRule wait(RULES_INSTALLATION_TIME) { - def s42IslRttRules = switchRulesFactory.get(sw.dpId).getServer42ISLRules() + def s42IslRttRules = switchRulesFactory.get(sw.dpId).getServer42ISLRelatedRules() assert s42IslRttRules.size() == (northbound.getLinks(sw.dpId, null, null, null).size() + 2) s42IslRttTurningRule = s42IslRttRules.find { it.cookie == Cookie.SERVER_42_ISL_RTT_TURNING_COOKIE } } @@ -313,7 +313,7 @@ class Server42IslRttSpec extends HealthCheckSpecification { then: "SERVER_42_ISL_RTT_OUTPUT_COOKIE and SERVER_42_ISL_RTT_INPUT rules updated according to the changes" and: "SERVER_42_ISL_RTT_TURNING_COOKIE is not changed" wait(RULES_INSTALLATION_TIME) { - def rules = switchRulesFactory.get(sw.dpId).getServer42ISLRules() + def rules = switchRulesFactory.get(sw.dpId).getServer42ISLRelatedRules() assert rules.size() == northbound.getLinks(sw.dpId, null, null, null).size() + 2 assert rules.findAll { new Cookie(it.cookie).getType() == CookieType.SERVER_42_ISL_RTT_INPUT @@ -347,7 +347,7 @@ class Server42IslRttSpec extends HealthCheckSpecification { then: "SERVER_42_ISL_RTT_OUTPUT_COOKIE and SERVER_42_ISL_RTT_INPUT rules updated according to the changes" and: "SERVER_42_ISL_RTT_TURNING_COOKIE is not changed" wait(RULES_INSTALLATION_TIME) { - def rules = switchRulesFactory.get(sw.dpId).getServer42ISLRules() + def rules = switchRulesFactory.get(sw.dpId).getServer42ISLRelatedRules() assert rules.size() == northbound.getLinks(sw.dpId, null, null, null).size() + 2 assert rules.findAll { new Cookie(it.cookie).getType() == CookieType.SERVER_42_ISL_RTT_INPUT @@ -460,7 +460,7 @@ class Server42IslRttSpec extends HealthCheckSpecification { wait(RULES_INSTALLATION_TIME) { checkIslRttRules(isl.srcSwitch.dpId, true) } when: "Delete ISL Rtt rules on the src switch" - def rulesToDelete = switchRulesFactory.get(isl.srcSwitch.dpId).getServer42ISLRules() + def rulesToDelete = switchRulesFactory.get(isl.srcSwitch.dpId).getServer42ISLRelatedRules() withPool { rulesToDelete.eachParallel { switchHelper.deleteSwitchRules(isl.srcSwitch.dpId, it.cookie) } } @@ -489,7 +489,7 @@ class Server42IslRttSpec extends HealthCheckSpecification { and: "ISL Rtt rules are really installed" wait(RULES_INSTALLATION_TIME) { - def installedRules = switchRulesFactory.get(isl.srcSwitch.dpId).getServer42ISLRules() + def installedRules = switchRulesFactory.get(isl.srcSwitch.dpId).getServer42ISLRelatedRules() assertThat(installedRules).containsExactlyInAnyOrder(*rulesToDelete) } @@ -530,7 +530,7 @@ class Server42IslRttSpec extends HealthCheckSpecification { void checkIslRttRules(SwitchId switchId, Boolean rulesExist) { def countOfRules = rulesExist ? (northbound.getLinks(switchId, null, null, null).size() + 2) : 0 - assert switchRulesFactory.get(switchId).getServer42ISLRules().size() == countOfRules + assert switchRulesFactory.get(switchId).getServer42ISLRelatedRules().size() == countOfRules } void checkIslRttStats(Isl isl, Date checkpointTime, Boolean statExist) { diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/DefaultRulesSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/DefaultRulesSpec.groovy index 5334bab26a3..fddc10d9a33 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/DefaultRulesSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/DefaultRulesSpec.groovy @@ -1,91 +1,86 @@ package org.openkilda.functionaltests.spec.switches import static org.assertj.core.api.Assertions.assertThat -import static org.junit.jupiter.api.Assumptions.assumeTrue -import static org.openkilda.functionaltests.extension.tags.Tag.HARDWARE import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE_SWITCHES import static org.openkilda.functionaltests.extension.tags.Tag.SWITCH_RECOVER_ON_FAIL import static org.openkilda.functionaltests.extension.tags.Tag.TOPOLOGY_DEPENDENT import static org.openkilda.functionaltests.helpers.Wrappers.wait +import static org.openkilda.functionaltests.model.switches.Manufacturer.NOVIFLOW +import static org.openkilda.functionaltests.model.switches.Manufacturer.OVS +import static org.openkilda.messaging.command.switches.DeleteRulesAction.DROP_ALL +import static org.openkilda.messaging.command.switches.DeleteRulesAction.REMOVE_SERVER_42_ISL_RTT_TURNING +import static org.openkilda.messaging.command.switches.DeleteRulesAction.REMOVE_SERVER_42_TURNING +import static org.openkilda.model.SwitchFeature.KILDA_OVS_SWAP_FIELD +import static org.openkilda.model.SwitchFeature.NOVIFLOW_SWAP_ETH_SRC_ETH_DST import static org.openkilda.model.cookie.Cookie.SERVER_42_FLOW_RTT_TURNING_COOKIE -import static org.openkilda.model.cookie.Cookie.SERVER_42_ISL_RTT_OUTPUT_COOKIE import static org.openkilda.model.cookie.Cookie.SERVER_42_ISL_RTT_TURNING_COOKIE +import static org.openkilda.model.cookie.CookieBase.CookieType.MULTI_TABLE_ISL_VLAN_EGRESS_RULES import static org.openkilda.testing.Constants.RULES_DELETION_TIME import static org.openkilda.testing.Constants.RULES_INSTALLATION_TIME import static org.openkilda.testing.service.floodlight.model.FloodlightConnectMode.RW import org.openkilda.functionaltests.HealthCheckSpecification import org.openkilda.functionaltests.extension.tags.Tags -import org.openkilda.functionaltests.helpers.model.SwitchRulesFactory +import org.openkilda.functionaltests.helpers.model.SwitchExtended import org.openkilda.messaging.command.switches.DeleteRulesAction import org.openkilda.messaging.command.switches.InstallRulesAction import org.openkilda.messaging.model.SwitchPropertiesDto.RttState -import org.openkilda.model.SwitchFeature import org.openkilda.model.cookie.Cookie -import org.openkilda.model.cookie.CookieBase.CookieType -import org.openkilda.testing.model.topology.TopologyDefinition.Switch - -import org.springframework.beans.factory.annotation.Autowired -import spock.lang.Shared class DefaultRulesSpec extends HealthCheckSpecification { - @Autowired - @Shared - SwitchRulesFactory switchRulesFactory - def setupSpec() { deleteAnyFlowsLeftoversIssue5480() } @Tags([TOPOLOGY_DEPENDENT, SMOKE, SMOKE_SWITCHES]) - def "Default rules are installed on switches #sw.hwSwString"() { + def "Default rules are installed on switches #sw.hwSwString()"() { expect: "Default rules are installed on the switch" - def cookies = switchRulesFactory.get(sw.dpId).getRules().cookie - cookies.sort() == sw.defaultCookies.sort() + def cookies = sw.rulesManager.getRules().cookie + cookies.sort() == sw.collectDefaultCookies().sort() where: - sw << getTopology().getActiveSwitches().unique { sw -> sw.description } + sw << switches.all().unique() } @Tags([SMOKE, SWITCH_RECOVER_ON_FAIL]) def "Default rules are installed when a new switch is connected"() { given: "A switch with no rules installed and not connected to the controller" - def sw = topology.activeSwitches.first() - switchHelper.deleteSwitchRules(sw.dpId, DeleteRulesAction.DROP_ALL) - wait(RULES_DELETION_TIME) { assert switchRulesFactory.get(sw.dpId).getRules().isEmpty() } - def blockData = switchHelper.knockoutSwitch(sw, RW) + def sw = switches.all().random() + sw.rulesManager.delete(DROP_ALL) + wait(RULES_DELETION_TIME) { assert sw.rulesManager.getRules().isEmpty() } + def blockData = sw.knockout(RW) when: "Connect the switch to the controller" - switchHelper.reviveSwitch(sw, blockData) + sw.revive(blockData) then: "Default rules are installed on the switch" wait(RULES_INSTALLATION_TIME) { - assert switchRulesFactory.get(sw.dpId).getRules().cookie.sort() == sw.defaultCookies.sort() + assert sw.rulesManager.getRules().cookie.sort() == sw.collectDefaultCookies().sort() } } @Tags([TOPOLOGY_DEPENDENT, SMOKE_SWITCHES]) - def "Able to install default rule on #sw.hwSwString [install-action=#data.installRulesAction]"( - Map data, Switch sw) { + def "Able to install default rule on #sw.hwSwString() [install-action=#data.installRulesAction]"( + Map data, SwitchExtended sw) { given: "A switch without any rules" - def defaultRules = switchRulesFactory.get(sw.dpId).getRules() - assertThat(defaultRules*.cookie.sort()).containsExactlyInAnyOrder(*sw.defaultCookies.sort()) + def defaultRules = sw.rulesManager.getRules() + assertThat(defaultRules*.cookie.sort()).containsExactlyInAnyOrder(*sw.collectDefaultCookies().sort()) - switchHelper.deleteSwitchRules(sw.dpId, DeleteRulesAction.DROP_ALL) - wait(RULES_DELETION_TIME) { assert switchRulesFactory.get(sw.dpId).getRules().empty } + sw.rulesManager.delete(DROP_ALL) + wait(RULES_DELETION_TIME) { assert sw.rulesManager.getRules().empty } when: "Install rules on the switch" - def installedRules = northbound.installSwitchRules(sw.dpId, data.installRulesAction) + def installedRules = sw.rulesManager.install(data.installRulesAction) then: "The corresponding rules are really installed" installedRules.size() == 1 def expectedRules = defaultRules.findAll { it.cookie == data.cookie } wait(RULES_INSTALLATION_TIME) { - def actualRules = switchRulesFactory.get(sw.dpId).getRules() - .findAll { new Cookie(it.cookie).getType() != CookieType.MULTI_TABLE_ISL_VLAN_EGRESS_RULES } + def actualRules = sw.rulesManager.getRules() + .findAll { new Cookie(it.cookie).getType() != MULTI_TABLE_ISL_VLAN_EGRESS_RULES } assertThat(actualRules).containsExactlyInAnyOrder(*expectedRules) } @@ -117,39 +112,39 @@ class DefaultRulesSpec extends HealthCheckSpecification { cookie : Cookie.VERIFICATION_UNICAST_VXLAN_RULE_COOKIE ] ], - getTopology().getActiveSwitches().unique { activeSw -> activeSw.description } + switches.all().unique() ].combinations() - .findAll { dataPiece, theSw -> - //OF_12 has only broadcast rule, so filter out all other combinations for OF_12 - !(theSw.ofVersion == "OF_12" && dataPiece.installRulesAction != InstallRulesAction.INSTALL_BROADCAST) && - //BFD, Round Trip and VXlan are available only on Noviflow - !(!theSw.noviflow && dataPiece.installRulesAction in [InstallRulesAction.INSTALL_BFD_CATCH, - InstallRulesAction.INSTALL_ROUND_TRIP_LATENCY, - InstallRulesAction.INSTALL_UNICAST_VXLAN]) && - //having broadcast rule with 'drop loop' rule on WB5164 will lead to packet storm. See #2595 - !(theSw.wb5164 && dataPiece.installRulesAction == InstallRulesAction.INSTALL_BROADCAST) - } + .findAll { dataPiece, SwitchExtended theSw -> + //OF_12 has only broadcast rule, so filter out all other combinations for OF_12 + !(theSw.ofVersion == "OF_12" && dataPiece.installRulesAction != InstallRulesAction.INSTALL_BROADCAST) && + //BFD, Round Trip and VXlan are available only on Noviflow + !(!theSw.isNoviflow() && dataPiece.installRulesAction in [InstallRulesAction.INSTALL_BFD_CATCH, + InstallRulesAction.INSTALL_ROUND_TRIP_LATENCY, + InstallRulesAction.INSTALL_UNICAST_VXLAN]) && + //having broadcast rule with 'drop loop' rule on WB5164 will lead to packet storm. See #2595 + !(theSw.isWb5164() && dataPiece.installRulesAction == InstallRulesAction.INSTALL_BROADCAST) + } } @Tags([TOPOLOGY_DEPENDENT, SMOKE_SWITCHES]) - def "Able to install default rule on switch: #sw.hwSwString [install-action=#data.installRulesAction]"( - Map data, Switch sw) { + def "Able to install default rule on switch: #sw.hwSwString() [install-action=#data.installRulesAction]"( + Map data, SwitchExtended sw) { given: "A switch without rules" - def defaultRules = switchRulesFactory.get(sw.dpId).getRules() - assert defaultRules*.cookie.sort() == sw.defaultCookies.sort() + def defaultRules = sw.rulesManager.getRules() + assert defaultRules*.cookie.sort() == sw.collectDefaultCookies().sort() - switchHelper.deleteSwitchRules(sw.dpId, DeleteRulesAction.DROP_ALL) - wait(RULES_DELETION_TIME) { assert switchRulesFactory.get(sw.dpId).getRules().empty } + sw.rulesManager.delete(DROP_ALL) + wait(RULES_DELETION_TIME) { assert sw.rulesManager.getRules().empty } when: "Install rules on the switch" - def installedRules = northbound.installSwitchRules(sw.dpId, data.installRulesAction) + def installedRules = sw.rulesManager.install(data.installRulesAction) then: "The corresponding rules are really installed" installedRules.size() == 1 def expectedRules = defaultRules.findAll { it.cookie == data.cookie } wait(RULES_INSTALLATION_TIME) { - def actualRules = switchRulesFactory.get(sw.dpId).getRules() + def actualRules = sw.rulesManager.getRules() assert actualRules.cookie == installedRules assertThat(actualRules).containsExactlyInAnyOrder(*expectedRules) } @@ -178,58 +173,59 @@ class DefaultRulesSpec extends HealthCheckSpecification { cookie : Cookie.MULTITABLE_TRANSIT_DROP_COOKIE ] ], - getTopology().getActiveSwitches().findAll { - it.noviflow || it.virtual - }.unique { activeSw -> activeSw.description } + (profile == "virtual" ? + switches.all().withManufacturer(OVS).unique() : + switches.all().withManufacturer(NOVIFLOW).unique()) ].combinations() } @Tags([TOPOLOGY_DEPENDENT, SMOKE, SMOKE_SWITCHES]) - def "Able to install default rules on #sw.hwSwString [install-action=INSTALL_DEFAULTS]"() { + def "Able to install default rules on #sw.hwSwString() [install-action=INSTALL_DEFAULTS]"() { given: "A switch without any rules" - def defaultRules = switchRulesFactory.get(sw.dpId).getRules() - assert defaultRules*.cookie.sort() == sw.defaultCookies.sort() + def defaultRules = sw.rulesManager.getRules() + assert defaultRules*.cookie.sort() == sw.collectDefaultCookies().sort() - switchHelper.deleteSwitchRules(sw.dpId, DeleteRulesAction.DROP_ALL) - wait(RULES_DELETION_TIME) { assert switchRulesFactory.get(sw.dpId).getRules().empty } + sw.rulesManager.delete(DROP_ALL) + wait(RULES_DELETION_TIME) { assert sw.rulesManager.getRules().empty } when: "Install rules on the switch" - def installedRules = northbound.installSwitchRules(sw.dpId, InstallRulesAction.INSTALL_DEFAULTS) + def installedRules = sw.rulesManager.install(InstallRulesAction.INSTALL_DEFAULTS) then: "The corresponding rules are really installed" installedRules.size() == defaultRules.size() wait(RULES_INSTALLATION_TIME) { - def actualRules = switchRulesFactory.get(sw.dpId).getRules() + def actualRules = sw.rulesManager.getRules() assertThat(actualRules).containsExactlyInAnyOrder(*defaultRules) } where: - sw << getTopology().getActiveSwitches().unique { sw -> sw.description } + sw << switches.all().unique() } @Tags([TOPOLOGY_DEPENDENT, SMOKE, SMOKE_SWITCHES]) - def "Able to delete default rule from #sw.hwSwString[delete-action=#data.deleteRulesAction]"( - Map data, Switch sw) { + def "Able to delete default rule from #sw.hwSwString()[delete-action=#data.deleteRulesAction]"( + Map data, SwitchExtended sw) { when: "Delete rules from the switch" - def defaultRules = switchRulesFactory.get(sw.dpId).getRules() - def expectedDefaultCookies = sw.defaultCookies + def defaultRules = sw.rulesManager.getRules() + def expectedDefaultCookies = sw.collectDefaultCookies() assert defaultRules*.cookie.sort() == expectedDefaultCookies.sort() - def deletedRules = switchHelper.deleteSwitchRules(sw.dpId, data.deleteRulesAction) + def deletedRules = sw.rulesManager.delete(data.deleteRulesAction) then: "The corresponding rules are really deleted" deletedRules.size() == 1 wait(RULES_DELETION_TIME) { - def actualRules = switchRulesFactory.get(sw.dpId).getRules() + def actualRules = sw.rulesManager.getRules() assertThat(actualRules).containsExactlyInAnyOrder(*defaultRules.findAll { it.cookie != data.cookie }) } and: "Switch and rules validation shows that corresponding default rule is missing" - verifyAll(northbound.validateSwitchRules(sw.dpId)) { + verifyAll(sw.rulesManager.validate()) { missingRules == deletedRules excessRules.empty properRules.sort() == expectedDefaultCookies.findAll { it != data.cookie }.sort() } - verifyAll(switchHelper.validateAndCollectFoundDiscrepancies(sw.dpId).get()) { + + verifyAll(sw.validate()) { rules.missing*.getCookie() == deletedRules rules.misconfigured.empty rules.excess.empty @@ -256,40 +252,41 @@ class DefaultRulesSpec extends HealthCheckSpecification { cookie : Cookie.DROP_VERIFICATION_LOOP_RULE_COOKIE ] ], - getTopology().getActiveSwitches().unique { activeSw -> activeSw.description } - ].combinations().findAll { dataPiece, theSw -> + switches.all().unique() + ].combinations().findAll { dataPiece, SwitchExtended theSw -> //OF_12 switches has only one broadcast rule, so not all iterations will be applicable !(theSw.ofVersion == "OF_12" && dataPiece.cookie != Cookie.VERIFICATION_BROADCAST_RULE_COOKIE) && //dropping this rule on WB5164 will lead to disco-packet storm. Reason: #2595 - !(theSw.wb5164 && dataPiece.cookie == Cookie.DROP_VERIFICATION_LOOP_RULE_COOKIE) + !(theSw.isWb5164() && dataPiece.cookie == Cookie.DROP_VERIFICATION_LOOP_RULE_COOKIE) } } @Tags([TOPOLOGY_DEPENDENT, SMOKE_SWITCHES]) - def "Able to delete default rule from #sw.hwSwString [delete-action=#data.deleteRulesAction]"(Map data, Switch sw) { + def "Able to delete default rule from #sw.hwSwString() [delete-action=#data.deleteRulesAction]"(Map data, SwitchExtended sw) { when: "Delete rule from the switch" def defaultRules - def expectedDefaultCookies = sw.defaultCookies + def expectedDefaultCookies = sw.collectDefaultCookies() wait(RULES_INSTALLATION_TIME) { - defaultRules = switchRulesFactory.get(sw.dpId).getRules() + defaultRules = sw.rulesManager.getRules() assert defaultRules*.cookie.sort() == expectedDefaultCookies.sort() } - def deletedRules = switchHelper.deleteSwitchRules(sw.dpId, data.deleteRulesAction) + def deletedRules = sw.rulesManager.delete(data.deleteRulesAction) then: "The corresponding rule is really deleted" deletedRules.size() == 1 wait(RULES_DELETION_TIME) { - def actualRules = switchRulesFactory.get(sw.dpId).getRules() + def actualRules = sw.rulesManager.getRules() assertThat(actualRules).containsExactlyInAnyOrder(*defaultRules.findAll { it.cookie != data.cookie }) } and: "Switch and rules validation shows that corresponding default rule is missing" - verifyAll(northbound.validateSwitchRules(sw.dpId)) { + verifyAll(sw.rulesManager.validate()) { missingRules == deletedRules excessRules.empty properRules.sort() == expectedDefaultCookies.findAll { it != data.cookie }.sort() } - verifyAll(switchHelper.validateAndCollectFoundDiscrepancies(sw.dpId).get()) { + + verifyAll(sw.validate()) { rules.missing*.getCookie() == deletedRules rules.misconfigured.empty rules.excess.empty @@ -320,23 +317,25 @@ class DefaultRulesSpec extends HealthCheckSpecification { cookie : Cookie.MULTITABLE_TRANSIT_DROP_COOKIE ] ], - getTopology().getActiveSwitches().findAll { - it.noviflow || it.virtual - }.unique { activeSw -> activeSw.description } + (profile == "virtual" ? + switches.all().withManufacturer(OVS).unique() : + switches.all().withManufacturer(NOVIFLOW).unique()) ].combinations() } - @Tags([TOPOLOGY_DEPENDENT, SMOKE_SWITCHES, HARDWARE]) + @Tags([TOPOLOGY_DEPENDENT, SMOKE_SWITCHES]) def "Able to delete/install the server42 Flow RTT turning rule on a switch"() { setup: "Select a switch which support server42 turning rule" - def sw = topology.activeSwitches.find { it.features.contains(SwitchFeature.NOVIFLOW_SWAP_ETH_SRC_ETH_DST) } ?: - assumeTrue(false, "No suiting switch found") + def sw = switches.all().withS42Support().first() + def features = sw.getDbFeatures() + assert features.contains(NOVIFLOW_SWAP_ETH_SRC_ETH_DST) || features.contains(KILDA_OVS_SWAP_FIELD) and: "Server42 is enabled in feature toggle" - assumeTrue(featureToggles.getFeatureToggles().server42FlowRtt) + !featureToggles.getFeatureToggles().server42FlowRtt && featureToggles.server42FlowRtt(true) + sw.waitForS42FlowRttRulesSetup() when: "Delete the server42 turning rule from the switch" - def deleteResponse = switchHelper.deleteSwitchRules(sw.dpId, DeleteRulesAction.REMOVE_SERVER_42_TURNING) + def deleteResponse = sw.rulesManager.delete(REMOVE_SERVER_42_TURNING) then: "The delete rule response contains the server42 turning cookie only" deleteResponse.size() == 1 @@ -344,17 +343,18 @@ class DefaultRulesSpec extends HealthCheckSpecification { and: "The corresponding rule is really deleted" wait(RULES_DELETION_TIME) { - assert switchRulesFactory.get(sw.dpId).getRules().findAll { it.cookie == SERVER_42_FLOW_RTT_TURNING_COOKIE }.empty + assert sw.rulesManager.getRules().findAll { it.cookie == SERVER_42_FLOW_RTT_TURNING_COOKIE }.empty } and: "Switch and rules validation shows that corresponding rule is missing" - def defaultCookiesWithoutMissingS42Rule = sw.defaultCookies.findAll { it != SERVER_42_FLOW_RTT_TURNING_COOKIE }.sort() - verifyAll(northbound.validateSwitchRules(sw.dpId)) { + def defaultCookiesWithoutMissingS42Rule = sw.collectDefaultCookies().findAll { it != SERVER_42_FLOW_RTT_TURNING_COOKIE }.sort() + verifyAll(sw.rulesManager.validate()) { missingRules == [SERVER_42_FLOW_RTT_TURNING_COOKIE] excessRules.empty properRules.sort() == defaultCookiesWithoutMissingS42Rule } - verifyAll(switchHelper.validateAndCollectFoundDiscrepancies(sw.dpId).get()) { + + verifyAll(sw.validate()) { rules.missing*.getCookie() == [SERVER_42_FLOW_RTT_TURNING_COOKIE] rules.misconfigured.empty rules.excess.empty @@ -362,7 +362,7 @@ class DefaultRulesSpec extends HealthCheckSpecification { } when: "Install the server42 turning rule" - def installResponse = northbound.installSwitchRules(sw.dpId, InstallRulesAction.INSTALL_SERVER_42_TURNING) + def installResponse = sw.rulesManager.install(InstallRulesAction.INSTALL_SERVER_42_TURNING) then: "The install rule response contains the server42 turning cookie only" installResponse.size() == 1 @@ -370,33 +370,28 @@ class DefaultRulesSpec extends HealthCheckSpecification { and: "The corresponding rule is really installed" wait(RULES_INSTALLATION_TIME) { - assert !switchRulesFactory.get(sw.dpId).getRules().findAll { it.cookie == SERVER_42_FLOW_RTT_TURNING_COOKIE }.empty + assert !sw.rulesManager.getRules().findAll { it.cookie == SERVER_42_FLOW_RTT_TURNING_COOKIE }.empty } } @Tags([TOPOLOGY_DEPENDENT, SMOKE_SWITCHES]) def "Able to delete/install the server42 ISL RTT turning rule on a switch"() { setup: "Select a switch which support server42 turning rule" - def sw = topology.getActiveServer42Switches().find(s -> switchHelper.getCachedSwProps(s.dpId).server42IslRtt != "DISABLED"); - assumeTrue(sw != null, "No suiting switch found") + def sw = switches.all().withS42Support().random() and: "Server42 is enabled in feature toggle" - assumeTrue(featureToggles.getFeatureToggles().server42IslRtt) + !featureToggles.getFeatureToggles().server42IslRtt && featureToggles.server42IslRtt(true) and: "server42IslRtt is enabled on the switch" - def originSwProps = switchHelper.getCachedSwProps(sw.dpId) - switchHelper.updateSwitchProperties(sw, originSwProps.jacksonCopy().tap({ - it.server42IslRtt = RttState.ENABLED.toString() - })) + def originSwProps = sw.getCashedProps().jacksonCopy() + sw.updateProperties(originSwProps.tap({ it.server42IslRtt = RttState.ENABLED.toString() })) + wait(RULES_INSTALLATION_TIME) { - assert switchRulesFactory.get(sw.dpId).getRules().findAll { - (it.cookie in [SERVER_42_ISL_RTT_TURNING_COOKIE, SERVER_42_ISL_RTT_OUTPUT_COOKIE]) || - (new Cookie(it.cookie).getType() in [CookieType.SERVER_42_ISL_RTT_INPUT]) - }.size() == northbound.getLinks(sw.dpId, null, null, null).size() + 2 + assert sw.rulesManager.getServer42ISLRelatedRules().size() == sw.getRelatedLinks().size() + 2 } when: "Delete the server42 ISL RTT turning rule from the switch" - def deleteResponse = switchHelper.deleteSwitchRules(sw.dpId, DeleteRulesAction.REMOVE_SERVER_42_ISL_RTT_TURNING) + def deleteResponse = sw.rulesManager.delete(REMOVE_SERVER_42_ISL_RTT_TURNING) then: "The delete rule response contains the server42 ISL RTT turning cookie only" deleteResponse.size() == 1 @@ -404,24 +399,25 @@ class DefaultRulesSpec extends HealthCheckSpecification { and: "The corresponding rule is really deleted" wait(RULES_DELETION_TIME) { - assert switchRulesFactory.get(sw.dpId).getRules().findAll { it.cookie == SERVER_42_ISL_RTT_TURNING_COOKIE }.empty + assert sw.rulesManager.getRules().findAll { it.cookie == SERVER_42_ISL_RTT_TURNING_COOKIE }.empty } and: "Switch and rules validation shows that corresponding rule is missing" - verifyAll(northbound.validateSwitchRules(sw.dpId)) { + verifyAll(sw.rulesManager.validate()) { missingRules == [SERVER_42_ISL_RTT_TURNING_COOKIE] excessRules.empty - properRules.sort() == sw.defaultCookies.findAll { it != SERVER_42_ISL_RTT_TURNING_COOKIE }.sort() + properRules.sort() == sw.collectDefaultCookies().findAll { it != SERVER_42_ISL_RTT_TURNING_COOKIE }.sort() } - verifyAll(switchHelper.validateAndCollectFoundDiscrepancies(sw.dpId).get()) { + + verifyAll(sw.validate()) { rules.missing*.getCookie() == [SERVER_42_ISL_RTT_TURNING_COOKIE] rules.misconfigured.empty rules.excess.empty - rules.proper*.getCookie().sort() == sw.defaultCookies.findAll { it != SERVER_42_ISL_RTT_TURNING_COOKIE }.sort() + rules.proper*.getCookie().sort() == sw.collectDefaultCookies().findAll { it != SERVER_42_ISL_RTT_TURNING_COOKIE }.sort() } when: "Install the server42 ISL RTT turning rule" - def installResponse = northbound.installSwitchRules(sw.dpId, InstallRulesAction.INSTALL_SERVER_42_ISL_RTT_TURNING) + def installResponse = sw.rulesManager.install(InstallRulesAction.INSTALL_SERVER_42_ISL_RTT_TURNING) then: "The install rule response contains the server42 ISL RTT turning cookie only" installResponse.size() == 1 @@ -429,7 +425,7 @@ class DefaultRulesSpec extends HealthCheckSpecification { and: "The corresponding rule is really installed" wait(RULES_INSTALLATION_TIME) { - assert !switchRulesFactory.get(sw.dpId).getRules().findAll { it.cookie == SERVER_42_ISL_RTT_TURNING_COOKIE }.empty + assert !sw.rulesManager.getRules().findAll { it.cookie == SERVER_42_ISL_RTT_TURNING_COOKIE }.empty } } } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/DefaultRulesValidationSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/DefaultRulesValidationSpec.groovy index 2a2a5624321..cd0052a707f 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/DefaultRulesValidationSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/DefaultRulesValidationSpec.groovy @@ -4,8 +4,9 @@ import org.openkilda.functionaltests.HealthCheckSpecification import org.openkilda.functionaltests.extension.tags.IterationTag import org.openkilda.functionaltests.extension.tags.Tags import org.openkilda.functionaltests.helpers.Wrappers +import org.openkilda.functionaltests.helpers.model.SwitchExtended import org.openkilda.testing.Constants -import org.openkilda.testing.model.topology.TopologyDefinition.Switch + import spock.lang.Narrative import static org.hamcrest.Matchers.containsInAnyOrder @@ -28,30 +29,30 @@ class DefaultRulesValidationSpec extends HealthCheckSpecification { @Tags(SMOKE) @IterationTag(tags = [LOW_PRIORITY], iterationNameRegex = /single-table/) - def "Switch and rule validation can properly detect default rules to 'proper' section (#sw.hwSwString #propsDescr)"( - Map swProps, Switch sw, String propsDescr) { + def "Switch and rule validation can properly detect default rules to 'proper' section (#sw.hwSwString() #propsDescr)"( + Map swProps, SwitchExtended sw) { given: "Clean switch without customer flows and with the given switchProps" - def originalProps = switchHelper.getCachedSwProps(sw.dpId) - switchHelper.updateSwitchProperties(sw, originalProps.jacksonCopy().tap({ + def originalProps = sw.getCashedProps().jacksonCopy() + sw.updateProperties(originalProps.tap({ it.switchLldp = swProps.switchLldp it.switchArp = swProps.switchArp })) expect: "Switch validation shows all expected default rules in 'proper' section" Wrappers.wait(Constants.RULES_INSTALLATION_TIME) { - verifyAll(northbound.validateSwitchRules(sw.dpId)) { + verifyAll(sw.rulesManager.validate()) { missingRules.empty excessRules.empty - properRules.sort() == sw.defaultCookies.sort() + properRules.sort() == sw.collectDefaultCookies().sort() } } and: "Rule validation shows all expected default rules in 'proper' section" - verifyAll(switchHelper.validate(sw.dpId)) { + verifyAll(sw.validate()) { rules.missing.empty rules.misconfigured.empty rules.excess.empty - assertThat sw.toString(), rules.proper*.cookie, containsInAnyOrder(sw.defaultCookies.toArray()) + assertThat sw.toString(), rules.proper*.cookie, containsInAnyOrder(sw.collectDefaultCookies().toArray()) } where: "Run for all combinations of unique switches and switch modes" @@ -65,7 +66,8 @@ class DefaultRulesValidationSpec extends HealthCheckSpecification { switchLldp: true, switchArp: true ] - ], getTopology().getActiveSwitches().unique { activeSw -> activeSw.hwSwString } + ], + switches.all().uniqueByHw() ].combinations() propsDescr = getDescr(swProps.switchLldp, swProps.switchArp) } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/FlowRulesSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/FlowRulesSpec.groovy index 9597875b328..acdf717e494 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/FlowRulesSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/FlowRulesSpec.groovy @@ -25,15 +25,13 @@ import org.openkilda.functionaltests.helpers.factory.FlowFactory import org.openkilda.functionaltests.helpers.model.FlowEncapsulationType import org.openkilda.functionaltests.helpers.model.FlowExtended import org.openkilda.functionaltests.helpers.model.FlowRuleEntity -import org.openkilda.functionaltests.helpers.model.SwitchRulesFactory +import org.openkilda.functionaltests.helpers.model.SwitchExtended import org.openkilda.functionaltests.model.stats.Direction import org.openkilda.messaging.command.switches.DeleteRulesAction -import org.openkilda.messaging.info.rule.FlowEntry import org.openkilda.messaging.payload.flow.FlowState import org.openkilda.model.SwitchId import org.openkilda.model.cookie.Cookie import org.openkilda.model.cookie.CookieBase.CookieType -import org.openkilda.testing.model.topology.TopologyDefinition.Switch import org.openkilda.testing.service.traffexam.TraffExamService import org.springframework.beans.factory.annotation.Autowired @@ -53,11 +51,8 @@ class FlowRulesSpec extends HealthCheckSpecification { @Autowired @Shared FlowFactory flowFactory - @Autowired - @Shared - SwitchRulesFactory switchRulesFactory @Shared - Switch srcSwitch, dstSwitch + SwitchExtended srcSwitch, dstSwitch @Shared List srcSwDefaultRules @Shared @@ -79,10 +74,10 @@ class FlowRulesSpec extends HealthCheckSpecification { int s42QinqOuterVlanCount = 1 def setupSpec() { - (srcSwitch, dstSwitch) = topology.getActiveSwitches()[0..1] - s42IsEnabledOnSrcSw = switchHelper.getCachedSwProps(srcSwitch.dpId).server42FlowRtt - srcSwDefaultRules = switchRulesFactory.get(srcSwitch.dpId).getRules() - dstSwDefaultRules = switchRulesFactory.get(dstSwitch.dpId).getRules() + (srcSwitch, dstSwitch) = switches.all().getSwitches()[0..1] + s42IsEnabledOnSrcSw = srcSwitch.getProps().server42FlowRtt + srcSwDefaultRules = srcSwitch.rulesManager.getRules() + dstSwDefaultRules = dstSwitch.rulesManager.getRules() } @Tags([VIRTUAL, SMOKE, SWITCH_RECOVER_ON_FAIL]) @@ -92,18 +87,18 @@ class FlowRulesSpec extends HealthCheckSpecification { def defaultPlusFlowRules = [] Wrappers.wait(RULES_INSTALLATION_TIME) { - defaultPlusFlowRules = switchRulesFactory.get(srcSwitch.dpId).getRules() + defaultPlusFlowRules = srcSwitch.rulesManager.getRules() def multiTableFlowRules = multiTableFlowRulesCount + sharedRulesCount assert defaultPlusFlowRules.size() == srcSwDefaultRules.size() + flowRulesCount + multiTableFlowRules } - def blockData = switchHelper.knockoutSwitch(srcSwitch, RW) + def blockData = srcSwitch.knockout(RW) when: "Connect the switch to the controller" - switchHelper.reviveSwitch(srcSwitch, blockData) + srcSwitch.revive(blockData) then: "Previously installed rules are not deleted from the switch" - def actualRules = switchRulesFactory.get(srcSwitch.dpId).getRules() + def actualRules = srcSwitch.rulesManager.getRules() assertThat(actualRules).containsExactlyInAnyOrder(*defaultPlusFlowRules) } @@ -115,12 +110,12 @@ class FlowRulesSpec extends HealthCheckSpecification { when: "Delete rules from the switch" List expectedRules = data.getExpectedRules(srcSwitch, srcSwDefaultRules) - def deletedRules = switchHelper.deleteSwitchRules(srcSwitch.dpId, data.deleteRulesAction) + def deletedRules = srcSwitch.rulesManager.delete(data.deleteRulesAction) then: "The corresponding rules are really deleted" deletedRules.size() == data.rulesDeleted Wrappers.wait(RULES_DELETION_TIME) { - def actualRules = switchRulesFactory.get(srcSwitch.dpId).getRules() + def actualRules = srcSwitch.rulesManager.getRules() assertThat(actualRules).containsExactlyInAnyOrder(*expectedRules) } @@ -139,7 +134,7 @@ class FlowRulesSpec extends HealthCheckSpecification { sharedRulesCount + (s42IsEnabledOnSrcSw ? s42FlowRttInput + s42QinqOuterVlanCount + s42FlowRttIngressForwardCount : 0), getExpectedRules : { sw, defaultRules -> - List noDefaultSwRules = switchRulesFactory.get(srcSwitch.dpId).getRules() - defaultRules + List noDefaultSwRules = srcSwitch.rulesManager.getRules() - defaultRules defaultRules + noDefaultSwRules.findAll { Cookie.isIngressRulePassThrough(it.cookie) } + (s42IsEnabledOnSrcSw ? noDefaultSwRules.findAll { new Cookie(it.cookie).getType() == CookieType.SERVER_42_FLOW_RTT_INPUT } : []) @@ -150,7 +145,7 @@ class FlowRulesSpec extends HealthCheckSpecification { rulesDeleted : flowRulesCount + sharedRulesCount + (s42IsEnabledOnSrcSw ? s42QinqOuterVlanCount + s42FlowRttIngressForwardCount : 0), getExpectedRules : { sw, defaultRules -> - List noDefaultSwRules = switchRulesFactory.get(srcSwitch.dpId).getRules() - defaultRules + List noDefaultSwRules = srcSwitch.rulesManager.getRules() - defaultRules defaultRules + noDefaultSwRules.findAll { Cookie.isIngressRulePassThrough(it.cookie) } + (s42IsEnabledOnSrcSw ? noDefaultSwRules.findAll { new Cookie(it.cookie).getType() == CookieType.SERVER_42_FLOW_RTT_INPUT } : []) @@ -161,7 +156,7 @@ class FlowRulesSpec extends HealthCheckSpecification { rulesDeleted : srcSwDefaultRules.size() + multiTableFlowRulesCount + (s42IsEnabledOnSrcSw ? s42FlowRttInput : 0), getExpectedRules : { sw, defaultRules -> defaultRules + getFlowRules(sw) + - switchRulesFactory.get(srcSwitch.dpId).getRules().findAll { + srcSwitch.rulesManager.getRules().findAll { Cookie.isIngressRulePassThrough(it.cookie) } } @@ -171,7 +166,7 @@ class FlowRulesSpec extends HealthCheckSpecification { rulesDeleted : srcSwDefaultRules.size() + multiTableFlowRulesCount + (s42IsEnabledOnSrcSw ? s42FlowRttInput : 0), getExpectedRules : { sw, defaultRules -> getFlowRules(sw) - - (s42IsEnabledOnSrcSw ? switchRulesFactory.get(srcSwitch.dpId).getRules().findAll { + (s42IsEnabledOnSrcSw ? srcSwitch.rulesManager.getRules().findAll { new Cookie(it.cookie).getType() == CookieType.SERVER_42_FLOW_RTT_INPUT } : []) } ], @@ -180,7 +175,7 @@ class FlowRulesSpec extends HealthCheckSpecification { rulesDeleted : srcSwDefaultRules.size() + multiTableFlowRulesCount + (s42IsEnabledOnSrcSw ? s42FlowRttInput : 0), getExpectedRules : { sw, defaultRules -> defaultRules + getFlowRules(sw) + - switchRulesFactory.get(srcSwitch.dpId).getRules().findAll { + srcSwitch.rulesManager.getRules().findAll { Cookie.isIngressRulePassThrough(it.cookie) } } @@ -196,15 +191,15 @@ class FlowRulesSpec extends HealthCheckSpecification { when: "Delete switch rules by #data.identifier" //exclude the "SERVER_42_INPUT" rule, this rule has less priority than usual flow rule def ruleToDelete = getFlowRules(data.switch).find { !new Cookie(it.cookie).serviceFlag } - def expectedDeletedRules = switchRulesFactory.get(data.switch.dpId).getRules() + def expectedDeletedRules = data.switch.rulesManager.getRules() .findAll { it."$data.identifier" == ruleToDelete."$data.identifier" && !new Cookie(it.cookie).serviceFlag } - def deletedRules = switchHelper.deleteSwitchRules(data.switch.dpId, ruleToDelete."$data.identifier") + def deletedRules = data.switch.rulesManager.delete(ruleToDelete."$data.identifier") then: "The requested rules are really deleted" deletedRules.size() == expectedDeletedRules.size() Wrappers.wait(RULES_DELETION_TIME) { - def actualRules = switchRulesFactory.get(data.switch.dpId).getRules() + def actualRules = data.switch.rulesManager.getRules() assert actualRules.findAll { it.cookie in expectedDeletedRules*.cookie }.empty } @@ -224,7 +219,7 @@ class FlowRulesSpec extends HealthCheckSpecification { assumeTrue(data.description != "priority", "https://github.com/telstra/open-kilda/issues/1701") flowFactory.getRandom(srcSwitch, dstSwitch) - def ingressRule = (switchRulesFactory.get(srcSwitch.dpId).getRules() - data.defaultRules).find { + def ingressRule = (srcSwitch.rulesManager.getRules() - data.defaultRules).find { new Cookie(it.cookie).serviceFlag } if (ingressRule) { @@ -232,11 +227,11 @@ class FlowRulesSpec extends HealthCheckSpecification { } when: "Delete switch rules by non-existing #data.description" - def deletedRules = switchHelper.deleteSwitchRules(data.switch.dpId, data.value) + def deletedRules = data.switch.rulesManager.delete(data.value) then: "All rules are kept intact" deletedRules.size() == 0 - switchRulesFactory.get(data.switch.dpId).getRules().size() == data.defaultRules.size() + flowRulesCount + data.switch.rulesManager.getRules().size() == data.defaultRules.size() + flowRulesCount where: data << [[description : "cookie", @@ -257,12 +252,11 @@ class FlowRulesSpec extends HealthCheckSpecification { def "Able to delete switch rules by #data.description"() { given: "A switch with some flow rules installed" flow.create() - def cookiesBefore = switchRulesFactory.get(data.switch.dpId).getRules().cookie.sort() - def s42IsEnabled = switchHelper.getCachedSwProps(data.switch.dpId).server42FlowRtt + def cookiesBefore = sw.rulesManager.getRules().cookie.sort() + def s42IsEnabled = sw.getProps().server42FlowRtt when: "Delete switch rules by #data.description" - def deletedRules = switchHelper.deleteSwitchRules(data.switch.dpId, data.inPort, data.inVlan, - data.encapsulationType, data.outPort) + def deletedRules = sw.rulesManager.delete(data.inPort, data.inVlan, data.encapsulationType, data.outPort) then: "The requested rules are really deleted" def amountOfDeletedRules = data.removedRules @@ -271,7 +265,7 @@ class FlowRulesSpec extends HealthCheckSpecification { } deletedRules.size() == amountOfDeletedRules Wrappers.wait(RULES_DELETION_TIME) { - def actualRules = switchRulesFactory.get(data.switch.dpId).getRules() + def actualRules = sw.rulesManager.getRules() assert actualRules*.cookie.sort() == cookiesBefore - deletedRules assert filterRules(actualRules, data.inPort, data.inVlan, data.outPort).empty } @@ -310,21 +304,21 @@ class FlowRulesSpec extends HealthCheckSpecification { ].tap { outPort = flow.destination.portNumber }, ] flow = data.flow as FlowExtended + sw = data.switch as SwitchExtended } @IterationTag(tags = [SMOKE], iterationNameRegex = /inVlan/) def "Attempt to delete switch rules by supplying non-existing #data.description keeps all rules intact"() { given: "A switch with some flow rules installed" flowFactory.getRandom(srcSwitch, dstSwitch) - def originalRules = switchRulesFactory.get(data.switch.dpId).getRules().cookie.sort() + def originalRules = sw.rulesManager.getRules().cookie.sort() when: "Delete switch rules by non-existing #data.description" - def deletedRules = switchHelper.deleteSwitchRules(data.switch.dpId, data.inPort, data.inVlan, - data.encapsulationType, data.outPort) + def deletedRules = sw.rulesManager.delete(data.inPort, data.inVlan, data.encapsulationType, data.outPort) then: "All rules are kept intact" deletedRules.size() == 0 - switchRulesFactory.get(data.switch.dpId).getRules().cookie.sort() == originalRules + sw.rulesManager.getRules().cookie.sort() == originalRules where: data << [[description : "inPort", @@ -360,6 +354,7 @@ class FlowRulesSpec extends HealthCheckSpecification { outPort : Integer.MAX_VALUE - 1 ] ] + sw = data.switch as SwitchExtended } @Tags([TOPOLOGY_DEPENDENT]) @@ -378,50 +373,39 @@ class FlowRulesSpec extends HealthCheckSpecification { and: "Remove flow rules so that they become 'missing'" def involvedSwitches = flow.retrieveAllEntityPaths().getInvolvedSwitches() - def defaultPlusFlowRulesMap = involvedSwitches.collectEntries { switchId -> - [switchId, switchRulesFactory.get(switchId).getRules()] - } - - def amountOfRulesMap = involvedSwitches.collectEntries { switchId -> - def swProps = switchHelper.getCachedSwProps(switchId) - def switchIdInSrcOrDst = (switchId in [switchPair.src.dpId, switchPair.dst.dpId]) - def defaultAmountOfFlowRules = 2 // ingress + egress - def amountOfServer42Rules = 0 - if(swProps.server42FlowRtt && switchIdInSrcOrDst) { - amountOfServer42Rules +=1 - switchId == switchPair.src.dpId && flow.source.vlanId && ++amountOfServer42Rules - switchId == switchPair.dst.dpId && flow.destination.vlanId && ++amountOfServer42Rules - } - - def rulesCount = defaultAmountOfFlowRules + amountOfServer42Rules + (switchIdInSrcOrDst ? 1 : 0) + .collect { switches.all().findSpecific(it) } + def defaultPlusFlowRulesMap = involvedSwitches.collectEntries { sw -> + [sw.switchId, sw.rulesManager.getRules()] + } - [switchId, (rulesCount)] + def amountOfRulesMap = involvedSwitches.collectEntries { sw -> + [sw.switchId, sw.collectFlowRelatedRulesAmount(flow)] } - involvedSwitches.each { switchId -> - switchHelper.deleteSwitchRules(switchId, DeleteRulesAction.IGNORE_DEFAULTS) + involvedSwitches.each { sw -> + sw.rulesManager.delete(DeleteRulesAction.IGNORE_DEFAULTS) Wrappers.wait(RULES_DELETION_TIME) { - assert northbound.validateSwitchRules(switchId).missingRules.size() == amountOfRulesMap[switchId] + assert sw.rulesManager.validate().missingRules.size() == amountOfRulesMap[sw.switchId] } } when: "Synchronize rules on switches" - def synchronizedRulesMap = involvedSwitches.collectEntries { switchId -> - [switchId, northbound.synchronizeSwitchRules(switchId)] + def synchronizedRulesMap = involvedSwitches.collectEntries { sw -> + [sw.switchId, sw.rulesManager.synchronize()] } then: "The corresponding rules are installed on switches" - involvedSwitches.each { switchId -> - assert synchronizedRulesMap[switchId].installedRules.size() == amountOfRulesMap[switchId] + involvedSwitches.each { sw -> + assert synchronizedRulesMap[sw.switchId].installedRules.size() == amountOfRulesMap[sw.switchId] Wrappers.wait(RULES_INSTALLATION_TIME) { - def actualRules = switchRulesFactory.get(switchId).getRules() - assertThat(actualRules).containsExactlyInAnyOrder(*defaultPlusFlowRulesMap[switchId]) + def actualRules = sw.rulesManager.getRules() + assertThat(actualRules).containsExactlyInAnyOrder(*defaultPlusFlowRulesMap[sw.switchId]) } } and: "No missing rules were found after rules validation" - involvedSwitches.each { switchId -> - verifyAll(northbound.validateSwitchRules(switchId)) { - properRules.findAll { !new Cookie(it).serviceFlag }.size() == amountOfRulesMap[switchId] + involvedSwitches.each { sw -> + verifyAll(sw.rulesManager.validate()) { + properRules.findAll { !new Cookie(it).serviceFlag }.size() == amountOfRulesMap[sw.switchId] missingRules.empty excessRules.empty } @@ -458,39 +442,35 @@ class FlowRulesSpec extends HealthCheckSpecification { .create() def flowPathInfo = flow.retrieveAllEntityPaths() + def involvedSwitches = flowPathInfo.getInvolvedSwitches() + .collect { switches.all().findSpecific(it) } - HashMap> flowInvolvedSwitchesWithRules = flowPathInfo.getInvolvedSwitches() - .collectEntries{ [(it): switchRulesFactory.get(it).getRules()] } as HashMap> - flow.verifyRulesForProtectedFlowOnSwitches(flowInvolvedSwitchesWithRules) + HashMap> flowInvolvedSwitchesWithRulesBefore = involvedSwitches + .collectEntries{ [(it.switchId): it.rulesManager.getRules()] } as HashMap> + flow.verifyRulesForProtectedFlowOnSwitches(flowInvolvedSwitchesWithRulesBefore) - def mainFlowPath = flowPathInfo.getPathNodes(Direction.FORWARD, false) - def protectedFlowPath = flowPathInfo.getPathNodes(Direction.FORWARD, true) - List commonNodeIds = mainFlowPath*.switchId.intersect(protectedFlowPath*.switchId) - List uniqueNodes = (protectedFlowPath.findAll { !commonNodeIds.contains(it.switchId) } + mainFlowPath.findAll { - !commonNodeIds.contains(it.switchId) - })*.switchId.unique() - def rulesOnSwitchesBefore = (commonNodeIds + uniqueNodes).collectEntries { - [it, switchRulesFactory.get(it).getRules().sort { it.cookie }] - } + def mainPathSwIds = flowPathInfo.getPathNodes(Direction.FORWARD, false).switchId + def protectedPathSwId = flowPathInfo.getPathNodes(Direction.FORWARD, true).switchId + List commonNodeIds = mainPathSwIds.intersect(protectedPathSwId) + List commonMainAndProtectedPathSws = involvedSwitches.findAll { it.switchId in commonNodeIds } + List uniqueMainAndProtectedPathSws = involvedSwitches.findAll { !(it.switchId in commonNodeIds) } and: "Delete flow rules(for main and protected paths) on involved switches for creating missing rules" - commonNodeIds.each { switchHelper.deleteSwitchRules(it, DeleteRulesAction.IGNORE_DEFAULTS) } - uniqueNodes.each { switchHelper.deleteSwitchRules(it, DeleteRulesAction.IGNORE_DEFAULTS) } - commonNodeIds.each { switchId -> - assert northbound.validateSwitchRules(switchId).missingRules.size() > 0 - } - uniqueNodes.each { assert northbound.validateSwitchRules(it).missingRules.size() == 2 } + commonMainAndProtectedPathSws.each { it.rulesManager.delete(DeleteRulesAction.IGNORE_DEFAULTS) } + uniqueMainAndProtectedPathSws.each { it.rulesManager.delete(DeleteRulesAction.IGNORE_DEFAULTS) } + commonMainAndProtectedPathSws.each { assert it.rulesManager.validate().missingRules.size() > 0 } + uniqueMainAndProtectedPathSws.each { assert it.rulesManager.validate().missingRules.size() == 2 } when: "Synchronize rules on switches" - commonNodeIds.each { - def response = northbound.synchronizeSwitchRules(it) + commonMainAndProtectedPathSws.each { + def response = it.rulesManager.synchronize() assert response.missingRules.size() > 0 assert response.installedRules.sort() == response.missingRules.sort() assert response.properRules.findAll { !new Cookie(it).serviceFlag }.empty assert response.excessRules.empty } - uniqueNodes.each { - def response = northbound.synchronizeSwitchRules(it) + uniqueMainAndProtectedPathSws.each { + def response = it.rulesManager.synchronize() assert response.missingRules.size() == 2 assert response.installedRules.sort() == response.missingRules.sort() assert response.properRules.findAll { !new Cookie(it).serviceFlag }.empty, it @@ -498,15 +478,15 @@ class FlowRulesSpec extends HealthCheckSpecification { } then: "No missing rules were found after rules synchronization" - commonNodeIds.each { switchId -> - verifyAll(northbound.validateSwitchRules(switchId)) { - properRules.sort() == rulesOnSwitchesBefore[switchId]*.cookie + commonMainAndProtectedPathSws.each { sw -> + verifyAll(sw.rulesManager.validate()) { + properRules.sort() == flowInvolvedSwitchesWithRulesBefore[sw.switchId]*.cookie.sort() missingRules.empty excessRules.empty } } - uniqueNodes.each { - verifyAll(northbound.validateSwitchRules(it)) { + uniqueMainAndProtectedPathSws.each { + verifyAll(it.rulesManager.validate()) { properRules.findAll { !new Cookie(it).serviceFlag }.size() == 2 missingRules.empty excessRules.empty @@ -514,9 +494,9 @@ class FlowRulesSpec extends HealthCheckSpecification { } and: "Synced rules are exactly the same as before delete (ignoring irrelevant fields)" - rulesOnSwitchesBefore.each { - def actualRules = switchRulesFactory.get(it.key).getRules() - assertThat(actualRules).containsExactlyInAnyOrder(*it.value) + involvedSwitches.each { sw -> + def actualRules = sw.rulesManager.getRules() + assertThat(actualRules).containsExactlyInAnyOrder(*flowInvolvedSwitchesWithRulesBefore[sw.switchId]) } } @@ -529,11 +509,11 @@ class FlowRulesSpec extends HealthCheckSpecification { and: "Create a flow going through these switches" def flow = flowFactory.getRandom(swPair) def flowInfo = flow.retrieveDetailsFromDB() - def flowRulesSrcSw = getFlowRules(swPair.src) - def flowRulesDstSw = getFlowRules(swPair.dst) - def sharedRuleSrcSw = flowRulesSrcSw.find { new Cookie(it.cookie).getType() == CookieType.SHARED_OF_FLOW && + def srcSw = switches.all().findSpecific(swPair.src.dpId) + def dstSw = switches.all().findSpecific(swPair.dst.dpId) + def sharedRuleSrcSw = getFlowRules(srcSw).find { new Cookie(it.cookie).getType() == CookieType.SHARED_OF_FLOW && it.match.inPort.toInteger() == flow.source.portNumber }.cookie - def sharedRuleDstSw = flowRulesDstSw.find { new Cookie(it.cookie).getType() == CookieType.SHARED_OF_FLOW && + def sharedRuleDstSw = getFlowRules(dstSw).find { new Cookie(it.cookie).getType() == CookieType.SHARED_OF_FLOW && it.match.inPort.toInteger() == flow.destination.portNumber }.cookie def ingressSrcSw = flowInfo.forwardPath.cookie.value @@ -554,8 +534,8 @@ class FlowRulesSpec extends HealthCheckSpecification { } then: "Traffic counters in shared/ingress/egress rule on source and destination switches represent packets movement" - def rulesAfterPassingTrafficSrcSw = getFlowRules(swPair.src) - def rulesAfterPassingTrafficDstSw = getFlowRules(swPair.dst) + def rulesAfterPassingTrafficSrcSw = getFlowRules(srcSw) + def rulesAfterPassingTrafficDstSw = getFlowRules(dstSw) //srcSw with(rulesAfterPassingTrafficSrcSw.find { it.cookie == sharedRuleSrcSw}) { !it.flags @@ -597,14 +577,14 @@ class FlowRulesSpec extends HealthCheckSpecification { then: "The flow was rerouted after reroute timeout" def flowInfoAfterReroute - List rulesAfterRerouteSrcSw - List rulesAfterRerouteDstSw + List rulesAfterRerouteSrcSw + List rulesAfterRerouteDstSw Wrappers.wait(rerouteDelay + WAIT_OFFSET) { assert flow.retrieveFlowStatus().status == FlowState.UP assert flow.retrieveAllEntityPaths() != actualFlowPath flowInfoAfterReroute = flow.retrieveDetailsFromDB() - rulesAfterRerouteSrcSw = getFlowRules(swPair.src) - rulesAfterRerouteDstSw = getFlowRules(swPair.dst) + rulesAfterRerouteSrcSw = getFlowRules(srcSw) + rulesAfterRerouteDstSw = getFlowRules(dstSw) //system doesn't reinstall shared rule assert rulesAfterRerouteSrcSw.find { new Cookie(it.cookie).getType() == CookieType.SHARED_OF_FLOW && it.match.inPort.toInteger() == flow.source.portNumber }.cookie == sharedRuleSrcSw @@ -668,83 +648,62 @@ class FlowRulesSpec extends HealthCheckSpecification { and: "Delete flow rules so that they become 'missing'" def flowInfoFromDb = flow.retrieveDetailsFromDB() def involvedSwitches = flow.retrieveAllEntityPaths().getInvolvedSwitches() + .collect { switches.all().findSpecific(it) } + def transitSwitchIds = involvedSwitches[1..-2] - def defaultPlusFlowRulesMap = involvedSwitches.collectEntries { switchId -> - [switchId, switchRulesFactory.get(switchId).getRules()] - } - - def rulesCountMap = involvedSwitches.collectEntries { switchId -> - def swProps = switchHelper.getCachedSwProps(switchId) - def switchIdInSrcOrDst = (switchId in [switchPair.src.dpId, switchPair.dst.dpId]) - def defaultAmountOfFlowRules = 2 // ingress + egress - def amountOfServer42Rules = 0 - if(swProps.server42FlowRtt && switchIdInSrcOrDst) { - amountOfServer42Rules +=1 - switchId == switchPair.src.dpId && flow.source.vlanId && ++amountOfServer42Rules - switchId == switchPair.dst.dpId && flow.destination.vlanId && ++amountOfServer42Rules - } + def defaultPlusFlowRulesMap = involvedSwitches.collectEntries { sw -> + [sw.switchId, sw.rulesManager.getRules()] + } - def rulesCount = defaultAmountOfFlowRules + amountOfServer42Rules + (switchIdInSrcOrDst ? 1 : 0) - [switchId, rulesCount] + def rulesCountMap = involvedSwitches.collectEntries { sw -> + [sw.switchId, sw.collectFlowRelatedRulesAmount(flow)] } - involvedSwitches.each { switchId -> - switchHelper.deleteSwitchRules(switchId, DeleteRulesAction.IGNORE_DEFAULTS) + involvedSwitches.each { sw -> + sw.rulesManager.delete(DeleteRulesAction.IGNORE_DEFAULTS) Wrappers.wait(RULES_DELETION_TIME) { - assert northbound.validateSwitchRules(switchId).missingRules.size() == rulesCountMap[switchId] + assert sw.rulesManager.validate().missingRules.size() == rulesCountMap[sw.switchId] } } when: "Synchronize rules on switches" - def synchronizedRulesMap = involvedSwitches.collectEntries { switchId -> - [switchId, northbound.synchronizeSwitchRules(switchId)] + def synchronizedRulesMap = involvedSwitches.collectEntries { sw -> + [sw.switchId, sw.rulesManager.synchronize()] } then: "The corresponding rules are installed on switches" - involvedSwitches.each { switchId -> - assert synchronizedRulesMap[switchId].installedRules.size() == rulesCountMap[switchId] + involvedSwitches.each { sw -> + assert synchronizedRulesMap[sw.switchId].installedRules.size() == rulesCountMap[sw.switchId] Wrappers.wait(RULES_INSTALLATION_TIME) { - def actualRules = switchRulesFactory.get(switchId).getRules() - assertThat(actualRules).containsExactlyInAnyOrder(*defaultPlusFlowRulesMap[switchId]) + def actualRules = sw.rulesManager.getRules() + assertThat(actualRules).containsExactlyInAnyOrder(*defaultPlusFlowRulesMap[sw.switchId]) } } and: "Rules are synced correctly" // ingressRule should contain "pushVxlan" // egressRule should contain "tunnel-id" - with(switchRulesFactory.get(switchPair.src.dpId).getRules()) { rules -> - assert rules.find { - it.cookie == flowInfoFromDb.forwardPath.cookie.value - }.instructions.applyActions.pushVxlan - assert rules.find { - it.cookie == flowInfoFromDb.reversePath.cookie.value - }.match.tunnelId - } - - with(switchRulesFactory.get(switchPair.dst.dpId).getRules()) { rules -> - assert rules.find { - it.cookie == flowInfoFromDb.forwardPath.cookie.value - }.match.tunnelId - assert rules.find { - it.cookie == flowInfoFromDb.reversePath.cookie.value - }.instructions.applyActions.pushVxlan - } - - transitSwitchIds.each { swId -> - with(switchRulesFactory.get(swId).getRules()) { rules -> - assert rules.find { - it.cookie == flowInfoFromDb.forwardPath.cookie.value - }.match.tunnelId - assert rules.find { - it.cookie == flowInfoFromDb.reversePath.cookie.value - }.match.tunnelId + with(involvedSwitches.find{ it.switchId == switchPair.src.dpId }.rulesManager.getRules()) { rules -> + assert rules.find { it.cookie == flowInfoFromDb.forwardPath.cookie.value }.instructions.applyActions.pushVxlan + assert rules.find { it.cookie == flowInfoFromDb.reversePath.cookie.value }.match.tunnelId + } + + with(involvedSwitches.find{ it.switchId == switchPair.dst.dpId }.rulesManager.getRules()) { rules -> + assert rules.find { it.cookie == flowInfoFromDb.forwardPath.cookie.value }.match.tunnelId + assert rules.find { it.cookie == flowInfoFromDb.reversePath.cookie.value }.instructions.applyActions.pushVxlan + } + + transitSwitchIds.each { sw -> + with(sw.rulesManager.getRules()) { rules -> + assert rules.find { it.cookie == flowInfoFromDb.forwardPath.cookie.value }.match.tunnelId + assert rules.find { it.cookie == flowInfoFromDb.reversePath.cookie.value }.match.tunnelId } } and: "No missing rules were found after rules validation" - involvedSwitches.each { switchId -> - verifyAll(northbound.validateSwitchRules(switchId)) { - properRules.findAll { !new Cookie(it).serviceFlag }.size() == rulesCountMap[switchId] + involvedSwitches.each { sw -> + verifyAll(sw.rulesManager.validate()) { + properRules.findAll { !new Cookie(it).serviceFlag }.size() == rulesCountMap[sw.switchId] missingRules.empty excessRules.empty } @@ -764,8 +723,8 @@ class FlowRulesSpec extends HealthCheckSpecification { return rules } - List getFlowRules(Switch sw) { - def defaultCookies = sw.defaultCookies - switchRulesFactory.get(sw.dpId).getRules().findAll { !(it.cookie in defaultCookies) }.sort() + List getFlowRules(SwitchExtended sw) { + def defaultCookies = sw.collectDefaultCookies() + sw.rulesManager.getRules().findAll { !(it.cookie in defaultCookies) }.sort() } } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/LagPortSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/LagPortSpec.groovy index cd0b36257f0..f10271e2572 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/LagPortSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/LagPortSpec.groovy @@ -3,9 +3,8 @@ package org.openkilda.functionaltests.spec.switches import static groovyx.gpars.GParsPool.withPool import static org.openkilda.functionaltests.extension.tags.Tag.HARDWARE import static org.openkilda.functionaltests.extension.tags.Tag.SWITCH_RECOVER_ON_FAIL -import static org.openkilda.functionaltests.helpers.SwitchHelper.randomVlan -import static org.openkilda.model.MeterId.LACP_REPLY_METER_ID -import static org.openkilda.model.cookie.Cookie.DROP_SLOW_PROTOCOLS_LOOP_COOKIE +import static org.openkilda.functionaltests.helpers.model.SwitchExtended.getLagCookies +import static org.openkilda.functionaltests.helpers.model.SwitchExtended.randomVlan import static org.openkilda.testing.Constants.NON_EXISTENT_SWITCH_ID import static org.openkilda.testing.service.floodlight.model.FloodlightConnectMode.RW @@ -17,13 +16,11 @@ import org.openkilda.functionaltests.error.LagNotUpdatedExpectedError import org.openkilda.functionaltests.error.flow.FlowNotCreatedExpectedError import org.openkilda.functionaltests.extension.tags.Tags import org.openkilda.functionaltests.helpers.factory.FlowFactory +import org.openkilda.functionaltests.helpers.model.LagPort +import org.openkilda.functionaltests.helpers.model.SwitchExtended import org.openkilda.grpc.speaker.model.LogicalPortDto import org.openkilda.messaging.model.grpc.LogicalPortType -import org.openkilda.model.cookie.Cookie -import org.openkilda.model.cookie.CookieBase.CookieType -import org.openkilda.model.cookie.PortColourCookie import org.openkilda.northbound.dto.v2.switches.LagPortRequest -import org.openkilda.testing.model.topology.TopologyDefinition.Switch import org.openkilda.testing.service.grpc.GrpcService import org.openkilda.testing.service.traffexam.TraffExamService @@ -35,13 +32,10 @@ import spock.lang.Shared import javax.inject.Provider - @See("https://github.com/telstra/open-kilda/blob/develop/docs/design/LAG-for-ports/README.md") @Narrative("Verify that flow can be created on a LAG port.") @Tags([HARDWARE]) class LagPortSpec extends HealthCheckSpecification { - public static final long LACP_METER_ID = LACP_REPLY_METER_ID.value - public static final String LACP_COOKIE = Cookie.toString(DROP_SLOW_PROTOCOLS_LOOP_COOKIE) @Autowired @Shared @@ -55,90 +49,88 @@ class LagPortSpec extends HealthCheckSpecification { @Shared Integer lagOffset = 2000 - def "Able to CRUD LAG port with lacp_reply=#lacpReply on #sw.hwSwString"() { + def "Able to CRUD LAG port with lacp_reply=#lacpReply on #sw.hwSwString()"(SwitchExtended sw, boolean lacpReply) { given: "A switch" - def portsArrayCreate = topology.getAllowedPortsForSwitch(sw)[-2, -1] as Set - def portsArrayUpdate = topology.getAllowedPortsForSwitch(sw)[1, -1] as Set + def portsArrayCreate = sw.getPorts()[-2, -1] as Set + def portsArrayUpdate = sw.getPorts()[1, -1] as Set assert portsArrayCreate.sort() != portsArrayUpdate.sort() when: "Create a LAG" - def createResponse = switchHelper.createLagLogicalPort(sw.dpId, portsArrayCreate, lacpReply) + def lagPort = sw.getLagPort(portsArrayCreate).create(lacpReply) then: "Response reports successful creation of the LAG port" - with(createResponse) { + verifyAll(lagPort) { logicalPortNumber > 0 portNumbers.sort() == portsArrayCreate.sort() it.lacpReply == lacpReply } - def lagPort = createResponse.logicalPortNumber and: "LAG port is really created" - def getResponse = northboundV2.getLagLogicalPort(sw.dpId) + def getResponse = sw.getAllLogicalPorts() getResponse.size() == 1 - with(getResponse[0]) { - logicalPortNumber == lagPort + verifyAll(getResponse[0]) { + logicalPortNumber == lagPort.logicalPortNumber portNumbers.sort() == portsArrayCreate.sort() } and: "LAG port is really created on the switch(check GRPC)" - def swAddress = northbound.getSwitch(sw.dpId).address - with(grpc.getSwitchLogicalPortConfig(swAddress, lagPort)) { - logicalPortNumber == lagPort - name == "novi_lport" + lagPort.toString() + def swAddress = sw.getDetails().address + verifyAll(grpc.getSwitchLogicalPortConfig(swAddress, lagPort.logicalPortNumber)) { + logicalPortNumber == lagPort.logicalPortNumber + name == "novi_lport" + lagPort.logicalPortNumber.toString() portNumbers.sort() == portsArrayCreate.sort() type == LogicalPortType.LAG } and: "Switch is valid" - !switchHelper.synchronizeAndCollectFixedDiscrepancies(sw.dpId).isPresent() + !sw.validateAndCollectFoundDiscrepancies().isPresent() when: "Update the LAG port" def payloadUpdate = new LagPortRequest(portNumbers: portsArrayUpdate) - def updateResponse = northboundV2.updateLagLogicalPort(sw.dpId, lagPort, payloadUpdate) + def updateLagPort = lagPort.update(payloadUpdate) then: "Response reports successful updation of the LAG port" - with(updateResponse) { - logicalPortNumber == lagPort + verifyAll(updateLagPort) { + logicalPortNumber == lagPort.logicalPortNumber portNumbers.sort() == portsArrayUpdate.sort() } and: "LAG port is really updated" - with(northboundV2.getLagLogicalPort(sw.dpId)) { + verifyAll(sw.getAllLogicalPorts()) { it.size() == 1 - it[0].logicalPortNumber == lagPort + it[0].logicalPortNumber == lagPort.logicalPortNumber it[0].portNumbers.sort() == portsArrayUpdate.sort() } and: "LAG port is really updated on the switch(check GRPC)" - with(grpc.getSwitchLogicalPortConfig(swAddress, lagPort)) { - logicalPortNumber == lagPort - name == "novi_lport" + lagPort.toString() + verifyAll(grpc.getSwitchLogicalPortConfig(swAddress, lagPort.logicalPortNumber)) { + logicalPortNumber == lagPort.logicalPortNumber + name == "novi_lport" + lagPort.logicalPortNumber.toString() portNumbers.sort() == portsArrayUpdate.sort() type == LogicalPortType.LAG } and: "Switch is valid" - !switchHelper.synchronizeAndCollectFixedDiscrepancies(sw.dpId).isPresent() + !sw.validateAndCollectFoundDiscrepancies().isPresent() when: "Delete the LAG port" - def deleteResponse = northboundV2.deleteLagLogicalPort(sw.dpId, lagPort) + def deleteResponse = lagPort.delete() then: "Response reports successful deletion of the LAG port" - with(deleteResponse) { - logicalPortNumber == lagPort + verifyAll(deleteResponse) { + logicalPortNumber == lagPort.logicalPortNumber portNumbers.sort() == portsArrayUpdate.sort() } and: "LAG port is really deleted from db" - northboundV2.getLagLogicalPort(sw.dpId).empty - def lagPortIsDeleted = true + sw.getAllLogicalPorts().empty and: "LAG port is really deleted from switch" - !grpc.getSwitchLogicalPorts(swAddress).find { it.logicalPortNumber == lagPort } + !grpc.getSwitchLogicalPorts(swAddress).find { it.logicalPortNumber == lagPort.logicalPortNumber } where: [sw, lacpReply] << [ - getTopology().getActiveSwitches().unique(false) { it.hwSwString }, // switches + switches.all().uniqueByHw(), // switches [false, true] // lacp reply ].combinations() } @@ -146,13 +138,14 @@ class LagPortSpec extends HealthCheckSpecification { def "Able to create a flow on a LAG port"() { given: "A switchPair with a LAG port on the src switch" def switchPair = switchPairs.all().withTraffgensOnBothEnds().random() - def traffgenSrcSwPort = switchPair.src.traffGens.switchPort[0] - def portsArray = (topology.getAllowedPortsForSwitch(switchPair.src)[-2, -1] << traffgenSrcSwPort).unique() - def lagPort = switchHelper.createLagLogicalPort(switchPair.src.dpId, portsArray as Set).logicalPortNumber + def srcSwitch = switches.all().findSpecific(switchPair.src.dpId) + def traffgenSrcSwPort = srcSwitch.traffGenPorts.first() + def portsArray = (srcSwitch.getPorts()[-2, -1] + [traffgenSrcSwPort]) as Set + def lagPortNumber = srcSwitch.getLagPort(portsArray).create().logicalPortNumber when: "Create a flow" def flow = flowFactory.getBuilder(switchPair) - .withSourcePort(lagPort) + .withSourcePort(lagPortNumber) .build().create() then: "Flow is valid and pingable" @@ -177,13 +170,14 @@ class LagPortSpec extends HealthCheckSpecification { and: "A flow on the LAG port" def swPair = switchPairs.singleSwitch() .withAtLeastNTraffgensOnSource(2).random() - def traffgenSrcSwPort = swPair.src.traffGens[0].switchPort - def traffgenDstSwPort = swPair.src.traffGens[1].switchPort - def lagPort = switchHelper.createLagLogicalPort(swPair.src.dpId, [traffgenSrcSwPort] as Set).logicalPortNumber + def sw = switches.all().findSpecific(swPair.src.dpId) + Integer traffgenSrcSwPort = sw.traffGenPorts.first() + Integer traffgenDstSwPort = sw.traffGenPorts.last() + def lagPortNumber = sw.getLagPort([traffgenSrcSwPort] as Set).create().logicalPortNumber when: "Create a flow" def flow = flowFactory.getBuilder(swPair) - .withSourcePort(lagPort) + .withSourcePort(lagPortNumber) .withDestinationPort(traffgenDstSwPort) .build().create() @@ -206,48 +200,50 @@ class LagPortSpec extends HealthCheckSpecification { @Tags(SWITCH_RECOVER_ON_FAIL) def "LAG port is not deleted after switch reconnecting"() { given: "A switch with a LAG port" - def sw = topology.getActiveSwitches().first() - def portsArray = topology.getAllowedPortsForSwitch(sw)[-2, -1] - def lagPort = switchHelper.createLagLogicalPort(sw.dpId, portsArray as Set).logicalPortNumber + def sw = switches.all().random() + def portsArray = sw.getPorts()[-2, -1] + def lagPort = sw.getLagPort(portsArray as Set).create() when: "Disconnect the switch" + def blockData = sw.knockout(RW) + and: "Connect the switch back" - def blockData = switchHelper.knockoutSwitch(sw, RW) - switchHelper.reviveSwitch(sw, blockData, true) + sw.revive(blockData, true) then: "The LAG port is still exist" - with(northboundV2.getLagLogicalPort(sw.dpId)[0]) { - logicalPortNumber == lagPort + with(sw.getAllLogicalPorts()[0]) { + logicalPortNumber == lagPort.logicalPortNumber portNumbers.sort() == portsArray.sort() } and: "Switch is valid" - !switchHelper.synchronizeAndCollectFixedDiscrepancies(sw.dpId).isPresent() + !sw.validateAndCollectFoundDiscrepancies().isPresent() } def "Unable to delete a LAG port in case flow on it"() { given: "A flow on a LAG port" def switchPair = switchPairs.all().random() - def portsArray = topology.getAllowedPortsForSwitch(switchPair.src)[-2, -1] - def lagPort = switchHelper.createLagLogicalPort(switchPair.src.dpId, portsArray as Set).logicalPortNumber - def flow = flowFactory.getBuilder(switchPair).withSourcePort(lagPort).build().create() + def srcSw = switches.all().findSpecific(switchPair.src.dpId) + def portsArray = srcSw.getPorts()[-2, -1] + def lagPort = srcSw.getLagPort(portsArray as Set).create() + def flow = flowFactory.getBuilder(switchPair).withSourcePort(lagPort.logicalPortNumber).build().create() when: "When delete LAG port" - northboundV2.deleteLagLogicalPort(switchPair.src.dpId, lagPort) + lagPort.delete() then: "Human readable error is returned" def exc = thrown(HttpClientErrorException) - new LagNotDeletedExpectedError(~/Couldn\'t delete LAG port \'$lagPort\' from switch $switchPair.src.dpId \ + new LagNotDeletedExpectedError(~/Couldn\'t delete LAG port \'$lagPort.logicalPortNumber\' from switch $switchPair.src.dpId \ because flows \'\[$flow.flowId\]\' use it as endpoint/).matches(exc) } def "Unable to create LAG on a port with flow on it"() { given: "Active switch with flow on it" - def sw = topology.activeSwitches.first() - def flow = flowFactory.getRandom(sw, sw) + def sw = switches.all().random() + def flow = flowFactory.getSingleSwRandom(sw) when: "Create a LAG port with flow's port" - switchHelper.createLagLogicalPort(sw.dpId, [flow.source.portNumber] as Set) + sw.getLagPort([flow.source.portNumber] as Set).create() then: "Human readable error is returned" def exc = thrown(HttpClientErrorException) @@ -257,10 +253,10 @@ because flows \'\[$flow.flowId\]\' use it as endpoint/).matches(exc) def "Unable to create a flow on port which is inside LAG group"() { given: "An active switch with LAG port on it" - def sw = topology.activeSwitches.first() - def portsArray = topology.getAllowedPortsForSwitch(sw)[-2, -1] + def sw = switches.all().random() + def portsArray = sw.getPorts()[-2, -1] def flowSourcePort = portsArray[0] - def lagPort = switchHelper.createLagLogicalPort(sw.dpId, portsArray as Set).logicalPortNumber + def lagPort = sw.getLagPort(portsArray as Set).create() when: "Create flow on ports which are in inside LAG group" flowFactory.getBuilder(sw, sw) @@ -271,7 +267,7 @@ because flows \'\[$flow.flowId\]\' use it as endpoint/).matches(exc) then: "Human readable error is returned" def exc = thrown(HttpClientErrorException) new FlowNotCreatedExpectedError(~/Port $flowSourcePort \ -on switch $sw.dpId is used as part of LAG port $lagPort/).matches(exc) +on switch $sw.switchId is used as part of LAG port $lagPort.logicalPortNumber/).matches(exc) } @@ -279,11 +275,12 @@ on switch $sw.dpId is used as part of LAG port $lagPort/).matches(exc) given: "A flow with mirrorPoint" def swP = switchPairs.all().neighbouring().random() def flow = flowFactory.getRandom(swP, false) - def mirrorPort = topology.getAllowedPortsForSwitch(swP.src).last() + def srcToInteract = switches.all().findSpecific(swP.src.dpId) + def mirrorPort = srcToInteract.getPorts().last() def mirrorEndpoint = flow.createMirrorPoint(swP.src.dpId, mirrorPort, randomVlan()) when: "Create a LAG port with port which is used as mirrorPort" - switchHelper.createLagLogicalPort(swP.src.dpId, [mirrorPort] as Set) + srcToInteract.getLagPort([mirrorPort] as Set).create() then: "Human readable error is returned" def exc = thrown(HttpClientErrorException) @@ -294,9 +291,9 @@ on switch $sw.dpId is used as part of LAG port $lagPort/).matches(exc) def "Unable to create a LAG port in case port is #data.description"() { when: "Create a LAG port on a occupied port" - def sw = topology.getActiveServer42Switches().first() + def sw = switches.all().withS42Support().first() def occupiedPort = data.portNumber(sw) - switchHelper.createLagLogicalPort(sw.dpId, [occupiedPort] as Set) + sw.getLagPort([occupiedPort] as Set).create() then: "Human readable error is returned" def exc = thrown(HttpClientErrorException) @@ -306,12 +303,12 @@ on switch $sw.dpId is used as part of LAG port $lagPort/).matches(exc) data << [ [ description: "occupied by server42", - portNumber : { Switch s -> s.prop.server42Port }, + portNumber : { SwitchExtended swExtended -> swExtended.sw.prop.server42Port }, errorDescription: ~/Physical port number \d+ on switch .*? is server42 port./ ], [ description: "occupied by isl", - portNumber : { Switch s -> getTopology().getBusyPortsForSwitch(s)[0] }, + portNumber : { SwitchExtended swExtended -> swExtended.getIslPorts()[0] }, errorDescription: ~/Physical port number \d+ intersects with existing ISLs/ ], [ @@ -321,7 +318,7 @@ on switch $sw.dpId is used as part of LAG port $lagPort/).matches(exc) ], [ description: "not exist", - portNumber : { Switch s -> s.maxPort + 1 }, + portNumber : { SwitchExtended swExtended -> swExtended.sw.maxPort + 1 }, errorDescription: ~/Invalid portno value./ ] ] @@ -329,25 +326,24 @@ on switch $sw.dpId is used as part of LAG port $lagPort/).matches(exc) def "Unable to create two LAG ports with the same physical port inside at the same time"() { given: "A switch with a LAG port" - def sw = topology.getActiveSwitches().first() - def availablePorts = topology.getAllowedPortsForSwitch(sw) + def sw = switches.all().random() + def availablePorts = sw.getPorts() def portsArray = availablePorts[-2, -1] def conflictPortsArray = availablePorts[-3, -1] - def payload = new LagPortRequest(portNumbers: portsArray) - def lagPort = switchHelper.createLagLogicalPort(sw.dpId, portsArray as Set).logicalPortNumber + sw.getLagPort(portsArray as Set).create() when: "Try to create the same LAG port with the same physical ports inside" - northboundV2.createLagLogicalPort(sw.dpId, new LagPortRequest(portNumbers: conflictPortsArray)) + sw.getLagPort(conflictPortsArray as Set).create() then: "Human readable error is returned" def exc = thrown(HttpClientErrorException) - new LagNotCreatedExpectedError(~/Physical ports \[${portsArray[-1]}]\ on switch $sw.dpId already \ + new LagNotCreatedExpectedError(~/Physical ports \[${portsArray[-1]}]\ on switch $sw.switchId already \ occupied by other LAG group\(s\)./).matches(exc) } def "Unable to proceed incorrect delete LAG port request (#data.description)"() { when: "Send invalid delete LAG port request" - getNorthboundV2().deleteLagLogicalPort(data.swIdForRequest(), data.logicalPortNumber) + northboundV2.deleteLagLogicalPort(data.swIdForRequest(), data.logicalPortNumber) then: "Human readable error is returned" def exc = thrown(HttpClientErrorException) @@ -357,7 +353,7 @@ occupied by other LAG group\(s\)./).matches(exc) data << [ [ description : "non-existent LAG port", - swIdForRequest : { getTopology().getActiveSwitches().first().dpId }, + swIdForRequest : { topology.switches.dpId.first() }, logicalPortNumber: 1999, // lagOffset - 1 errorDescription : ~/LAG port 1999 on switch .*? not found/ ], @@ -372,75 +368,74 @@ occupied by other LAG group\(s\)./).matches(exc) def "System is able to detect and sync missed LAG port"() { given: "A switch with a LAG port" - def sw = topology.getActiveSwitches().first() - def portsArray = topology.getAllowedPortsForSwitch(sw)[-2,-1] - def lagPort = switchHelper.createLagLogicalPort(sw.dpId, portsArray as Set).logicalPortNumber + def sw = switches.all().random() + def portsArray = sw.getPorts()[-2,-1] + def lagPort = sw.getLagPort(portsArray as Set).create() when: "Delete LAG port via grpc" - grpc.deleteSwitchLogicalPort(northbound.getSwitch(sw.dpId).address, lagPort) + grpc.deleteSwitchLogicalPort(sw.getDetails().address, lagPort.logicalPortNumber) then: "System detects that LAG port is missed" - def lagPortMissingInfo = switchHelper.validateAndCollectFoundDiscrepancies(sw.dpId).get().logicalPorts.missing + def lagPortMissingInfo = sw.validate().logicalPorts.missing lagPortMissingInfo.size() == 1 - with (lagPortMissingInfo[0]) { + verifyAll(lagPortMissingInfo[0]) { type == LogicalPortType.LAG.toString() - logicalPortNumber == lagPort + logicalPortNumber == lagPort.logicalPortNumber physicalPorts.sort() == portsArray.sort() } when: "Synchronize the switch" - switchHelper.synchronizeAndCollectFixedDiscrepancies(sw.dpId) + sw.synchronizeAndCollectFixedDiscrepancies() then: "LAG port is reinstalled" - !switchHelper.validateAndCollectFoundDiscrepancies(sw.dpId).isPresent() + !sw.validateAndCollectFoundDiscrepancies().isPresent() } def "System is able to detect misconfigured LAG port"() { //system can't re-install misconfigured LAG port given: "A switch with a LAG port" - def sw = topology.getActiveSwitches().first() - def portsArray = topology.getAllowedPortsForSwitch(sw)[-3,-1] - def lagPort = switchHelper.createLagLogicalPort(sw.dpId, portsArray as Set).logicalPortNumber + def sw = switches.all().random() + def portsArray = sw.getPorts()[-3,-1] + def lagPort = sw.getLagPort(portsArray as Set).create() when: "Modify LAG port via grpc(delete, create with incorrect ports)" - def swAddress = northbound.getSwitch(sw.dpId).address - grpc.deleteSwitchLogicalPort(swAddress, lagPort) - def request = new LogicalPortDto(LogicalPortType.LAG, [portsArray[0]], lagPort) + def swAddress = sw.getDetails().address + grpc.deleteSwitchLogicalPort(swAddress, lagPort.logicalPortNumber) + def request = new LogicalPortDto(LogicalPortType.LAG, [portsArray[0]], lagPort.logicalPortNumber) grpc.createLogicalPort(swAddress, request) then: "System detects misconfigured LAG port" - !switchHelper.validateAndCollectFoundDiscrepancies(sw.dpId).get().logicalPorts.misconfigured.empty + !sw.validate().logicalPorts.misconfigured.empty } - def "Able to create/update LAG port with duplicated port numbers on the #sw.hwSwString switch"() { + def "Able to create/update LAG port with duplicated port numbers on the switch"() { given: "Switch and two ports" - def sw = getTopology().getActiveSwitches().get(0) - def testPorts = topology.getAllowedPortsForSwitch(sw).take(2) + def sw = switches.all().random() + def testPorts = sw.getPorts().take(2) assert testPorts.size > 1 when: "Create LAG port with duplicated port numbers" - def switchPortToCreate = testPorts.get(0) - def swAddress = northbound.getSwitch(sw.dpId).address + def switchPortToCreate = testPorts.first() + def swAddress = sw.getDetails().address def portListToCreate = [switchPortToCreate, switchPortToCreate] - def lagPortCreateResponse = switchHelper.createLagLogicalPort(sw.dpId, portListToCreate as Set) + def lagPort = sw.getLagPort(portListToCreate as Set).create() then: "Response shows that LAG port created successfully" - with(lagPortCreateResponse) { + verifyAll(lagPort) { logicalPortNumber > 0 - portNumbers == [switchPortToCreate] + portNumbers == [switchPortToCreate] as Set } - def lagPort = lagPortCreateResponse.logicalPortNumber and: "Request on user side shows that LAG port created" - with(northboundV2.getLagLogicalPort(sw.dpId)[0]) { - logicalPortNumber == lagPort + verifyAll(sw.getAllLogicalPorts()[0]) { + logicalPortNumber == lagPort.logicalPortNumber portNumbers == [switchPortToCreate] } and: "Created port exists in a list of all LAG ports from switch side (GRPC)" - with(grpc.getSwitchLogicalPortConfig(swAddress, lagPort)) { - logicalPortNumber == lagPort - name == "novi_lport" + lagPort.toString() + verifyAll(grpc.getSwitchLogicalPortConfig(swAddress, lagPort.logicalPortNumber)) { + logicalPortNumber == lagPort.logicalPortNumber + name == "novi_lport" + lagPort.logicalPortNumber.toString() portNumbers == [switchPortToCreate] type == LogicalPortType.LAG } @@ -449,24 +444,24 @@ occupied by other LAG group\(s\)./).matches(exc) def switchPortToUpdate = testPorts.get(1) def portListToUpdate = [switchPortToUpdate, switchPortToUpdate] def updatePayload = new LagPortRequest(portNumbers: portListToUpdate) - def lagPortUpdateResponse = northboundV2.updateLagLogicalPort(sw.dpId, lagPort, updatePayload) + def updatedLagPort = lagPort.update(updatePayload) then: "Response shows that LAG port updated successfully" - with(lagPortUpdateResponse) { - logicalPortNumber == lagPort - portNumbers == [switchPortToUpdate] + verifyAll(updatedLagPort) { + logicalPortNumber == lagPort.logicalPortNumber + portNumbers == [switchPortToUpdate] as Set } and: "Check on user side that LAG port updated successfully" - with(northboundV2.getLagLogicalPort(sw.dpId)[0]) { - logicalPortNumber == lagPort + verifyAll(sw.getAllLogicalPorts()[0]) { + logicalPortNumber == lagPort.logicalPortNumber portNumbers == [switchPortToUpdate] } and: "Check that LAG port updated successfully on switch side (via GRPC)" - with(grpc.getSwitchLogicalPortConfig(swAddress, lagPort)) { - logicalPortNumber == lagPort - name == "novi_lport" + lagPort.toString() + verifyAll(grpc.getSwitchLogicalPortConfig(swAddress, lagPort.logicalPortNumber)) { + logicalPortNumber == lagPort.logicalPortNumber + name == "novi_lport" + lagPort.logicalPortNumber.toString() portNumbers == [switchPortToUpdate] type == LogicalPortType.LAG } @@ -474,50 +469,48 @@ occupied by other LAG group\(s\)./).matches(exc) def "Able to create and delete single LAG port with lacp_reply=#data.portLacpReply"() { given: "A switch" - def sw = topology.getActiveSwitches().first() - def portsArrayCreate = topology.getAllowedPortsForSwitch(sw)[-2, -1] as Set + def sw = switches.all().random() + def portsArrayCreate = sw.getPorts()[-2, -1] as Set when: "Create a LAG port" - def createResponse = switchHelper.createLagLogicalPort( - sw.dpId, portsArrayCreate, data.portLacpReply) + def lagPort = sw.getLagPort(portsArrayCreate).create(data.portLacpReply) then: "Response reports successful creation of the LAG port" - with(createResponse) { + verifyAll(lagPort) { logicalPortNumber > 0 portNumbers.sort() == portsArrayCreate.sort() lacpReply == data.portLacpReply } - def portNumber = createResponse.logicalPortNumber and: "Correct rules and meters are on the switch" - assertSwitchHasCorrectLacpRulesAndMeters( - sw, data.mustContainCookies(portNumber), data.mustNotContainCookies(portNumber), data.mustContainLacpMeter) + sw.verifyLacpRulesAndMeters(data.mustContainCookies(lagPort), + data.mustNotContainCookies(lagPort), data.mustContainLacpMeter) when: "Delete the LAG port" - def deleteResponse = northboundV2.deleteLagLogicalPort(sw.dpId, portNumber) + def deleteResponse = lagPort.delete() then: "Response reports successful delete of the LAG port" - with(deleteResponse) { - logicalPortNumber == portNumber + verifyAll(deleteResponse) { + logicalPortNumber == lagPort.logicalPortNumber portNumbers.sort() == portsArrayCreate.sort() lacpReply == data.portLacpReply } and: "No LACP rules and meters on the switch" - assertSwitchHasCorrectLacpRulesAndMeters(sw, [], [LACP_COOKIE, getLagCookie(portNumber)], false) + sw.verifyLacpRulesAndMeters([], getLagCookies([lagPort], true), false) where: data << [ [ portLacpReply : false, - mustContainCookies : { int port -> [] }, - mustNotContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustContainCookies : { LagPort port -> [] }, + mustNotContainCookies : { LagPort port -> getLagCookies([port], true) }, mustContainLacpMeter : false ], [ portLacpReply : true, - mustContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, - mustNotContainCookies : { int port -> [] }, + mustContainCookies : { LagPort port -> getLagCookies([port], true) }, + mustNotContainCookies : { LagPort port -> [] }, mustContainLacpMeter : true ] ] @@ -525,46 +518,43 @@ occupied by other LAG group\(s\)./).matches(exc) def "Able to create and delete LAG port with #data.description"() { given: "A switch with LAG port" - def sw = topology.getActiveSwitches().first() - def physicalPortsOfLag1 = topology.getAllowedPortsForSwitch(sw)[-2, -1] as Set - def physicalPortsOfLag2 = topology.getAllowedPortsForSwitch(sw)[-4, -3] as Set - def portNumber1 = switchHelper.createLagLogicalPort( - sw.dpId, physicalPortsOfLag1 as Set, data.existingPortLacpReply).logicalPortNumber + def sw = switches.all().random() + def physicalPortsOfLag1 = sw.getPorts()[-2, -1] as Set + def physicalPortsOfLag2 = sw.getPorts()[-4, -3] as Set + def initialLagPort = sw.getLagPort(physicalPortsOfLag1).create(data.existingPortLacpReply) when: "Create a LAG port" - def createResponse = northboundV2.createLagLogicalPort( - sw.dpId, new LagPortRequest(physicalPortsOfLag2, data.newPortLacpReply)) + def lagPort = sw.getLagPort(physicalPortsOfLag2).create(data.newPortLacpReply) then: "Response reports successful creation of the LAG port" - with(createResponse) { + verifyAll(lagPort) { logicalPortNumber > 0 portNumbers.sort() == physicalPortsOfLag2.sort() lacpReply == data.newPortLacpReply } - def portNumber2 = createResponse.logicalPortNumber and: "Correct rules and meters are on the switch" - assertSwitchHasCorrectLacpRulesAndMeters( - sw, data.mustContainCookies(portNumber1, portNumber2), - data.mustNotContainCookies(portNumber1, portNumber2), data.mustContainLacpMeter) + sw.verifyLacpRulesAndMeters(data.mustContainCookies(initialLagPort, lagPort), + data.mustNotContainCookies(initialLagPort, lagPort), data.mustContainLacpMeter) when: "Delete created LAG port" - def deleteResponse = northboundV2.deleteLagLogicalPort(sw.dpId, portNumber2) + def deleteResponse = lagPort.delete() then: "Response reports successful delete of the LAG port" - with(deleteResponse) { - logicalPortNumber == portNumber2 + verifyAll(deleteResponse) { + logicalPortNumber == lagPort.logicalPortNumber portNumbers.sort() == physicalPortsOfLag2.sort() lacpReply == data.newPortLacpReply } and: "No LACP rules and meters of second LAG port on the switch" if (data.existingPortLacpReply) { // Switch must contain LACP rules and meter for first LAG port - assertSwitchHasCorrectLacpRulesAndMeters(sw, - [LACP_COOKIE, getLagCookie(portNumber1)], [getLagCookie(portNumber2)], true) + sw.verifyLacpRulesAndMeters(getLagCookies([initialLagPort], true), + getLagCookies([lagPort], false), true) + } else { // Switch must not contain any LACP rules and meter - assertSwitchHasCorrectLacpRulesAndMeters(sw, - [], [LACP_COOKIE, getLagCookie(portNumber1), getLagCookie(portNumber2), ], false) + sw.verifyLacpRulesAndMeters([], + getLagCookies([initialLagPort, lagPort], true), false) } where: @@ -573,34 +563,32 @@ occupied by other LAG group\(s\)./).matches(exc) description: "disabled LACP replies, near to LAG port with disabled LACP replies", existingPortLacpReply : false, newPortLacpReply : false, - mustContainCookies : { int oldPort, newPort -> [] }, - mustNotContainCookies : { int oldPort, newPort -> [ - LACP_COOKIE, getLagCookie(oldPort), getLagCookie(newPort)] }, + mustContainCookies : { LagPort oldPort, newPort -> [] }, + mustNotContainCookies : { LagPort oldPort, newPort -> getLagCookies([oldPort, newPort], true) }, mustContainLacpMeter : false ], [ description: "enabled LACP replies, near to LAG port with disabled LACP replies", existingPortLacpReply : false, newPortLacpReply : true, - mustContainCookies : { int oldPort, newPort -> [LACP_COOKIE, getLagCookie(newPort)] }, - mustNotContainCookies : { int oldPort, newPort -> [getLagCookie(oldPort)] }, + mustContainCookies : { LagPort oldPort, newPort -> getLagCookies([newPort], true) }, + mustNotContainCookies : { LagPort oldPort, newPort -> getLagCookies([oldPort], false) }, mustContainLacpMeter : true ], [ description: "disabled LACP replies, near to LAG port with enabled LACP replies", existingPortLacpReply : true, newPortLacpReply : false, - mustContainCookies : { int oldPort, newPort -> [LACP_COOKIE, getLagCookie(oldPort)] }, - mustNotContainCookies : { int oldPort, newPort -> [getLagCookie(newPort)] }, + mustContainCookies : { LagPort oldPort, newPort -> getLagCookies([oldPort], true) }, + mustNotContainCookies : { LagPort oldPort, newPort -> getLagCookies([newPort], false) }, mustContainLacpMeter : true ], [ description: "enabled LACP replies, near to LAG port with enabled LACP replies", existingPortLacpReply : true, newPortLacpReply : true, - mustContainCookies : { int oldPort, newPort -> [ - LACP_COOKIE, getLagCookie(oldPort), getLagCookie(newPort)] }, - mustNotContainCookies : { int oldPort, newPort -> [] }, + mustContainCookies : { LagPort oldPort, newPort -> getLagCookies([oldPort, newPort], true) }, + mustNotContainCookies : { LagPort oldPort, newPort -> [] }, mustContainLacpMeter : true ] ] @@ -608,34 +596,31 @@ occupied by other LAG group\(s\)./).matches(exc) def "Able to update #data.description for single LAG port"() { given: "A switch" - def sw = topology.getActiveSwitches().first() - def physicalPortsOfCreatedLag = topology.getAllowedPortsForSwitch(sw)[-2, -1] as Set - def physicalPortsOfUpdatedLag = topology.getAllowedPortsForSwitch(sw)[-3, -2] as Set + def sw = switches.all().random() + def physicalPortsOfCreatedLag = sw.getPorts()[-2, -1] as Set + def physicalPortsOfUpdatedLag = sw.getPorts()[-3, -2] as Set and: "A LAG port" - def createResponse = switchHelper.createLagLogicalPort( - sw.dpId, physicalPortsOfCreatedLag, data.oldlacpReply) - with(createResponse) { + def lagPort = sw.getLagPort(physicalPortsOfCreatedLag).create(data.oldlacpReply) + verifyAll(lagPort) { assert logicalPortNumber > 0 assert portNumbers.sort() == physicalPortsOfCreatedLag.sort() } - def portNumber = createResponse.logicalPortNumber when: "Update the LAG port" def updatedPhysicalPorts = data.updatePorts ? physicalPortsOfUpdatedLag : physicalPortsOfCreatedLag - def updateResponse = northboundV2.updateLagLogicalPort( - sw.dpId, portNumber, new LagPortRequest(updatedPhysicalPorts, data.newlacpReply)) + def updatedLagPort = lagPort.update(new LagPortRequest(updatedPhysicalPorts, data.newlacpReply)) then: "Response reports successful update of the LAG port" - with(updateResponse) { - logicalPortNumber == portNumber + verifyAll(updatedLagPort) { + logicalPortNumber == lagPort.logicalPortNumber portNumbers.sort() == updatedPhysicalPorts.sort() lacpReply == data.newlacpReply } and: "Correct rules and meters are on the switch" - assertSwitchHasCorrectLacpRulesAndMeters( - sw, data.mustContainCookies(portNumber), data.mustNotContainCookies(portNumber), data.mustContainLacpMeter) + sw.verifyLacpRulesAndMeters(data.mustContainCookies(lagPort), + data.mustNotContainCookies(lagPort), data.mustContainLacpMeter) where: data << [ @@ -644,8 +629,8 @@ occupied by other LAG group\(s\)./).matches(exc) oldlacpReply : false, newlacpReply : false, updatePorts: true, - mustContainCookies : { int port -> [] }, - mustNotContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustContainCookies : { LagPort port -> [] }, + mustNotContainCookies : { LagPort port -> getLagCookies([port], true) }, mustContainLacpMeter : false, ], [ @@ -653,8 +638,8 @@ occupied by other LAG group\(s\)./).matches(exc) oldlacpReply : true, newlacpReply : true, updatePorts: false, - mustContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, - mustNotContainCookies : { int port -> [] }, + mustContainCookies : { LagPort port -> getLagCookies([port], true) }, + mustNotContainCookies : { LagPort port -> [] }, mustContainLacpMeter : true, ], [ @@ -662,8 +647,8 @@ occupied by other LAG group\(s\)./).matches(exc) oldlacpReply : false, newlacpReply : true, updatePorts: false, - mustContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, - mustNotContainCookies : { int port -> [] }, + mustContainCookies : { LagPort port -> getLagCookies([port], true) }, + mustNotContainCookies : { LagPort port -> [] }, mustContainLacpMeter : true, ], [ @@ -671,8 +656,8 @@ occupied by other LAG group\(s\)./).matches(exc) oldlacpReply : true, newlacpReply : false, updatePorts: false, - mustContainCookies : { int port -> [] }, - mustNotContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustContainCookies : { LagPort port -> [] }, + mustNotContainCookies : { LagPort port -> getLagCookies([port], true) }, mustContainLacpMeter : false, ], [ @@ -680,8 +665,8 @@ occupied by other LAG group\(s\)./).matches(exc) oldlacpReply : false, newlacpReply : true, updatePorts: true, - mustContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, - mustNotContainCookies : { int port -> [] }, + mustContainCookies : { LagPort port -> getLagCookies([port], true) }, + mustNotContainCookies : { LagPort port -> [] }, mustContainLacpMeter : true, ], [ @@ -689,8 +674,8 @@ occupied by other LAG group\(s\)./).matches(exc) oldlacpReply : true, newlacpReply : false, updatePorts: true, - mustContainCookies : { int port -> [] }, - mustNotContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustContainCookies : { LagPort port -> [] }, + mustNotContainCookies : { LagPort port -> getLagCookies([port], true) }, mustContainLacpMeter : false, ] ] @@ -698,41 +683,36 @@ occupied by other LAG group\(s\)./).matches(exc) def "Able to update #data.description near to existing LAG port with lacp_reply=#data.existingPortLacpReply"() { given: "A switch" - def sw = topology.getActiveSwitches().first() - def physicalPortsOfLag1 = topology.getAllowedPortsForSwitch(sw)[-2, -1] as Set - def physicalPortsOfCreatedLag2 = topology.getAllowedPortsForSwitch(sw)[-4, -3] as Set - def physicalPortsOfUpdatedLag2 = topology.getAllowedPortsForSwitch(sw)[-5, -4] as Set + def sw = switches.all().random() + def physicalPortsOfLag1 = sw.getPorts()[-2, -1] as Set + def physicalPortsOfCreatedLag2 = sw.getPorts()[-4, -3] as Set + def physicalPortsOfUpdatedLag2 = sw.getPorts()[-5, -4] as Set and: "LAG port 1" - def portNumber1 = switchHelper.createLagLogicalPort( - sw.dpId, physicalPortsOfLag1, data.existingPortLacpReply).logicalPortNumber + def lagPort1 = sw.getLagPort(physicalPortsOfLag1).create(data.existingPortLacpReply) and: "LAG port 2" - def createResponse = northboundV2.createLagLogicalPort( - sw.dpId, new LagPortRequest(physicalPortsOfCreatedLag2, data.oldlacpReply)) - with(createResponse) { + def lagPort2 = sw.getLagPort(physicalPortsOfCreatedLag2).create(data.oldlacpReply) + verifyAll(lagPort2) { assert logicalPortNumber > 0 assert portNumbers.sort() == physicalPortsOfCreatedLag2.sort() assert lacpReply == data.oldlacpReply } - def portNumber2 = createResponse.logicalPortNumber when: "Update the LAG port" def updatedPhysicalPorts = data.updatePorts ? physicalPortsOfUpdatedLag2 : physicalPortsOfCreatedLag2 - def updateResponse = northboundV2.updateLagLogicalPort( - sw.dpId, portNumber2, new LagPortRequest(updatedPhysicalPorts, data.newlacpReply)) + def updatedLagPort2 = lagPort2.update(new LagPortRequest(updatedPhysicalPorts, data.newlacpReply)) then: "Response reports successful update of the LAG port" - with(updateResponse) { - logicalPortNumber == portNumber2 + verifyAll(updatedLagPort2) { + logicalPortNumber == lagPort2.logicalPortNumber portNumbers.sort() == updatedPhysicalPorts.sort() lacpReply == data.newlacpReply } and: "Correct rules and meters are on the switch" - assertSwitchHasCorrectLacpRulesAndMeters( - sw, data.mustContainCookies(portNumber1, portNumber2), - data.mustNotContainCookies(portNumber1, portNumber2), data.mustContainLacpMeter) + sw.verifyLacpRulesAndMeters(data.mustContainCookies(lagPort1, lagPort2), + data.mustNotContainCookies(lagPort1, lagPort2), data.mustContainLacpMeter) where: data << [ @@ -742,8 +722,8 @@ occupied by other LAG group\(s\)./).matches(exc) oldlacpReply : false, newlacpReply : false, updatePorts: true, - mustContainCookies : { int port1, port2 -> [] }, - mustNotContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1), getLagCookie(port2)] }, + mustContainCookies : { LagPort port1, port2 -> [] }, + mustNotContainCookies : { LagPort port1, port2 -> getLagCookies([port1, port2], true) }, mustContainLacpMeter : false, ], [ @@ -752,8 +732,8 @@ occupied by other LAG group\(s\)./).matches(exc) oldlacpReply : true, newlacpReply : true, updatePorts: true, - mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port2)] }, - mustNotContainCookies : { int port1, port2 -> [getLagCookie(port1)] }, + mustContainCookies : { LagPort port1, port2 -> getLagCookies([port2], true) }, + mustNotContainCookies : { LagPort port1, port2 -> getLagCookies([port1], false) }, mustContainLacpMeter : true, ], [ @@ -762,8 +742,8 @@ occupied by other LAG group\(s\)./).matches(exc) oldlacpReply : false, newlacpReply : true, updatePorts: false, - mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port2)] }, - mustNotContainCookies : { int port1, port2 -> [getLagCookie(port1)] }, + mustContainCookies : { LagPort port1, port2 -> getLagCookies([port2], true) }, + mustNotContainCookies : { LagPort port1, port2 -> getLagCookies([port1], false)}, mustContainLacpMeter : true, ], [ @@ -772,8 +752,8 @@ occupied by other LAG group\(s\)./).matches(exc) oldlacpReply : true, newlacpReply : false, updatePorts: false, - mustContainCookies : { int port1, port2 -> [] }, - mustNotContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1), getLagCookie(port2)] }, + mustContainCookies : { LagPort port1, port2 -> [] }, + mustNotContainCookies : { LagPort port1, port2 -> getLagCookies([port1, port2], true) }, mustContainLacpMeter : false, ], [ @@ -782,8 +762,8 @@ occupied by other LAG group\(s\)./).matches(exc) oldlacpReply : false, newlacpReply : false, updatePorts: true, - mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1)] }, - mustNotContainCookies : { int port1, port2 -> [getLagCookie(port2)] }, + mustContainCookies : { LagPort port1, port2 -> getLagCookies([port1], true) }, + mustNotContainCookies : { LagPort port1, port2 -> getLagCookies([port2], false)}, mustContainLacpMeter : true, ], [ @@ -792,8 +772,8 @@ occupied by other LAG group\(s\)./).matches(exc) oldlacpReply : true, newlacpReply : true, updatePorts: true, - mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1), getLagCookie(port2)] }, - mustNotContainCookies : { int port1, port2 -> [] }, + mustContainCookies : { LagPort port1, port2 -> getLagCookies([port1, port2], true) }, + mustNotContainCookies : { LagPort port1, port2 -> [] }, mustContainLacpMeter : true, ], [ @@ -802,8 +782,8 @@ occupied by other LAG group\(s\)./).matches(exc) oldlacpReply : false, newlacpReply : true, updatePorts: false, - mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1), getLagCookie(port2)] }, - mustNotContainCookies : { int port1, port2 -> [] }, + mustContainCookies : { LagPort port1, port2 -> getLagCookies([port1, port2], true) }, + mustNotContainCookies : { LagPort port1, port2 -> [] }, mustContainLacpMeter : true, ], [ @@ -812,78 +792,56 @@ occupied by other LAG group\(s\)./).matches(exc) oldlacpReply : true, newlacpReply : false, updatePorts: false, - mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1)] }, - mustNotContainCookies : { int port1, port2 -> [getLagCookie(port2)] }, + mustContainCookies : { LagPort port1, port2 -> getLagCookies([port1], true) }, + mustNotContainCookies : { LagPort port1, port2 -> getLagCookies([port2], false) }, mustContainLacpMeter : true, ] ] } - private void assertSwitchHasCorrectLacpRulesAndMeters( - Switch sw, mustContainCookies, mustNotContainsCookies, mustContainLacpMeter) { - // validate switch - !switchHelper.validateAndCollectFoundDiscrepancies(sw.dpId).isPresent() - - // check cookies - def hexCookies = northbound.getSwitchRules(sw.dpId).flowEntries*.cookie.collect { Cookie.toString(it) } - assert hexCookies.containsAll(mustContainCookies) - assert hexCookies.intersect(mustNotContainsCookies).isEmpty() - - // check meters - def meters = northbound.getAllMeters(sw.dpId).meterEntries*.meterId - if (mustContainLacpMeter) { - assert LACP_REPLY_METER_ID.value in meters - } else { - assert LACP_REPLY_METER_ID.value !in meters - } - } - def "Unable decrease bandwidth on LAG port lower than connected flows bandwidth sum"() { given: "Flows on a LAG port with switch ports" - def switchPair = switchPairs.all().random() - def testPorts = topology.getAllowedPortsForSwitch(switchPair.src).takeRight(2).sort() + def switchPair = switchPairs.all().withoutWBSwitch().random() + def srcSw = switches.all().findSpecific(switchPair.src.dpId) + def testPorts = srcSw.getPorts().takeRight(2).sort() assert testPorts.size > 1 - def maximumBandwidth = testPorts.sum { northbound.getPort(switchPair.src.dpId, it).currentSpeed } - def lagPort = switchHelper.createLagLogicalPort(switchPair.src.dpId, testPorts as Set).logicalPortNumber - def flow = flowFactory.getBuilder(switchPair) - .withSourcePort(lagPort) + def maximumBandwidth = testPorts.sum { srcSw.getPort(it).retrieveDetails().currentSpeed } + def lagPort = srcSw.getLagPort(testPorts as Set).create() + + flowFactory.getBuilder(switchPair) + .withSourcePort(lagPort.logicalPortNumber) .withBandwidth(maximumBandwidth as Long) .build().create() when: "Decrease LAG port bandwidth by deleting one port to make it lower than connected flows bandwidth sum" def updatePayload = new LagPortRequest(portNumbers: [testPorts.get(0)]) - northboundV2.updateLagLogicalPort(switchPair.src.dpId, lagPort, updatePayload) + lagPort.update(updatePayload) then: "Human readable error is returned" def exc = thrown(HttpClientErrorException) new LagNotUpdatedExpectedError( - switchPair.getSrc().getDpId(), lagPort, ~/Not enough bandwidth for LAG port $lagPort./).matches(exc) + srcSw.switchId, lagPort.logicalPortNumber, ~/Not enough bandwidth for LAG port $lagPort.logicalPortNumber./).matches(exc) then: "No bandwidth changed for LAG port and all connected ports are in place" - with(northboundV2.getLagLogicalPort(switchPair.src.dpId)[0]) { - logicalPortNumber == lagPort + verifyAll(srcSw.getAllLogicalPorts()[0]) { + logicalPortNumber == lagPort.logicalPortNumber portNumbers == testPorts } } def "Able to delete LAG port if it is already removed from switch"() { given: "A switch with a LAG port" - def sw = topology.getActiveSwitches().first() - def portsArray = topology.getAllowedPortsForSwitch(sw)[-2,-1] - def lagPort = switchHelper.createLagLogicalPort(sw.dpId, portsArray as Set).logicalPortNumber + def sw = switches.all().random() + def portsArray = sw.getPorts()[-2,-1] + def lagPort = sw.getLagPort(portsArray as Set).create() when: "Delete LAG port via grpc" - grpc.deleteSwitchLogicalPort(northbound.getSwitch(sw.dpId).address, lagPort) + grpc.deleteSwitchLogicalPort(sw.getDetails().address, lagPort.logicalPortNumber) then: "Able to delete LAG port from switch with no exception" - def deleteResponse = northboundV2.deleteLagLogicalPort(sw.dpId, lagPort) - - with(deleteResponse) { - logicalPortNumber == lagPort + def deleteResponse = lagPort.delete() + verifyAll(deleteResponse) { + logicalPortNumber == lagPort.logicalPortNumber portNumbers.sort() == portsArray.sort() } } - - def getLagCookie(portNumber) { - new PortColourCookie(CookieType.LACP_REPLY_INPUT, portNumber).toString() - } } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/MetersSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/MetersSpec.groovy index a3b655cee85..2fb2da4a1b1 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/MetersSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/MetersSpec.groovy @@ -6,6 +6,8 @@ import static org.openkilda.functionaltests.extension.tags.Tag.HARDWARE 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.model.SwitchExtended.verifyBurstSizeOnWb5164 +import static org.openkilda.functionaltests.helpers.model.SwitchExtended.verifyRateSizeOnWb5164 import static org.openkilda.functionaltests.model.switches.Manufacturer.CENTEC import static org.openkilda.functionaltests.model.switches.Manufacturer.NOVIFLOW import static org.openkilda.functionaltests.model.switches.Manufacturer.OVS @@ -27,16 +29,15 @@ 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.FlowRuleEntity -import org.openkilda.functionaltests.helpers.model.SwitchRulesFactory +import org.openkilda.functionaltests.helpers.model.SwitchExtended +import org.openkilda.functionaltests.model.switches.Manufacturer import org.openkilda.messaging.info.meter.MeterEntry -import org.openkilda.messaging.info.rule.FlowEntry import org.openkilda.model.SwitchId import org.openkilda.model.cookie.Cookie import org.openkilda.model.cookie.CookieBase.CookieType import org.openkilda.testing.Constants import org.openkilda.testing.model.topology.TopologyDefinition.Switch -import groovy.transform.Memoized import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.web.client.HttpClientErrorException @@ -61,10 +62,6 @@ class MetersSpec extends HealthCheckSpecification { @Shared FlowFactory flowFactory - @Autowired - @Shared - SwitchRulesFactory switchRulesFactory - @Value('${burst.coefficient}') double burstCoefficient @@ -75,62 +72,56 @@ class MetersSpec extends HealthCheckSpecification { @Tags([TOPOLOGY_DEPENDENT, SMOKE, SMOKE_SWITCHES]) @IterationTag(tags = [HARDWARE], iterationNameRegex = NOT_OVS_REGEX) def "Able to delete a meter from a #switchType switch"() { - assumeTrue(switches as boolean, "Unable to find required switches in topology") - setup: "Select a #switchType switch and retrieve default meters" - def sw = switches.first() - def defaultMeters = northbound.getAllMeters(sw.dpId) + def sw = switches.all().withManufacturer(switchType).random() + def defaultMeters = sw.metersManager.getMeters() when: "A flow is created and its meter is deleted" - def flow = flowFactory.getRandom(sw, sw) - def meterToDelete = northbound.getAllMeters(sw.dpId).meterEntries.find { - !defaultMeters.meterEntries*.meterId.contains(it.meterId) + def flow = flowFactory.getSingleSwRandom(sw) + def meterToDelete = sw.metersManager.getMeters().find { + !defaultMeters.meterId.contains(it.meterId) }.meterId - def deleteResult = northbound.deleteMeter(sw.dpId, meterToDelete) + def deleteResult = sw.metersManager.delete(meterToDelete) then: "Delete operation should be successful" deleteResult.deleted - !northbound.getAllMeters(sw.dpId).meterEntries.find { it.meterId == meterToDelete } + !sw.metersManager.getMeters().find { it.meterId == meterToDelete } when: "Delete the flow" flow.delete() then: "No excessive meters are installed on the switch" Wrappers.wait(WAIT_OFFSET) { - assert defaultMeters.meterEntries.sort() == northbound.getAllMeters(sw.dpId).meterEntries.sort() + assert defaultMeters.sort() == sw.metersManager.getMeters().sort() } where: - switchType | switches - "Centec" | getCentecSwitches() - "Noviflow" | getNoviflowSwitches() - "Noviflow(Wb5164)" | getNoviflowWb5164() - "OVS" | getVirtualSwitches() + switchType << [CENTEC, NOVIFLOW, WB5164, OVS] } @Tags([TOPOLOGY_DEPENDENT]) @IterationTag(tags = [HARDWARE], iterationNameRegex = NOT_OVS_REGEX) def "Unable to delete a meter with invalid ID=#meterId on a #switchType switch"() { - assumeTrue(switches as boolean, "Unable to find required switches in topology") + setup: "Select a #switchType switch" + def sw = switches.all().withManufacturer(switchType).random() when: "Try to delete meter with invalid ID" - SwitchId swId = switches[0].dpId - northbound.deleteMeter(swId, meterId) + sw.metersManager.delete(meterId) then: "Got BadRequest because meter ID is invalid" def exc = thrown(HttpClientErrorException) - new MeterExpectedError("Meter id must be positive.", ~/$swId/).matches(exc) + new MeterExpectedError("Meter id must be positive.", ~/$sw.switchId/).matches(exc) where: - meterId | switches | switchType - -1 | getNoviflowSwitches() | "Noviflow" - 0 | getNoviflowSwitches() | "Noviflow" - -1 | getCentecSwitches() | "Centec" - 0 | getCentecSwitches() | "Centec" - -1 | getNoviflowWb5164() | "Noviflow(Wb5164)" - 0 | getNoviflowWb5164() | "Noviflow(Wb5164)" - -1 | getVirtualSwitches() | "OVS" - 0 | getVirtualSwitches() | "OVS" + meterId | switchType + -1 | NOVIFLOW + 0 | NOVIFLOW + -1 | CENTEC + 0 | CENTEC + -1 | WB5164 + 0 | WB5164 + -1 | OVS + 0 | OVS } /** @@ -138,45 +129,45 @@ class MetersSpec extends HealthCheckSpecification { * System should recalculate the PKTPS value to KBPS on Centec switches. */ @Tags([HARDWARE, SMOKE_SWITCHES]) - def "Default meters should express bandwidth in kbps re-calculated from pktps on Centec #sw.hwSwString"() { + def "Default meters should express bandwidth in kbps re-calculated from pktps on Centec #sw.hwSwString()"() { expect: "Only the default meters should be present on the switch" - def meters = northbound.getAllMeters(sw.dpId) - assert meters.meterEntries.size() == 2 - assert meters.meterEntries.each { + def meters = sw.metersManager.getMeters() + assert meters.size() == 2 + assert meters.each { assert it.rate == Math.max((long) (DISCO_PKT_RATE * DISCO_PKT_SIZE * 8 / 1024L), MIN_RATE_KBPS) } //unable to use #getExpectedBurst. For Centects there's special burst due to KBPS - assert meters.meterEntries.every { it.burstSize == (long) ((DISCO_PKT_BURST * DISCO_PKT_SIZE * 8) / 1024) } - assert meters.meterEntries.every(defaultMeters) - assert meters.meterEntries.every { ["KBPS", "BURST", "STATS"].containsAll(it.flags) } - assert meters.meterEntries.every { it.flags.size() == 3 } + assert meters.every { it.burstSize == (long) ((DISCO_PKT_BURST * DISCO_PKT_SIZE * 8) / 1024) } + assert meters.every(defaultMeters) + assert meters.every { ["KBPS", "BURST", "STATS"].containsAll(it.flags) } + assert meters.every { it.flags.size() == 3 } where: - sw << (getCentecSwitches().unique { it.description } + sw << (switches.all().withManufacturer(CENTEC).unique() ?: assumeTrue(false, "Unable to find Centec switches in topology")) } @Tags([HARDWARE, SMOKE_SWITCHES]) - def "Default meters should express bandwidth in pktps on Noviflow #sw.hwSwString"() { + def "Default meters should express bandwidth in pktps on Noviflow #sw.hwSwString()"() { //TODO: Research how to calculate burstSize on OpenVSwitch in this case // now burstSize is equal to 4096, rate == 200 expect: "Only the default meters should be present on the switch" - def meters = northbound.getAllMeters(sw.dpId) - meters.meterEntries*.meterId.sort() == sw.defaultMeters.sort() - meters.meterEntries.each { assert it.burstSize == switchHelper.getExpectedBurst(sw.dpId, it.rate) } - meters.meterEntries.each { assert ["PKTPS", "BURST", "STATS"].containsAll(it.flags) } - meters.meterEntries.each { assert it.flags.size() == 3 } + def meters = sw.metersManager.getMeters() + meters.meterId.sort() == sw.collectDefaultMeters().sort() + meters.each { assert it.burstSize == sw.getExpectedBurst(it.rate) } + meters.each { assert ["PKTPS", "BURST", "STATS"].containsAll(it.flags) } + meters.each { assert it.flags.size() == 3 } where: - sw << (getNoviflowSwitches().unique { it.nbFormat().hardware + it.nbFormat().software } + sw << (switches.all().withManufacturer(NOVIFLOW).uniqueByHw() ?: assumeTrue(false, "Unable to find Noviflow switch in topology" )) } @Tags([HARDWARE, SMOKE_SWITCHES]) - def "Default meters should express bandwidth in kbps on Noviflow Wb5164 #sw.hwSwString"() { + def "Default meters should express bandwidth in kbps on Noviflow Wb5164 #sw.hwSwString()"() { expect: "Only the default meters should be present on the switch" - def meters = northbound.getAllMeters(sw.dpId) - meters.meterEntries*.meterId.sort() == sw.defaultMeters.sort() + def meters = sw.metersManager.getMeters() + meters.meterId.sort() == sw.collectDefaultMeters().sort() /* burstSizre doesn't depend on rate on WB switches, it should be calculated by formula burstSize * packet_size * 8 / 1024, where burstSize - 4096, packet_size: lldp - 300, arp - 100, unicast/multicast - 250 */ @@ -187,7 +178,7 @@ class MetersSpec extends HealthCheckSpecification { createMeterIdForDefaultRule(LLDP_POST_INGRESS_VXLAN_COOKIE).getValue(), createMeterIdForDefaultRule(LLDP_POST_INGRESS_ONE_SWITCH_COOKIE).getValue()] //16, 17, 18 - meters.meterEntries.each { meter -> + meters.each { meter -> if (meter.meterId in arpMeters) { verifyBurstSizeOnWb5164(meter.burstSize, Math.max((long) (DISCO_PKT_BURST * 100 * 8 / 1024L), MIN_RATE_KBPS)) @@ -199,11 +190,11 @@ class MetersSpec extends HealthCheckSpecification { Math.max((long) (DISCO_PKT_BURST * 250 * 8 / 1024L), MIN_RATE_KBPS)) } } - meters.meterEntries.each { assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) } - meters.meterEntries.each { assert it.flags.size() == 3 } + meters.each { assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) } + meters.each { assert it.flags.size() == 3 } where: - sw << (getNoviflowWb5164().unique { it.description } ?: + sw << (switches.all().withManufacturer(WB5164).unique() ?: assumeTrue(false, "Unable to find Noviflow Wb5164 switches in topology")) } @@ -211,23 +202,21 @@ class MetersSpec extends HealthCheckSpecification { @IterationTag(tags = [HARDWARE], iterationNameRegex = NOT_OVS_REGEX) def "Meters are created/deleted when creating/deleting a single-switch flow with ignore_bandwidth=#ignoreBandwidth \ on a #switchType switch"() { - assumeTrue(switches as boolean, "Unable to find required switches in topology") - given: "A #switchType switch with OpenFlow 1.3 support" - def sw = switches.first() + def sw = switches.all().withManufacturer(switchType).random() when: "Get default meters from the switch" - def defaultMeters = northbound.getAllMeters(sw.dpId) + def defaultMeters = sw.metersManager.getMeters() assert defaultMeters and: "Create a single-switch flow" - def flow = flowFactory.getBuilder(sw, sw) + def flow = flowFactory.getSingleSwBuilder(sw) .withIgnoreBandwidth(ignoreBandwidth).build() .create() then: "New meters should appear after flow setup" - def newMeters = northbound.getAllMeters(sw.dpId) - def newMeterEntries = newMeters.meterEntries.findAll { !defaultMeters.meterEntries.contains(it) } + def newMeters = sw.metersManager.getMeters() + def newMeterEntries = newMeters.findAll { !defaultMeters.contains(it) } newMeterEntries.size() == 2 and: "All new meters should have KBPS, BURST and STATS flags installed" @@ -237,7 +226,7 @@ on a #switchType switch"() { newMeterEntries*.rate.each { verifyRateSizeOnWb5164(it, flow.maximumBandwidth) } and: "Switch validation shows no discrepancies in meters" - !switchHelper.synchronizeAndCollectFixedDiscrepancies(sw.dpId).isPresent() + !sw.synchronizeAndCollectFixedDiscrepancies().isPresent() and: "Flow validation shows no discrepancies in meters" flow.validateAndCollectDiscrepancies().isEmpty() @@ -247,52 +236,46 @@ on a #switchType switch"() { then: "New meters should disappear from the switch" Wrappers.wait(WAIT_OFFSET) { - def newestMeters = northbound.getAllMeters(sw.dpId) - newestMeters.meterEntries.containsAll(defaultMeters.meterEntries) - newestMeters.meterEntries.size() == defaultMeters.meterEntries.size() + def newestMeters = sw.metersManager.getMeters() + newestMeters.containsAll(defaultMeters) + newestMeters.size() == defaultMeters.size() } where: - switchType | switches | ignoreBandwidth - "Centec" | getCentecSwitches() | false - "Centec" | getCentecSwitches() | true - "Noviflow" | getNoviflowSwitches() | false - "Noviflow" | getNoviflowSwitches() | true - "Noviflow(Wb5164)" | getNoviflowWb5164() | false - "Noviflow(Wb5164)" | getNoviflowWb5164() | true - "OVS" | getVirtualSwitches() | false - "OVS" | getVirtualSwitches() | true + switchType | ignoreBandwidth + CENTEC | false + CENTEC | true + NOVIFLOW | false + NOVIFLOW | true + WB5164 | false + WB5164 | true + OVS | false + OVS | true } @Tags([TOPOLOGY_DEPENDENT]) @IterationTag(tags = [HARDWARE], iterationNameRegex = NOT_OVS_REGEX) def "Meters are not created when creating a single-switch flow with maximum_bandwidth=0 on a #switchType switch"() { - assumeTrue(switches as boolean, "Unable to find required switches in topology") - given: "A #switchType switch with OpenFlow 1.3 support" - def sw = switches.first() + def sw = switches.all().withManufacturer(switchType).random() when: "Get default meters from the switch" - def defaultMeters = northbound.getAllMeters(sw.dpId) + def defaultMeters = sw.metersManager.getMeters() assert defaultMeters and: "Create a single-switch flow with maximum_bandwidth=0" - flowFactory.getBuilder(sw, sw) + flowFactory.getSingleSwBuilder(sw) .withBandwidth(0) .withIgnoreBandwidth(true).build() .create() then: "Ony default meters should be present on the switch and new meters should not appear after flow setup" - def newMeters = northbound.getAllMeters(sw.dpId) - def newMeterEntries = newMeters.meterEntries.findAll { !defaultMeters.meterEntries.contains(it) } + def newMeters = sw.metersManager.getMeters() + def newMeterEntries = newMeters.findAll { !defaultMeters.contains(it) } newMeterEntries.empty where: - switchType | switches - "Centec" | getCentecSwitches() - "Noviflow" | getNoviflowSwitches() - "Noviflow(Wb5164)" | getNoviflowWb5164() - "OVS" | getVirtualSwitches() + switchType << [CENTEC, NOVIFLOW, WB5164, OVS] } @Tags([TOPOLOGY_DEPENDENT]) @@ -306,14 +289,16 @@ meters in flow rules at all (#srcSwitch - #dstSwitch flow)"() { def flow = flowFactory.getRandom(switchPair) then: "The source and destination switches have only one meter in the flow's ingress rule" - def srcSwFlowMeters = northbound.getAllMeters(flow.source.switchId).meterEntries.findAll(flowMeters) - def dstSwFlowMeters = northbound.getAllMeters(flow.destination.switchId).meterEntries.findAll(flowMeters) + def srcSwToInteract = switches.all().findSpecific(switchPair.src.dpId) + def dstSwToInteract = switches.all().findSpecific(switchPair.dst.dpId) + def srcSwFlowMeters = srcSwToInteract.metersManager.getMeters().findAll(flowMeters) + def dstSwFlowMeters = dstSwToInteract.metersManager.getMeters().findAll(flowMeters) srcSwFlowMeters.size() == 1 dstSwFlowMeters.size() == 1 - def srcSwitchRules = switchRulesFactory.get(flow.source.switchId).getRules().findAll { !Cookie.isDefaultRule(it.cookie) } - def dstSwitchRules = switchRulesFactory.get(flow.destination.switchId).getRules().findAll { !Cookie.isDefaultRule(it.cookie) } + def srcSwitchRules = srcSwToInteract.rulesManager.getRules().findAll { !Cookie.isDefaultRule(it.cookie) } + def dstSwitchRules = dstSwToInteract.rulesManager.getRules().findAll { !Cookie.isDefaultRule(it.cookie) } def srcSwIngressFlowRules = srcSwitchRules.findAll { it.match.inPort == flow.source.portNumber.toString() } assert srcSwIngressFlowRules.size() == 2 //shared + simple ingress @@ -351,30 +336,29 @@ meters in flow rules at all (#srcSwitch - #dstSwitch flow)"() { .collect { [it.srcSwitch, it.dstSwitch] }.flatten().unique() as List flowInvolvedSwitches[1..-2].findAll { it.ofVersion != "OF_12" }.each { sw -> - assert northbound.getAllMeters(sw.dpId).meterEntries.findAll(flowMeters).empty - def flowRules = switchRulesFactory.get(sw.dpId).getRules().findAll { !(it.cookie in sw.defaultCookies) } + def swToInteract =switches.all().findSpecific(sw.dpId) + assert swToInteract.metersManager.getMeters().findAll(flowMeters).empty + def defaultCookies = swToInteract.collectDefaultCookies() + def flowRules = swToInteract.rulesManager.getRules().findAll { !(it.cookie in defaultCookies) } flowRules.each { assert !it.instructions.goToMeter } } where: srcSwitch | dstSwitch - CENTEC | CENTEC - NOVIFLOW | NOVIFLOW - CENTEC | NOVIFLOW - WB5164 | WB5164 - OVS | OVS + CENTEC | CENTEC + NOVIFLOW | NOVIFLOW + CENTEC | NOVIFLOW + WB5164 | WB5164 + OVS | OVS } @Tags([TOPOLOGY_DEPENDENT, SMOKE_SWITCHES]) @IterationTag(tags = [HARDWARE], iterationNameRegex = NOT_OVS_REGEX) - def "Meter burst size is correctly set on #data.switchType switches for #flowRate flow rate"() { + def "Meter burst size is correctly set on #switchType switches for #flowRate flow rate"() { setup: "A single-switch flow with #flowRate kbps bandwidth is created on OpenFlow 1.3 compatible switch" - def switches = data.switches - assumeTrue(switches as boolean, "Unable to find required switches in topology") - - def sw = switches.first() - def defaultMeters = northbound.getAllMeters(sw.dpId) - def flow = flowFactory.getBuilder(sw, sw) + def sw = switches.all().withManufacturer(switchType as Manufacturer).random() + def defaultMeters = sw.metersManager.getMeters() + def flow = flowFactory.getSingleSwBuilder(sw) .withBandwidth(100).build() .create() @@ -382,32 +366,25 @@ meters in flow rules at all (#srcSwitch - #dstSwitch flow)"() { flow.update(flow.tap { it.maximumBandwidth = flowRate as Long }) then: "New meters should be installed on the switch" - def newMeters = northbound.getAllMeters(sw.dpId).meterEntries.findAll { - !defaultMeters.meterEntries.contains(it) - } + def newMeters = sw.metersManager.getMeters().findAll { !defaultMeters.contains(it) } assert newMeters.size() == 2 and: "New meters rate should be equal to flow bandwidth" newMeters*.rate.each { assert it == flowRate } and: "New meters burst size matches the expected value for given switch model" - newMeters*.burstSize.each { assert it == switchHelper.getExpectedBurst(sw.dpId, flowRate) } + newMeters*.burstSize.each { assert it == sw.getExpectedBurst(flowRate as long)} and: "Switch validation shows no discrepancies in meters" - !switchHelper.synchronizeAndCollectFixedDiscrepancies(sw.dpId).isPresent() + !sw.synchronizeAndCollectFixedDiscrepancies().isPresent() and: "Flow validation shows no discrepancies in meters" flow.validateAndCollectDiscrepancies().isEmpty() where: - [flowRate, data] << [ + [flowRate, switchType] << [ [150, 1000, 1024, 5120, 10240, 2480, 960000], - [ - ["switchType": "Noviflow", - "switches" : getNoviflowSwitches()], - ["switchType": "OVS", - "switches" : getVirtualSwitches()] - ] + [OVS, NOVIFLOW] ].combinations() } @@ -415,24 +392,21 @@ meters in flow rules at all (#srcSwitch - #dstSwitch flow)"() { @Tags([HARDWARE, TOPOLOGY_DEPENDENT, SMOKE_SWITCHES]) def "Flow burst should be correctly set on Centec switches in case of #flowRate kbps flow bandwidth"() { setup: "A single-switch flow with #flowRate kbps bandwidth is created on OpenFlow 1.3 compatible Centec switch" - def switches = getCentecSwitches() - assumeTrue(switches as boolean, "Unable to find required switches in topology") - - def sw = switches.first() - def expectedBurstSize = switchHelper.getExpectedBurst(sw.dpId, flowRate) - def defaultMeters = northbound.getAllMeters(sw.dpId) - def flow = flowFactory.getBuilder(sw, sw) + def sw = switches.all().withManufacturer(CENTEC).random() + def expectedBurstSize = sw.getExpectedBurst(flowRate) + def defaultMeters = sw.metersManager.getMeters() + def flow = flowFactory.getSingleSwBuilder(sw) .withBandwidth(100).build() .create() when: "Update flow bandwidth to #flowRate kbps" - flow.update(flow.tap{ it.maximumBandwidth = flowRate}) + flow.update(flow.tap{ it.maximumBandwidth = flowRate }) then: "Meters with updated rate should be installed on the switch" def newMeters = null Wrappers.wait(Constants.RULES_DELETION_TIME + Constants.RULES_INSTALLATION_TIME) { - newMeters = northbound.getAllMeters(sw.dpId).meterEntries.findAll { - !defaultMeters.meterEntries.contains(it) + newMeters = sw.metersManager.getMeters().findAll { + !defaultMeters.contains(it) } assert newMeters.size() == 2 assert newMeters*.rate.every { it == flowRate } @@ -442,7 +416,7 @@ meters in flow rules at all (#srcSwitch - #dstSwitch flow)"() { newMeters*.burstSize.every { it == expectedBurstSize } and: "Switch validation shows no discrepancies in meters" - !switchHelper.synchronizeAndCollectFixedDiscrepancies(sw.dpId).isPresent() + !sw.synchronizeAndCollectFixedDiscrepancies().isPresent() and: "Flow validation shows no discrepancies in meters" flow.validateAndCollectDiscrepancies().isEmpty() @@ -462,12 +436,9 @@ meters in flow rules at all (#srcSwitch - #dstSwitch flow)"() { @Tags([HARDWARE, SMOKE_SWITCHES]) def "Meter burst size is correctly set on Noviflow Wb5164 switches for #flowRate flow rate"() { setup: "A single-switch flow with #flowRate kbps bandwidth is created on OpenFlow 1.3 compatible switch" - def switches = getNoviflowWb5164() - assumeTrue(switches as boolean, "Unable to find required switches in topology") - - def sw = switches.first() - def defaultMeters = northbound.getAllMeters(sw.dpId) - def flow = flowFactory.getBuilder(sw, sw) + def sw = switches.all().withManufacturer(WB5164).random() + def defaultMeters = sw.metersManager.getMeters() + def flow = flowFactory.getSingleSwBuilder(sw) .withBandwidth(100).build() .create() @@ -475,8 +446,8 @@ meters in flow rules at all (#srcSwitch - #dstSwitch flow)"() { flow.update(flow.tap { it.maximumBandwidth = flowRate }) then: "New meters should be installed on the switch" - def newMeters = northbound.getAllMeters(sw.dpId).meterEntries.findAll { - !defaultMeters.meterEntries.contains(it) + def newMeters = sw.metersManager.getMeters().findAll { + !defaultMeters.contains(it) } assert newMeters.size() == 2 @@ -488,12 +459,12 @@ meters in flow rules at all (#srcSwitch - #dstSwitch flow)"() { and: "New meters burst size matches the expected value for given switch model" newMeters.each { meter -> Long actualBurstSize = meter.burstSize - Long expectedBurstSize = switchHelper.getExpectedBurst(sw.dpId, flowRate) + Long expectedBurstSize = sw.getExpectedBurst(flowRate) verifyBurstSizeOnWb5164(expectedBurstSize, actualBurstSize) } and: "Switch validation shows no discrepancies in meters" - !switchHelper.synchronizeAndCollectFixedDiscrepancies(sw.dpId).isPresent() + !sw.synchronizeAndCollectFixedDiscrepancies().isPresent() and: "Flow validation shows no discrepancies in meters" flow.validateAndCollectDiscrepancies().isEmpty() @@ -506,12 +477,11 @@ meters in flow rules at all (#srcSwitch - #dstSwitch flow)"() { @IterationTag(tags = [HARDWARE], iterationNameRegex = NOT_OVS_REGEX) def "System allows to reset meter values to defaults without reinstalling rules for #data.description flow"() { given: "Switches combination (#data.description)" - assumeTrue(data.switches.size() > 1, "Desired switch combination is not available in current topology") - def src = data.switches[0] - def dst = data.switches[1] + def swPair = switchPairs.all().nonNeighbouring() + .withSwitchesManufacturedBy(data.srcType, data.dstType).random() and: "A flow with custom meter rate and burst, that differ from defaults" - def flow = flowFactory.getBuilder(src, dst) + def flow = flowFactory.getBuilder(swPair) .withBandwidth(1000).build() .create() /*at this point meters are set for given flow. Now update flow bandwidth directly via DB, so that existing meter @@ -520,11 +490,13 @@ meters in flow rules at all (#srcSwitch - #dstSwitch flow)"() { flow.updateFlowBandwidthInDB(newBandwidth) //at this point existing meters do not correspond with the flow //now save some original data for further comparison before resetting meters - Map> originalRules = [src.dpId, dst.dpId].collectEntries { - [(it): switchRulesFactory.get(it).getRules()] + def srcToInteract = switches.all().findSpecific(swPair.src.dpId) + def dstToInteract = switches.all().findSpecific(swPair.dst.dpId) + Map> originalRules = [srcToInteract, dstToInteract].collectEntries { + [(it.switchId): it.rulesManager.getRules()] } - Map> originalMeters = [src.dpId, dst.dpId].collectEntries { - [(it): northbound.getAllMeters(it).meterEntries] + Map> originalMeters = [srcToInteract, dstToInteract].collectEntries { + [(it.switchId): it.metersManager.getMeters()] } when: "Ask system to reset meters for the flow" @@ -534,14 +506,15 @@ meters in flow rules at all (#srcSwitch - #dstSwitch flow)"() { [response.srcMeter, response.dstMeter].each { switchMeterEntries -> def originalFlowMeters = originalMeters[switchMeterEntries.switchId].findAll(flowMeters) switchMeterEntries.meterEntries.each { meterEntry -> - if (northbound.getSwitch(switchMeterEntries.switchId).hardware =~ "WB5164") { + def sw = srcToInteract.switchId == switchMeterEntries.switchId ? srcToInteract : dstToInteract + if (sw.isWb5164()) { verifyRateSizeOnWb5164(newBandwidth, meterEntry.rate) - Long expectedBurstSize = switchHelper.getExpectedBurst(switchMeterEntries.switchId, newBandwidth) + Long expectedBurstSize = sw.getExpectedBurst(newBandwidth) Long actualBurstSize = meterEntry.burstSize verifyBurstSizeOnWb5164(expectedBurstSize, actualBurstSize) } else { assert meterEntry.rate == newBandwidth - assert meterEntry.burstSize == switchHelper.getExpectedBurst(switchMeterEntries.switchId, newBandwidth) + assert meterEntry.burstSize == sw.getExpectedBurst(newBandwidth) } } assert switchMeterEntries.meterEntries*.meterId.sort() == originalFlowMeters*.meterId.sort() @@ -556,50 +529,52 @@ meters in flow rules at all (#srcSwitch - #dstSwitch flow)"() { // expect dstFlowMeters, sameBeanAs(response.dstMeter.meterEntries).ignoring("timestamp") and: "Default meters are unchanged" - [src.dpId, dst.dpId].each { SwitchId swId -> - def actualDefaultMeters = northbound.getAllMeters(swId).meterEntries.findAll(defaultMeters) - assertThat(actualDefaultMeters).containsExactlyInAnyOrder(*originalMeters[swId].findAll(defaultMeters)) + [srcToInteract, dstToInteract].each { SwitchExtended sw -> + def actualDefaultMeters = sw.metersManager.getMeters().findAll(defaultMeters) + assertThat(actualDefaultMeters).containsExactlyInAnyOrder(*originalMeters[sw.switchId].findAll(defaultMeters)) } and: "Switch rules are unchanged" - [src.dpId, dst.dpId].each { SwitchId swId -> - def actualRules = switchRulesFactory.get(swId).getRules() - assertThat(actualRules).containsExactlyInAnyOrder(*originalRules[swId]) + [srcToInteract, dstToInteract].each { SwitchExtended sw -> + def actualRules = sw.rulesManager.getRules() + assertThat(actualRules).containsExactlyInAnyOrder(*originalRules[sw.switchId]) } where: data << [ [ description: "Noviflow-Noviflow", - switches : noviflowSwitches + srcType : NOVIFLOW, + dstType : NOVIFLOW ], [ description: "Centec-Centec", - switches : centecSwitches + srcType : CENTEC, + dstType : CENTEC ], [ description: "Centec-Noviflow", - switches : !centecSwitches.empty && !noviflowSwitches.empty ? - [centecSwitches[0], noviflowSwitches[0]] : [] + srcType : CENTEC, + dstType : NOVIFLOW ], [ description: "Noviflow_Wb5164-Noviflow_Wb5164", - switches : noviflowWb5164 + srcType : WB5164, + dstType : WB5164 ], [ description: "OVS-OVS", - switches : virtualSwitches + srcType : OVS, + dstType : OVS ] ] } def "Try to reset meters for unmetered flow"() { given: "A flow with the 'bandwidth: 0' and 'ignoreBandwidth: true' fields" - def availableSwitches = topology.activeSwitches - def src = availableSwitches[0] - def dst = availableSwitches[1] + def swPair = switchPairs.all().neighbouring().random() - def flow = flowFactory.getBuilder(src, dst) + def flow = flowFactory.getBuilder(swPair) .withBandwidth(0) .withIgnoreBandwidth(true).build() .create() @@ -612,28 +587,7 @@ meters in flow rules at all (#srcSwitch - #dstSwitch flow)"() { new MeterExpectedError("Can't update meter: Flow '$flow.flowId' is unmetered", ~/Modify meters in FlowMeterModifyFsm/).matches(exc) } - - @Memoized - List getNoviflowSwitches() { - topology.activeSwitches.findAll { it.noviflow && it.ofVersion == "OF_13" && !it.wb5164 } - } - - @Memoized - List getCentecSwitches() { - topology.getActiveSwitches().findAll { it.centec } - } - - @Memoized - List getNoviflowWb5164() { - topology.getActiveSwitches().findAll { it.wb5164 } - } - - @Memoized - List getVirtualSwitches() { - topology.getActiveSwitches().findAll { it.virtual } - } - - List filterRules(List rules, inPort, inVlan, outPort) { + List filterRules(List rules, inPort, inVlan, outPort) { if (inPort) { rules = rules.findAll { it.match.inPort == inPort.toString() } } @@ -650,14 +604,4 @@ meters in flow rules at all (#srcSwitch - #dstSwitch flow)"() { def defaultMeters = { it.meterId <= MAX_SYSTEM_RULE_METER_ID } def flowMeters = { it.meterId > MAX_SYSTEM_RULE_METER_ID } - - void verifyBurstSizeOnWb5164(Long expected, Long actual) { - //...ValidationServiceImpl.E_SWITCH_METER_RATE_EQUALS_DELTA_COEFFICIENT = 0.01 - assert Math.abs(expected - actual) <= expected * 0.01 - } - - void verifyRateSizeOnWb5164(Long expectedRate, Long actualRate) { - //...ValidationServiceImpl.E_SWITCH_METER_BURST_SIZE_EQUALS_DELTA_COEFFICIENT = 0.01 - assert Math.abs(expectedRate - actualRate) <= expectedRate * 0.01 - } } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchValidationSingleSwFlowSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchValidationSingleSwFlowSpec.groovy index 55bd0ee2e78..51f12cb1b97 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchValidationSingleSwFlowSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchValidationSingleSwFlowSpec.groovy @@ -1,14 +1,19 @@ package org.openkilda.functionaltests.spec.switches -import static org.junit.jupiter.api.Assumptions.assumeTrue import static org.openkilda.functionaltests.extension.tags.Tag.HARDWARE 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.model.SwitchExtended.isDefaultMeter +import static org.openkilda.functionaltests.helpers.model.SwitchExtended.verifyHexRuleSectionsAreEmpty +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.functionaltests.model.switches.Manufacturer.CENTEC +import static org.openkilda.functionaltests.model.switches.Manufacturer.NOVIFLOW +import static org.openkilda.functionaltests.model.switches.Manufacturer.OVS +import static org.openkilda.functionaltests.model.switches.Manufacturer.WB5164 import static org.openkilda.functionaltests.spec.switches.MetersSpec.NOT_OVS_REGEX -import static org.openkilda.model.MeterId.MAX_SYSTEM_RULE_METER_ID import static org.openkilda.model.MeterId.MIN_FLOW_METER_ID import static org.openkilda.testing.Constants.WAIT_OFFSET import static org.openkilda.testing.tools.KafkaUtils.buildMessage @@ -23,7 +28,6 @@ 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.rulemanager.FlowSpeakerData import org.openkilda.rulemanager.Instructions @@ -31,10 +35,8 @@ 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 groovy.transform.Memoized import org.apache.kafka.clients.producer.KafkaProducer import org.apache.kafka.clients.producer.ProducerRecord import org.springframework.beans.factory.annotation.Autowired @@ -44,7 +46,6 @@ import spock.lang.Narrative import spock.lang.See import spock.lang.Shared - @See("https://github.com/telstra/open-kilda/tree/develop/docs/design/hub-and-spoke/switch-validate") @Narrative("""This test suite checks the switch validate feature on a single flow switch. Description of fields: @@ -74,109 +75,101 @@ class SwitchValidationSingleSwFlowSpec extends HealthCheckSpecification { @Tags([SMOKE]) @IterationTag(tags = [HARDWARE], iterationNameRegex = NOT_OVS_REGEX) def "Switch validation is able to store correct information on a #switchType switch in the 'proper' section"() { - assumeTrue(switches as boolean, "Unable to find required switches in topology") - setup: "Select a #switchType switch and retrieve default meters" - def sw = switches.first() + def sw = switches.all().withManufacturer(switchType).first() when: "Create a flow" def flow = flowFactory.getRandom(sw, sw) - def meterIds = getCreatedMeterIds(sw.dpId) - Long burstSize = switchHelper.getExpectedBurst(sw.dpId, flow.maximumBandwidth) + def meterIds = sw.metersManager.getCreatedMeterIds() + Long burstSize = sw.getExpectedBurst(flow.maximumBandwidth) then: "Two meters are automatically created." meterIds.size() == 2 and: "The correct info is stored in the 'proper' section" - def switchValidateInfo = switchHelper.validateV1(sw.dpId) + def switchValidateInfo = sw.validateV1() switchValidateInfo.meters.proper.collect { it.meterId }.containsAll(meterIds) - def createdCookies = getCookiesWithMeter(sw.dpId) + def createdCookies = sw.rulesManager.getRulesWithMeter().cookie switchValidateInfo.meters.proper*.cookie.containsAll(createdCookies) def properMeters = switchValidateInfo.meters.proper.findAll({ !isDefaultMeter(it) }) properMeters.each { - verifyRateIsCorrect(sw, it.rate, flow.maximumBandwidth) + sw.verifyRateSizeIsCorrect(it.rate, flow.maximumBandwidth) assert it.flowId == flow.flowId assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) - switchHelper.verifyBurstSizeIsCorrect(sw, burstSize, it.burstSize) + sw.verifyBurstSizeIsCorrect(burstSize, it.burstSize) } - switchValidateInfo.verifyMeterSectionsAreEmpty(["missing", "misconfigured", "excess"]) + verifyMeterSectionsAreEmpty(switchValidateInfo, ["missing", "misconfigured", "excess"]) and: "Created rules are stored in the 'proper' section" switchValidateInfo.rules.proper.containsAll(createdCookies) and: "The rest fields in the 'rule' section are empty" - switchValidateInfo.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyRuleSectionsAreEmpty(switchValidateInfo, ["missing", "excess"]) when: "Delete the flow" flow.delete() then: "Check that the switch validate request returns empty sections" Wrappers.wait(WAIT_OFFSET) { - def switchValidateInfoAfterDelete = switchHelper.validateV1(sw.dpId) - switchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() - switchValidateInfoAfterDelete.verifyMeterSectionsAreEmpty() + def switchValidateInfoAfterDelete = sw.validateV1() + verifyRuleSectionsAreEmpty(switchValidateInfoAfterDelete) + verifyMeterSectionsAreEmpty(switchValidateInfoAfterDelete) } where: - switchType | switches - "Centec" | getCentecSwitches() - "Noviflow" | getNoviflowSwitches() - "Noviflow(Wb5164)" | getNoviflowWb5164() - "OVS" | getVirtualSwitches() + switchType << [CENTEC, NOVIFLOW, WB5164, OVS] } @IterationTag(tags = [HARDWARE], iterationNameRegex = NOT_OVS_REGEX) def "Switch validation is able to detect meter info into the 'misconfigured' section on a #switchType switch"() { - assumeTrue(switches as boolean, "Unable to find required switches in topology") - setup: "Select a #switchType switch and retrieve default meters" - def sw = switches.first() + def sw = switches.all().withManufacturer(switchType).first() when: "Create a flow" def amountOfMultiTableFlRules = 4 //2 SHARED_OF_FLOW, 2 MULTI_TABLE_INGRESS_RULES def amountOfFlowRules = 2 //SERVICE_OR_FLOW_SEGMENT(ingress/egress) - def amountOfSwRules = northbound.getSwitchRules(sw.dpId).flowEntries.size() + def amountOfSwRules = sw.rulesManager.getRules().size() def amountOfRules = amountOfSwRules + amountOfFlowRules + amountOfMultiTableFlRules - def amountOfMeters = northbound.getAllMeters(sw.dpId).meterEntries.size() + def amountOfMeters = sw.metersManager.getMeters().size() def amountOfFlowMeters = 2 - def flow = flowFactory.getBuilder(sw, sw, false).withBandwidth(5000).build().create() - def meterIds = getCreatedMeterIds(sw.dpId) - Long burstSize = switchHelper.getExpectedBurst(sw.dpId, flow.maximumBandwidth) + def flow = flowFactory.getSingleSwBuilder(sw, false).withBandwidth(5000).build().create() + def meterIds = sw.metersManager.getCreatedMeterIds() + Long burstSize = sw.getExpectedBurst(flow.maximumBandwidth) and: "Change bandwidth for the created flow directly in DB" Long newBandwidth = flow.maximumBandwidth + 100 - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronize(sw.dpId)}) + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {sw.synchronize()}) /** at this point meters are set for given flow. Now update flow bandwidth directly via DB, it is done just for moving meters from the 'proper' section into the 'misconfigured'*/ flow.updateFlowBandwidthInDB(newBandwidth) //at this point existing meters do not correspond with the flow then: "Meters info is moved into the 'misconfigured' section" - def switchValidateInfo = switchHelper.validateV1(sw.dpId) - def createdCookies = getCookiesWithMeter(sw.dpId) + def switchValidateInfo = sw.validateV1() + def createdCookies = sw.rulesManager.getRulesWithMeter().cookie switchValidateInfo.meters.misconfigured.meterId.size() == 2 switchValidateInfo.meters.misconfigured*.meterId.containsAll(meterIds) switchValidateInfo.meters.misconfigured*.cookie.containsAll(createdCookies) switchValidateInfo.meters.misconfigured.each { - verifyRateIsCorrect(sw, it.rate, flow.maximumBandwidth) + sw.verifyRateSizeIsCorrect(it.rate, flow.maximumBandwidth) assert it.flowId == flow.flowId assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) - switchHelper.verifyBurstSizeIsCorrect(sw, burstSize, it.burstSize) + sw.verifyBurstSizeIsCorrect(burstSize, it.burstSize) } and: "Reason is specified why meters are misconfigured" switchValidateInfo.meters.misconfigured.each { - verifyRateIsCorrect(sw, it.actual.rate, flow.maximumBandwidth) + sw.verifyRateSizeIsCorrect(it.actual.rate, flow.maximumBandwidth) assert it.expected.rate == newBandwidth } and: "The rest fields are empty" - switchValidateInfo.verifyMeterSectionsAreEmpty(["proper", "missing", "excess"]) + verifyMeterSectionsAreEmpty(switchValidateInfo, ["proper", "missing", "excess"]) and: "Created rules are still stored in the 'proper' section" switchValidateInfo.rules.proper.containsAll(createdCookies) @@ -184,23 +177,24 @@ class SwitchValidationSingleSwFlowSpec extends HealthCheckSpecification { and: "Flow validation shows discrepancies" def flowValidateResponse = flow.validate() // check isServer42 only for src switch because it is single switch pair, src equal to dst - def isSwitchServer42 = switchHelper.isServer42Supported(flow.source.switchId) + def isSwitchServer42 = sw.isS42FlowRttEnabled() def expectedRulesCount = [ flow.getFlowRulesCountBySwitch(FlowDirection.FORWARD, 1, isSwitchServer42), flow.getFlowRulesCountBySwitch(FlowDirection.REVERSE, 1, isSwitchServer42)] + flowValidateResponse.eachWithIndex { direction, i -> assert direction.discrepancies.size() == 2 def rate = direction.discrepancies[0] assert rate.field == "meterRate" assert rate.expectedValue == newBandwidth.toString() - verifyRateIsCorrect(sw, rate.actualValue.toLong(), flow.maximumBandwidth) + sw.verifyRateSizeIsCorrect(rate.actualValue.toLong(), flow.maximumBandwidth) def burst = direction.discrepancies[1] assert burst.field == "meterBurstSize" - Long newBurstSize = switchHelper.getExpectedBurst(sw.dpId, newBandwidth) - switchHelper.verifyBurstSizeIsCorrect(sw, newBurstSize, burst.expectedValue.toLong()) - switchHelper.verifyBurstSizeIsCorrect(sw, burstSize, burst.actualValue.toLong()) + Long newBurstSize = sw.getExpectedBurst(newBandwidth) + sw.verifyBurstSizeIsCorrect(newBurstSize, burst.expectedValue.toLong()) + sw.verifyBurstSizeIsCorrect(burstSize, burst.actualValue.toLong()) assert direction.flowRulesTotal == ((FlowDirectionType.FORWARD.toString() == direction.direction) ? expectedRulesCount[0] : expectedRulesCount[1]) @@ -213,9 +207,9 @@ class SwitchValidationSingleSwFlowSpec extends HealthCheckSpecification { flow.resetMeters() then: "Misconfigured meters are reinstalled according to the new bandwidth and moved into the 'proper' section" - with(switchHelper.validateV1(sw.dpId)) { + with(sw.validateV1()) { it.meters.proper.findAll { it.meterId in meterIds }.each { assert it.rate == newBandwidth } - it.verifyMeterSectionsAreEmpty(["missing", "misconfigured", "excess"]) + verifyMeterSectionsAreEmpty(it, ["missing", "misconfigured", "excess"]) } and: "Flow validation shows no discrepancies" @@ -226,56 +220,50 @@ class SwitchValidationSingleSwFlowSpec extends HealthCheckSpecification { then: "Check that the switch validate request returns empty sections" Wrappers.wait(WAIT_OFFSET) { - def switchValidateInfoAfterDelete = switchHelper.validateV1(sw.dpId) - switchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() - switchValidateInfoAfterDelete.verifyMeterSectionsAreEmpty() + def switchValidateInfoAfterDelete = sw.validateV1() + verifyRuleSectionsAreEmpty(switchValidateInfoAfterDelete) + verifyMeterSectionsAreEmpty(switchValidateInfoAfterDelete) } where: - switchType | switches - "Centec" | getCentecSwitches() - "Noviflow" | getNoviflowSwitches() - "Noviflow(Wb5164)" | getNoviflowWb5164() - "OVS" | getVirtualSwitches() + switchType << [CENTEC, NOVIFLOW, WB5164, OVS] } @IterationTag(tags = [HARDWARE], iterationNameRegex = NOT_OVS_REGEX) def "Switch validation is able to detect meter info into the 'missing' section on a #switchType switch"() { - assumeTrue(switches as boolean, "Unable to find required switches in topology") - setup: "Select a #switchType switch and retrieve default meters" - def sw = switches.first() + def sw = switches.all().withManufacturer(switchType).first() when: "Create a flow" def flow = flowFactory.getRandom(sw, sw) - def meterIds = getCreatedMeterIds(sw.dpId) + def meterIds = sw.metersManager.getCreatedMeterIds() and: "Remove created meter" - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronize(sw.dpId)}) - northbound.deleteMeter(sw.dpId, meterIds[0]) - northbound.deleteMeter(sw.dpId, meterIds[1]) + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {sw.synchronize()}) + sw.metersManager.delete(meterIds[0]) + sw.metersManager.delete(meterIds[1]) then: "Meters info/rules are moved into the 'missing' section" - Long burstSize = switchHelper.getExpectedBurst(sw.dpId, flow.maximumBandwidth) - def switchValidateInfo = switchHelper.validateV1(sw.dpId) - def createdCookies = getCookiesWithMeter(sw.dpId) + Long burstSize = sw.getExpectedBurst(flow.maximumBandwidth) + def switchValidateInfo = sw.validateV1() + def createdCookies = sw.rulesManager.getRulesWithMeter().cookie switchValidateInfo.meters.missing.meterId.size() == 2 switchValidateInfo.rules.missing.containsAll(createdCookies) switchValidateInfo.meters.missing*.meterId.containsAll(meterIds) switchValidateInfo.meters.missing*.cookie.containsAll(createdCookies) switchValidateInfo.meters.missing.each { - verifyRateIsCorrect(sw, it.rate, flow.maximumBandwidth) + sw.verifyRateSizeIsCorrect(it.rate, flow.maximumBandwidth) assert it.flowId == flow.flowId assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) - switchHelper.verifyBurstSizeIsCorrect(sw, burstSize, it.burstSize) + sw.verifyBurstSizeIsCorrect(burstSize, it.burstSize) } and: "The rest fields are empty" - switchValidateInfo.verifyRuleSectionsAreEmpty(["proper", "excess"]) - switchValidateInfo.verifyMeterSectionsAreEmpty(["proper", "misconfigured", "excess"]) + verifyRuleSectionsAreEmpty(switchValidateInfo, ["proper", "excess"]) + verifyMeterSectionsAreEmpty(switchValidateInfo, ["proper", "misconfigured", "excess"]) when: "Try to synchronize the switch" - def syncResponse = switchHelper.synchronize(sw.dpId, false) + def syncResponse = sw.synchronize(false) then: "System detects missing rules and meters, then installs them" syncResponse.rules.missing.size() == 2 @@ -292,34 +280,28 @@ class SwitchValidationSingleSwFlowSpec extends HealthCheckSpecification { then: "Check that the switch validate request returns empty sections" Wrappers.wait(WAIT_OFFSET) { - def switchValidateInfoAfterDelete = switchHelper.validateV1(sw.dpId) - switchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() - switchValidateInfoAfterDelete.verifyMeterSectionsAreEmpty() + def switchValidateInfoAfterDelete = sw.validateV1() + verifyRuleSectionsAreEmpty(switchValidateInfoAfterDelete) + verifyMeterSectionsAreEmpty(switchValidateInfoAfterDelete) } where: - switchType | switches - "Centec" | getCentecSwitches() - "Noviflow" | getNoviflowSwitches() - "Noviflow(Wb5164)" | getNoviflowWb5164() - "OVS" | getVirtualSwitches() + switchType << [CENTEC, NOVIFLOW, WB5164, OVS] } @IterationTag(tags = [HARDWARE], iterationNameRegex = NOT_OVS_REGEX) def "Switch validation is able to detect meter info into the 'excess' section on a #switchType switch"() { - assumeTrue(switches as boolean, "Unable to find required switches in topology") - setup: "Select a #switchType switch and retrieve default meters" - def sw = switches.first() + def sw = switches.all().withManufacturer(switchType).first() when: "Create a flow" // No TraffGens because the Single switch flow is created at the same port, and no traffic is checked def flow = flowFactory.getRandom(sw, sw, false) - def metersIds = getCreatedMeterIds(sw.dpId) - Long burstSize = switchHelper.getExpectedBurst(sw.dpId, flow.maximumBandwidth) + def metersIds = sw.metersManager.getCreatedMeterIds() + Long burstSize = sw.getExpectedBurst(flow.maximumBandwidth) then: "Rules and meters are created" - def swValidateInfo = switchHelper.validateV1(sw.dpId) + def swValidateInfo = sw.validateV1() def properMeters = swValidateInfo.meters.proper.findAll({ !isDefaultMeter(it) }) def amountOfFlowRules = 4 properMeters.meterId.size() == 2 @@ -327,80 +309,74 @@ class SwitchValidationSingleSwFlowSpec extends HealthCheckSpecification { when: "Update meterId for created flow directly via db" long newMeterId = 100; - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronize(sw.dpId)}) + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {sw.synchronize()}) flow.updateFlowMeterIdInDB(newMeterId) then: "Origin meters are moved into the 'excess' section" - def switchValidateInfo = switchHelper.validateV1(sw.dpId) + def switchValidateInfo = sw.validateV1() switchValidateInfo.meters.excess.meterId.size() == 2 switchValidateInfo.meters.excess.collect { it.meterId }.containsAll(metersIds) switchValidateInfo.meters.excess.each { - verifyRateIsCorrect(sw, it.rate, flow.maximumBandwidth) + sw.verifyRateSizeIsCorrect(it.rate, flow.maximumBandwidth) assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) - switchHelper.verifyBurstSizeIsCorrect(sw, burstSize, it.burstSize) + sw.verifyBurstSizeIsCorrect(burstSize, it.burstSize) } and: "Updated meters are stored in the 'missing' section" - def createdCookies = getCookiesWithMeter(sw.dpId) + def createdCookies = sw.rulesManager.getRulesWithMeter().cookie switchValidateInfo.meters.missing.collect { it.cookie }.containsAll(createdCookies) switchValidateInfo.meters.missing.each { - verifyRateIsCorrect(sw, it.rate, flow.maximumBandwidth) + sw.verifyRateSizeIsCorrect(it.rate, flow.maximumBandwidth) assert it.flowId == flow.flowId assert it.meterId == newMeterId || it.meterId == newMeterId + 1 assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) - switchHelper.verifyBurstSizeIsCorrect(sw, burstSize, it.burstSize) + sw.verifyBurstSizeIsCorrect(burstSize, it.burstSize) } and: "Rules still exist in the 'proper' section" - switchValidateInfo.verifyRuleSectionsAreEmpty(["missing", "excess"]) + verifyRuleSectionsAreEmpty(switchValidateInfo, ["missing", "excess"]) when: "Delete the flow" flow.delete() and: "Delete excess meters" - metersIds.each { northbound.deleteMeter(sw.dpId, it) } + metersIds.each { sw.metersManager.delete(it) } then: "Check that the switch validate request returns empty sections" Wrappers.wait(WAIT_OFFSET) { - def switchValidateInfoAfterDelete = switchHelper.validateV1(sw.dpId) - switchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() - switchValidateInfoAfterDelete.verifyMeterSectionsAreEmpty() + def switchValidateInfoAfterDelete = sw.validateV1() + verifyRuleSectionsAreEmpty(switchValidateInfoAfterDelete) + verifyMeterSectionsAreEmpty(switchValidateInfoAfterDelete) } where: - switchType | switches - "Centec" | getCentecSwitches() - "Noviflow" | getNoviflowSwitches() - "Noviflow(Wb5164)" | getNoviflowWb5164() - "OVS" | getVirtualSwitches() + switchType << [CENTEC, NOVIFLOW, WB5164, OVS] } @IterationTag(tags = [HARDWARE], iterationNameRegex = NOT_OVS_REGEX) def "Switch validation is able to detect rule info into the 'missing' section on a #switchType switch"() { - assumeTrue(switches as boolean, "Unable to find required switches in topology") - setup: "Select a #switchType switch and retrieve default meters" - def sw = switches.first() + def sw = switches.all().withManufacturer(switchType).first() when: "Create a flow" def flow = flowFactory.getRandom(sw, sw) - def createdCookies = getCookiesWithMeter(sw.dpId) + def createdCookies = sw.rulesManager.getRulesWithMeter().cookie def createdHexCookies = createdCookies.collect { Long.toHexString(it) } and: "Delete created rules" - switchHelper.deleteSwitchRules(sw.dpId, DeleteRulesAction.IGNORE_DEFAULTS) + sw.rulesManager.delete(DeleteRulesAction.IGNORE_DEFAULTS) then: "Rule info is moved into the 'missing' section" - def switchValidateInfo = switchHelper.validateV1(sw.dpId) + def switchValidateInfo = sw.validateV1() switchValidateInfo.rules.missing.containsAll(createdCookies) switchValidateInfo.rules.missingHex.containsAll(createdHexCookies) and: "The rest fields in the 'rule' section are empty" - switchValidateInfo.verifyRuleSectionsAreEmpty(["proper", "excess"]) - switchValidateInfo.verifyHexRuleSectionsAreEmpty(["properHex", "excessHex"]) + verifyRuleSectionsAreEmpty(switchValidateInfo, ["proper", "excess"]) + verifyHexRuleSectionsAreEmpty(switchValidateInfo, ["properHex", "excessHex"]) when: "Try to synchronize the switch" - def syncResponse = switchHelper.synchronize(sw.dpId, false) + def syncResponse = sw.synchronize(false) then: "System detects missing rules, then installs them" def amountOfFlowRules = 4 @@ -414,58 +390,51 @@ class SwitchValidationSingleSwFlowSpec extends HealthCheckSpecification { then: "Check that the switch validate request returns empty sections" Wrappers.wait(WAIT_OFFSET) { - def switchValidateInfoAfterDelete = switchHelper.validateV1(sw.dpId) - switchValidateInfoAfterDelete.verifyRuleSectionsAreEmpty() - switchValidateInfoAfterDelete.verifyMeterSectionsAreEmpty() + def switchValidateInfoAfterDelete = sw.validateV1() + verifyRuleSectionsAreEmpty(switchValidateInfoAfterDelete) + verifyMeterSectionsAreEmpty(switchValidateInfoAfterDelete) } where: - switchType | switches - "Centec" | getCentecSwitches() - "Noviflow" | getNoviflowSwitches() - "Noviflow(Wb5164)" | getNoviflowWb5164() - "OVS" | getVirtualSwitches() + switchType << [CENTEC, NOVIFLOW, WB5164, OVS] } @IterationTag(tags = [HARDWARE], iterationNameRegex = NOT_OVS_REGEX) def "Switch validation is able to detect rule/meter info into the 'excess' section on a #switchType switch"() { - assumeTrue(switches as boolean, "Unable to find required switches in topology") - setup: "Select a #switchType switch and no meters/rules exist on a switch" - def sw = switches.first() - def switchValidateInfoInitState = switchHelper.validateV1(sw.dpId) - switchValidateInfoInitState.verifyRuleSectionsAreEmpty() - switchValidateInfoInitState.verifyMeterSectionsAreEmpty() + def sw = switches.all().withManufacturer(switchType).first() + def switchValidateInfoInitState = sw.validateV1() + verifyRuleSectionsAreEmpty(switchValidateInfoInitState) + verifyMeterSectionsAreEmpty(switchValidateInfoInitState) when: "Create excess rules/meter directly via kafka" Long fakeBandwidth = 333 - Long burstSize = switchHelper.getExpectedBurst(sw.dpId, fakeBandwidth) + Long burstSize = sw.getExpectedBurst(fakeBandwidth) def producer = new KafkaProducer(producerProps) //pick a meter id which is not yet used on src switch - def excessMeterId = ((MIN_FLOW_METER_ID..100) - northbound.getAllMeters(sw.dpId) - .meterEntries*.meterId).first() - cleanupManager.addAction(SYNCHRONIZE_SWITCH, {switchHelper.synchronize(sw.dpId)}) - producer.send(new ProducerRecord(speakerTopic, sw.dpId.toString(), buildMessage( + def excessMeterId = ((MIN_FLOW_METER_ID..100) - sw.metersManager.getMeters().meterId).first() + cleanupManager.addAction(SYNCHRONIZE_SWITCH, {sw.synchronize()}) + producer.send(new ProducerRecord(speakerTopic, sw.switchId.toString(), buildMessage( FlowSpeakerData.builder() - .switchId(sw.dpId) + .switchId(sw.switchId) .ofVersion(OfVersion.of(sw.ofVersion)) .cookie(new Cookie(1L)) .table(OfTable.EGRESS) .priority(100) .instructions(Instructions.builder().build()) .build()).toJson())).get() - producer.send(new ProducerRecord(speakerTopic, sw.dpId.toString(), buildMessage( + producer.send(new ProducerRecord(speakerTopic, sw.switchId.toString(), buildMessage( FlowSpeakerData.builder() - .switchId(sw.dpId) + .switchId(sw.switchId) .ofVersion(OfVersion.of(sw.ofVersion)) .cookie(new Cookie(2L)) .table(OfTable.TRANSIT) .priority(100) .instructions(Instructions.builder().build()) .build()).toJson())).get() - producer.send(new ProducerRecord(speakerTopic, sw.dpId.toString(), buildMessage([ + producer.send(new ProducerRecord(speakerTopic, sw.switchId.toString(), buildMessage([ FlowSpeakerData.builder() - .switchId(sw.dpId) + .switchId(sw.switchId) .ofVersion(OfVersion.of(sw.ofVersion)) .cookie(new Cookie(3L)) .table(OfTable.INPUT) @@ -473,7 +442,7 @@ class SwitchValidationSingleSwFlowSpec extends HealthCheckSpecification { .instructions(Instructions.builder().build()) .build(), MeterSpeakerData.builder() - .switchId(sw.dpId) + .switchId(sw.switchId) .ofVersion(OfVersion.of(sw.ofVersion)) .meterId(new MeterId(excessMeterId)) .rate(fakeBandwidth) @@ -486,22 +455,22 @@ class SwitchValidationSingleSwFlowSpec extends HealthCheckSpecification { //they will be added after the next line def switchValidateInfo Wrappers.wait(WAIT_OFFSET) { - switchValidateInfo = switchHelper.validateV1(sw.dpId) + switchValidateInfo = sw.validateV1() //excess egress/ingress/transit rules are added - switchValidateInfo.rules.excess.size() == 3 - switchValidateInfo.rules.excessHex.size() == 3 - switchValidateInfo.meters.excess.size() == 1 + assert switchValidateInfo.rules.excess.size() == 3 + assert switchValidateInfo.rules.excessHex.size() == 3 + assert switchValidateInfo.meters.excess.size() == 1 } switchValidateInfo.meters.excess.each { - verifyRateIsCorrect(sw, it.rate, fakeBandwidth) + sw.verifyRateSizeIsCorrect(it.rate, fakeBandwidth) assert it.meterId == excessMeterId assert ["KBPS", "BURST", "STATS"].containsAll(it.flags) - switchHelper.verifyBurstSizeIsCorrect(sw, burstSize, it.burstSize) + sw.verifyBurstSizeIsCorrect(burstSize, it.burstSize) } when: "Try to synchronize the switch" - def syncResponse = switchHelper.synchronize(sw.dpId, true) + def syncResponse = sw.synchronize(true) then: "System detects excess rules and meters, then deletes them" syncResponse.rules.excess.size() == 3 @@ -514,48 +483,42 @@ class SwitchValidationSingleSwFlowSpec extends HealthCheckSpecification { and: "Switch validation doesn't complain about excess rules and meters" Wrappers.wait(WAIT_OFFSET) { - def switchValidateResponse = switchHelper.validateV1(sw.dpId) - switchValidateResponse.verifyRuleSectionsAreEmpty() - switchValidateResponse.verifyMeterSectionsAreEmpty() + def switchValidateResponse = sw.validateV1() + verifyRuleSectionsAreEmpty(switchValidateResponse) + verifyMeterSectionsAreEmpty(switchValidateResponse) } - def testIsCompleted = true - cleanup: producer && producer.close() where: - switchType | switches - "Centec" | getCentecSwitches() - "Noviflow" | getNoviflowSwitches() - "Noviflow(Wb5164)" | getNoviflowWb5164() - "OVS" | getVirtualSwitches() + switchType << [CENTEC, NOVIFLOW, WB5164, OVS] } @IterationTag(tags = [HARDWARE], iterationNameRegex = NOT_OVS_REGEX) def "Able to validate and sync a #switchType switch having missing rules of single-port single-switch flow"() { - assumeTrue(sw as boolean, "Unable to find $switchType switch in topology") given: "A single-port single-switch flow" // No TraffGens because the Single switch flow is created at the same port, and no traffic is checked + def sw = switches.all().withManufacturer(switchType).first() def flow = flowFactory.getRandom(sw, sw, false) when: "Remove flow rules from the switch, so that they become missing" - switchHelper.deleteSwitchRules(sw.dpId, DeleteRulesAction.IGNORE_DEFAULTS) + sw.rulesManager.delete(DeleteRulesAction.IGNORE_DEFAULTS) then: "Switch validation shows missing rules" def amountOfFlowRules = 4 - switchHelper.validateV1(sw.dpId).rules.missing.size() == amountOfFlowRules - switchHelper.validateV1(sw.dpId).rules.missingHex.size() == amountOfFlowRules + sw.validateV1().rules.missing.size() == amountOfFlowRules + sw.validateV1().rules.missingHex.size() == amountOfFlowRules when: "Synchronize switch" - with(switchHelper.synchronize(sw.dpId, false)) { + with(sw.synchronize(false)) { it.rules.installed.size() == amountOfFlowRules } then: "Switch validation shows no discrepancies" - with(switchHelper.validateV1(sw.dpId)) { - it.verifyRuleSectionsAreEmpty(["missing", "excess"]) - it.verifyHexRuleSectionsAreEmpty(["missingHex", "excessHex"]) + with(sw.validateV1()) { + verifyRuleSectionsAreEmpty(it, ["missing", "excess"]) + verifyHexRuleSectionsAreEmpty(it, ["missingHex", "excessHex"]) it.rules.proper.findAll { !new Cookie(it).serviceFlag }.size() == amountOfFlowRules def properMeters = it.meters.proper.findAll({ dto -> !isDefaultMeter(dto) }) properMeters.size() == 2 @@ -565,57 +528,13 @@ class SwitchValidationSingleSwFlowSpec extends HealthCheckSpecification { flow.delete() then: "Switch validation returns empty sections" - with(switchHelper.validateV1(sw.dpId)) { - it.verifyRuleSectionsAreEmpty() - it.verifyMeterSectionsAreEmpty() + with(sw.validateV1()) { + verifyRuleSectionsAreEmpty(it) + verifyMeterSectionsAreEmpty(it) } where: - switchType | sw - "Centec" | getCentecSwitches()[0] - "Noviflow" | getNoviflowSwitches()[0] - "Noviflow(Wb5164)" | getNoviflowWb5164()[0] - "OVS" | getVirtualSwitches()[0] - - } - - @Memoized - List getNoviflowSwitches() { - topology.activeSwitches.findAll { it.noviflow && it.ofVersion == "OF_13" && !it.wb5164 } + switchType << [CENTEC, NOVIFLOW, WB5164, OVS] } - @Memoized - List getCentecSwitches() { - topology.getActiveSwitches().findAll { it.centec } - } - - @Memoized - List getNoviflowWb5164() { - topology.getActiveSwitches().findAll { it.wb5164 } - } - - @Memoized - List getVirtualSwitches() { - topology.getActiveSwitches().findAll { it.virtual } - } - - List getCreatedMeterIds(SwitchId switchId) { - return northbound.getAllMeters(switchId).meterEntries.findAll { - it.meterId > MAX_SYSTEM_RULE_METER_ID - }*.meterId - } - - List getCookiesWithMeter(SwitchId switchId) { - return northbound.getSwitchRules(switchId).flowEntries.findAll { - !new Cookie(it.cookie).serviceFlag && it.instructions.goToMeter - }*.cookie - } - - void verifyRateIsCorrect(Switch sw, Long expected, Long actual) { - if (sw.isWb5164()) { - assert Math.abs(expected - actual) <= expected * 0.01 - } else { - assert expected == actual - } - } } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchesSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchesSpec.groovy index e0ccb6eebe8..255fff761b4 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchesSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchesSpec.groovy @@ -5,7 +5,10 @@ import static org.openkilda.functionaltests.extension.tags.Tag.LOW_PRIORITY import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE_SWITCHES import static org.openkilda.functionaltests.extension.tags.Tag.SWITCH_RECOVER_ON_FAIL -import static org.openkilda.functionaltests.model.cleanup.CleanupActionType.RESTORE_SWITCH_PROPERTIES +import static org.openkilda.functionaltests.helpers.model.FlowActionType.REROUTE_FAILED +import static org.openkilda.messaging.command.switches.DeleteRulesAction.DROP_ALL_ADD_DEFAULTS +import static org.openkilda.messaging.command.switches.InstallRulesAction.INSTALL_DEFAULTS +import static org.openkilda.messaging.payload.flow.FlowState.DOWN import static org.openkilda.testing.Constants.NON_EXISTENT_SWITCH_ID import static org.openkilda.testing.Constants.WAIT_OFFSET import static org.openkilda.testing.service.floodlight.model.FloodlightConnectMode.RW @@ -16,7 +19,6 @@ import org.openkilda.functionaltests.extension.tags.Tags import org.openkilda.functionaltests.helpers.Wrappers import org.openkilda.functionaltests.helpers.factory.FlowFactory import org.openkilda.functionaltests.helpers.model.FlowActionType -import org.openkilda.functionaltests.model.cleanup.CleanupManager import org.openkilda.functionaltests.model.stats.Direction import org.openkilda.messaging.command.switches.DeleteRulesAction import org.openkilda.messaging.command.switches.InstallRulesAction @@ -28,14 +30,11 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.client.HttpClientErrorException import spock.lang.Shared - class SwitchesSpec extends HealthCheckSpecification { @Shared SwitchNotFoundExpectedError switchNotFoundExpectedError = new SwitchNotFoundExpectedError( "Switch $NON_EXISTENT_SWITCH_ID not found", ~/Switch $NON_EXISTENT_SWITCH_ID not found/) - @Autowired - @Shared - CleanupManager cleanupManager + @Autowired @Shared FlowFactory flowFactory @@ -48,11 +47,11 @@ class SwitchesSpec extends HealthCheckSpecification { @Tags([SMOKE, SMOKE_SWITCHES]) def "System is able to return a certain switch info by its id"() { when: "Request info about certain switch from Northbound" - def sw = topology.activeSwitches[0] - def response = northbound.getSwitch(sw.dpId) + def sw = switches.all().random() + def response = sw.getDetails() then: "Switch information is returned" - response.switchId == sw.dpId + response.switchId == sw.switchId !response.hostname.empty !response.address.empty !response.description.empty @@ -76,7 +75,8 @@ class SwitchesSpec extends HealthCheckSpecification { @Tags(ISL_RECOVER_ON_FAIL) def "Systems allows to get a flow that goes through a switch"() { given: "Two active not neighboring switches with two diverse paths at least" - def switchPair = switchPairs.all().nonNeighbouring().withAtLeastNNonOverlappingPaths(2).random() + def switchPair = switchPairs.all().nonNeighbouring() + .withAtLeastNNonOverlappingPaths(2).random() and: "A protected flow" def protectedFlow = flowFactory.getBuilder(switchPair) @@ -84,7 +84,8 @@ class SwitchesSpec extends HealthCheckSpecification { .build().create() and: "A single switch flow" - def allowedPorts = topology.getAllowedPortsForSwitch(switchPair.src).findAll { + def srcSwitch = switches.all().findSpecific(switchPair.src.dpId) + def allowedPorts = srcSwitch.getPorts().findAll { it != protectedFlow.source.portNumber } def r = new Random() @@ -100,40 +101,40 @@ class SwitchesSpec extends HealthCheckSpecification { def involvedSwitchIds = flowPathInfo.getInvolvedSwitches() then: "The created flows are in the response list from the src switch" - def switchFlowsResponseSrcSwitch = northbound.getSwitchFlows(switchPair.src.dpId) + def switchFlowsResponseSrcSwitch = srcSwitch.getFlows() switchFlowsResponseSrcSwitch*.id.sort() == [protectedFlow.flowId, singleFlow.flowId].sort() and: "Only the protectedFlow is in the response list from the involved switch(except the src switch)" involvedSwitchIds.findAll { it != switchPair.src.dpId }.each { switchId -> - def getSwitchFlowsResponse = northbound.getSwitchFlows(switchId) + def getSwitchFlowsResponse = switches.all().findSpecific(switchId).getFlows() assert getSwitchFlowsResponse.size() == 1 assert getSwitchFlowsResponse[0].id == protectedFlow.flowId } when: "Get all flows going through the src switch based on the port of the main path" - def getSwitchFlowsResponse1 = northbound.getSwitchFlows(switchPair.src.dpId, mainPath[0].portNo) + def getSwitchFlowsResponse1 = srcSwitch.getFlows(mainPath[0].portNo) then: "Only the protected flow is in the response list" getSwitchFlowsResponse1.size() == 1 getSwitchFlowsResponse1[0].id == protectedFlow.flowId when: "Get all flows going through the src switch based on the port of the protected path" - def getSwitchFlowsResponse2 = northbound.getSwitchFlows(switchPair.src.dpId, protectedPath[0].portNo) + def getSwitchFlowsResponse2 = srcSwitch.getFlows(protectedPath[0].portNo) then: "Only the protected flow is in the response list" getSwitchFlowsResponse2.size() == 1 getSwitchFlowsResponse2[0].id == protectedFlow.flowId when: "Get all flows going through the src switch based on the dstPort of the single switch flow" - def getSwitchFlowsResponse3 = northbound.getSwitchFlows(switchPair.src.dpId, singleFlow.destination.portNumber) + def getSwitchFlowsResponse3 = srcSwitch.getFlows(singleFlow.destination.portNumber) then: "Only the single switch flow is in the response list" getSwitchFlowsResponse3.size() == 1 getSwitchFlowsResponse3[0].id == singleFlow.flowId when: "Get all flows going through the dst switch based on the dstPort of the protected flow" - def getSwitchFlowsResponse4 = northbound.getSwitchFlows(switchPair.dst.dpId, - protectedFlow.destination.portNumber) + def dstSwitch = switches.all().findSpecific(switchPair.dst.dpId) + def getSwitchFlowsResponse4 = dstSwitch.getFlows(protectedFlow.destination.portNumber) then: "Only the protected flow is in the response list" getSwitchFlowsResponse4.size() == 1 @@ -146,7 +147,7 @@ class SwitchesSpec extends HealthCheckSpecification { .build().create() and: "Get all flows going through the src switch" - def getSwitchFlowsResponse5 = northbound.getSwitchFlows(switchPair.src.dpId) + def getSwitchFlowsResponse5 = srcSwitch.getFlows() then: "The created flows are in the response list" getSwitchFlowsResponse5.size() == 3 @@ -158,14 +159,14 @@ class SwitchesSpec extends HealthCheckSpecification { and: "Get all flows going through the src switch" Wrappers.wait(WAIT_OFFSET * 2) { - assert protectedFlow.retrieveFlowStatus().status == FlowState.DOWN - assert protectedFlow.retrieveFlowHistory().getEntriesByType(FlowActionType.REROUTE_FAILED).last() - .payload.find { it.action == FlowActionType.REROUTE_FAILED.payloadLastAction } - assert defaultFlow.retrieveFlowStatus().status == FlowState.DOWN - assert defaultFlow.retrieveFlowHistory().getEntriesByType(FlowActionType.REROUTE_FAILED).last() - .payload.find { it.action == FlowActionType.REROUTE_FAILED.payloadLastAction } + assert protectedFlow.retrieveFlowStatus().status == DOWN + assert protectedFlow.retrieveFlowHistory().getEntriesByType(REROUTE_FAILED).last() + .payload.find { it.action == REROUTE_FAILED.payloadLastAction } + assert defaultFlow.retrieveFlowStatus().status == DOWN + assert defaultFlow.retrieveFlowHistory().getEntriesByType(REROUTE_FAILED).last() + .payload.find { it.action == REROUTE_FAILED.payloadLastAction } } - def getSwitchFlowsResponse6 = northbound.getSwitchFlows(switchPair.src.dpId) + def getSwitchFlowsResponse6 = srcSwitch.getFlows() then: "The created flows are in the response list" getSwitchFlowsResponse6*.id.sort() == [protectedFlow.flowId, singleFlow.flowId, defaultFlow.flowId].sort() @@ -191,11 +192,11 @@ class SwitchesSpec extends HealthCheckSpecification { def singleFlow = flowFactory.getRandom(switchPair.src, switchPair.src) when: "Deactivate the src switch" - def switchToDisconnect = topology.switches.find { it.dpId == switchPair.src.dpId } - switchHelper.knockoutSwitch(switchToDisconnect, RW) + def switchToDisconnect = switches.all().findSpecific(switchPair.src.dpId) + switchToDisconnect.knockout(RW) and: "Get all flows going through the deactivated src switch" - def switchFlowsResponseSrcSwitch = northbound.getSwitchFlows(switchPair.src.dpId) + def switchFlowsResponseSrcSwitch = switchToDisconnect.getFlows() then: "The created flows are in the response list from the deactivated src switch" switchFlowsResponseSrcSwitch*.id.sort() == [simpleFlow.flowId, singleFlow.flowId].sort() @@ -222,7 +223,7 @@ class SwitchesSpec extends HealthCheckSpecification { @Tags(LOW_PRIORITY) def "System returns human readable error when installing switch rules on non-existing switch"() { when: "Install switch rules on non-existing switch" - northbound.installSwitchRules(NON_EXISTENT_SWITCH_ID, InstallRulesAction.INSTALL_DEFAULTS) + northbound.installSwitchRules(NON_EXISTENT_SWITCH_ID, INSTALL_DEFAULTS) then: "Not Found error is returned" def e = thrown(HttpClientErrorException) @@ -232,7 +233,7 @@ class SwitchesSpec extends HealthCheckSpecification { @Tags(LOW_PRIORITY) def "System returns human readable error when deleting switch rules on non-existing switch"() { when: "Delete switch rules on non-existing switch" - switchHelper.deleteSwitchRules(NON_EXISTENT_SWITCH_ID, DeleteRulesAction.DROP_ALL_ADD_DEFAULTS) + northbound.deleteSwitchRules(NON_EXISTENT_SWITCH_ID, DROP_ALL_ADD_DEFAULTS) then: "Not Found error is returned" def e = thrown(HttpClientErrorException) @@ -301,25 +302,17 @@ class SwitchesSpec extends HealthCheckSpecification { @Tags(LOW_PRIORITY) def "Able to partially update switch a 'location.#data.field' field"() { given: "A switch" - def sw = topology.activeSwitches.first() - def initConf = northbound.getSwitch(sw.dpId) + def sw = switches.all().random() when: "Request a switch partial update for a #data.field field" SwitchPatchDto updateRequest = [location: [(data.field): data.newValue]] as SwitchPatchDto - cleanupManager.addAction(RESTORE_SWITCH_PROPERTIES, {northboundV2.partialSwitchUpdate(sw.dpId, [location: [ - latitude: initConf.location.latitude ?: 0, - longitude: initConf.location.longitude ?: 0, - city: initConf.location.city ?: "", - country: initConf.location.country ?: "", - street: initConf.location.street ?: "" - ]] as SwitchPatchDto)}) - def response = northboundV2.partialSwitchUpdate(sw.dpId, updateRequest) + def response = sw.partialUpdate(updateRequest) then: "Update response reflects the changes" response.location."$data.field" == data.newValue and: "Changes actually took place" - northbound.getSwitch(sw.dpId).location."$data.field" == data.newValue + sw.getDetails().location."$data.field" == data.newValue where: data << [ @@ -348,19 +341,16 @@ class SwitchesSpec extends HealthCheckSpecification { def "Able to partially update switch a 'pop' field"() { given: "A switch" - def sw = topology.activeSwitches.first() - def initConf = northbound.getSwitch(sw.dpId) + def sw = switches.all().random() when: "Request a switch partial update for a 'pop' field" def newPopValue = "test_POP" - cleanupManager.addAction(RESTORE_SWITCH_PROPERTIES, - {northboundV2.partialSwitchUpdate(sw.dpId, new SwitchPatchDto().tap { it.pop = initConf.pop ?: "" })}) - def response = northboundV2.partialSwitchUpdate(sw.dpId, new SwitchPatchDto().tap { it.pop = newPopValue }) + def response = sw.partialUpdate(new SwitchPatchDto().tap { it.pop = newPopValue }) then: "Update response reflects the changes" response.pop == newPopValue and: "Changes actually took place" - northbound.getSwitch(sw.dpId).pop == newPopValue + sw.getDetails().pop == newPopValue } }