diff --git a/CHANGELOG.md b/CHANGELOG.md index 3289e044be2..706ad942cfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## v1.125.0 (19/10/2022) + +### Features: +- [#4958](https://github.com/telstra/open-kilda/pull/4958) Added LACP replies for LAG ports [**floodlight**][**northbound**][**storm-topologies**] +- [#4959](https://github.com/telstra/open-kilda/pull/4959) Added functional tests for LACP feature (Issues: [#2882](https://github.com/telstra/open-kilda/issues/2882) [#3439](https://github.com/telstra/open-kilda/issues/3439)) [**tests**] + +For the complete list of changes, check out [the commit log](https://github.com/telstra/open-kilda/compare/v1.124.0...v1.125.0). + +### Affected Components: +nb, swmanager, fl + +### Upgrade notes: +OrientDB schema have been changed in this release. You need to apply schema migration. Please follow [migration instructions](https://github.com/telstra/open-kilda/tree/develop/docker/db-migration/migrations). + +--- + ## v1.124.0 (07/09/2022) ### Features: diff --git a/confd/templates/floodlight-modules/floodlightkilda.properties.tmpl b/confd/templates/floodlight-modules/floodlightkilda.properties.tmpl index 3d0cfeef714..d4ff63af289 100644 --- a/confd/templates/floodlight-modules/floodlightkilda.properties.tmpl +++ b/confd/templates/floodlight-modules/floodlightkilda.properties.tmpl @@ -80,8 +80,9 @@ org.openkilda.floodlight.pathverification.PathVerificationService.hmac256-secret org.openkilda.floodlight.pathverification.PathVerificationService.verification-bcast-packet-dst={{ getv "/kilda_floodlight_broadcast_mac_address" }} org.openkilda.floodlight.KildaCore.flow-ping-magic-src-mac-address={{ getv "/kilda_floodlight_flow_ping_magic_src_mac_address" }} org.openkilda.floodlight.KildaCore.server42-flow-rtt-udp-port-offset={{ getv "/kilda_floodlight_server42_flow_rtt_udp_port_offset" }} -org.openkilda.floodlight.KildaCore.server42-isl-rtt-udp-port-offset={{ getv "/kilda_floodlight_server42_isl_rtt_udp_port_offset" }} -org.openkilda.floodlight.KildaCore.server42-isl-rtt-magic-mac-address={{ getv "/kilda_floodlight_server42_isl_rtt_magic_mac_address" }} +org.openkilda.floodlight.KildaCore.lacp-system-id={{ getv "/kilda_floodlight_lacp_system_id" }} +org.openkilda.floodlight.KildaCore.lacp-system-priority={{ getv "/kilda_floodlight_lacp_system_priority" }} +org.openkilda.floodlight.KildaCore.lacp-port-priority={{ getv "/kilda_floodlight_lacp_port_priority" }} org.openkilda.floodlight.switchmanager.SwitchManager.environment-naming-prefix={{ getv "/kilda_environment_naming_prefix" }} org.openkilda.floodlight.switchmanager.SwitchManager.connect-mode=AUTO org.openkilda.floodlight.switchmanager.SwitchManager.broadcast-rate-limit=200 diff --git a/confd/vars/main.yaml b/confd/vars/main.yaml index a743f47151f..a4d4dd233a6 100644 --- a/confd/vars/main.yaml +++ b/confd/vars/main.yaml @@ -51,6 +51,9 @@ kilda_floodlight_flow_ping_magic_src_mac_address: "00:26:E1:FF:FF:FE" kilda_floodlight_server42_flow_rtt_udp_port_offset: 5000 kilda_floodlight_server42_isl_rtt_udp_port_offset: 10000 kilda_floodlight_server42_isl_rtt_magic_mac_address: "00:26:E1:FF:FF:FD" +kilda_floodlight_lacp_system_id: "00:00:00:00:00:01" +kilda_floodlight_lacp_system_priority: 1 +kilda_floodlight_lacp_port_priority: 1 kilda_floodlight_ovs_meters_enabled: true diff --git a/docker/db-migration/migrations/023-add-lacp-reply-into-lag-class.yaml b/docker/db-migration/migrations/023-add-lacp-reply-into-lag-class.yaml new file mode 100644 index 00000000000..f4985300d44 --- /dev/null +++ b/docker/db-migration/migrations/023-add-lacp-reply-into-lag-class.yaml @@ -0,0 +1,22 @@ +databaseChangeLog: + - changeSet: + id: tag + author: snikitin + changes: + - tagDatabase: + tag: 023-add-lacp-reply-into-lag-class + + - changeSet: + id: add_lacp_reply_into_lag_class + author: snikitin + changes: + - sql: "CREATE PROPERTY lag_logical_port.lacp_reply IF NOT EXISTS BOOLEAN" + - sql: "UPDATE lag_logical_port SET lacp_reply = true" + - sql: "CREATE INDEX lag_logical_port.lacp_reply NOTUNIQUE_HASH_INDEX" + - sql: "DROP INDEX lag_logical_port_unique" + - sql: "CREATE INDEX lag_logical_port_unique on lag_logical_port (switch_id, logical_port_number, lacp_reply) UNIQUE_HASH_INDEX" + rollback: + - sql: "DROP INDEX lag_logical_port.lacp_reply" + - sql: "DROP INDEX lag_logical_port_unique" + - sql: "CREATE INDEX lag_logical_port_unique on lag_logical_port (switch_id, logical_port_number) UNIQUE_HASH_INDEX" + - sql: "DROP PROPERTY lag_logical_port.lacp_reply IF EXISTS" diff --git a/docker/db-migration/migrations/root.yaml b/docker/db-migration/migrations/root.yaml index c362ca380b0..279a88b027b 100644 --- a/docker/db-migration/migrations/root.yaml +++ b/docker/db-migration/migrations/root.yaml @@ -78,3 +78,6 @@ databaseChangeLog: - include: relativeToChangelogFile: true file: 022-add-port-class.yaml + - include: + relativeToChangelogFile: true + file: 023-add-lacp-reply-into-lag-class.yaml diff --git a/docs/design/hub-and-spoke/lag/create/create-lag-port-fsm.png b/docs/design/hub-and-spoke/lag/create/create-lag-port-fsm.png index a3402d6cd96..f929c543aea 100644 Binary files a/docs/design/hub-and-spoke/lag/create/create-lag-port-fsm.png and b/docs/design/hub-and-spoke/lag/create/create-lag-port-fsm.png differ diff --git a/docs/design/hub-and-spoke/lag/create/create-lag-port-fsm.puml b/docs/design/hub-and-spoke/lag/create/create-lag-port-fsm.puml index a2149dd9a6e..567ca22bdd7 100644 --- a/docs/design/hub-and-spoke/lag/create/create-lag-port-fsm.puml +++ b/docs/design/hub-and-spoke/lag/create/create-lag-port-fsm.puml @@ -10,10 +10,15 @@ CREATE_LAG_IN_DB --> GRPC_COMMAND_SEND : next CREATE_LAG_IN_DB --> FINISHED_WITH_ERROR : error CREATE_LAG_IN_DB : enter / validate LAG port request, create GRPC commands -GRPC_COMMAND_SEND --> FINISHED : lag_installed +GRPC_COMMAND_SEND --> SPEAKER_COMMAND_SEND : lag_installed GRPC_COMMAND_SEND --> FINISHED_WITH_ERROR : error GRPC_COMMAND_SEND : enter / send GRPC commands +SPEAKER_COMMAND_SEND --> FINISHED : skip_speaker_commands_installation +SPEAKER_COMMAND_SEND --> FINISHED : speaker_entities_installed +SPEAKER_COMMAND_SEND --> FINISHED_WITH_ERROR : error +SPEAKER_COMMAND_SEND : enter / send speaker commands + FINISHED : enter / send-successful-response FINISHED_WITH_ERROR : enter / [LAG port created in DB] delete created LAG port, send-error-response diff --git a/docs/design/hub-and-spoke/lag/create/h&s-create-lag-port.png b/docs/design/hub-and-spoke/lag/create/h&s-create-lag-port.png index b6f979b3881..4c6931e9af2 100644 Binary files a/docs/design/hub-and-spoke/lag/create/h&s-create-lag-port.png and b/docs/design/hub-and-spoke/lag/create/h&s-create-lag-port.png differ diff --git a/docs/design/hub-and-spoke/lag/create/h&s-create-lag-port.puml b/docs/design/hub-and-spoke/lag/create/h&s-create-lag-port.puml index 3bcf9e2324f..b0212400190 100644 --- a/docs/design/hub-and-spoke/lag/create/h&s-create-lag-port.puml +++ b/docs/design/hub-and-spoke/lag/create/h&s-create-lag-port.puml @@ -6,6 +6,7 @@ boundary Northbound as NB participant SwitchManager << Hub >> participant SpeakerWorker participant GRPC +participant Floodlight database DB User -> NB : Create LAG @@ -25,7 +26,16 @@ GRPC -> SpeakerWorker : CreateLogicalPortResponse activate SpeakerWorker SpeakerWorker -> SwitchManager : CreateLogicalPortResponse deactivate SpeakerWorker -SwitchManager ->> NB: LagPortResponse +SwitchManager -> SpeakerWorker : OfCommands +activate SpeakerWorker +SpeakerWorker -> Floodlight : OfCommands +deactivate SpeakerWorker +Floodlight -> Floodlight : Installing rules\nand meter +Floodlight -> SpeakerWorker : OfResponse +activate SpeakerWorker +SpeakerWorker -> SwitchManager : OfResponse +deactivate SpeakerWorker +SwitchManager -> NB : LagPortResponse deactivate SwitchManager NB -> User: LagPortDto deactivate NB diff --git a/docs/design/hub-and-spoke/lag/delete/delete-lag-port-fsm.png b/docs/design/hub-and-spoke/lag/delete/delete-lag-port-fsm.png index 22107796e91..aff4cea650c 100644 Binary files a/docs/design/hub-and-spoke/lag/delete/delete-lag-port-fsm.png and b/docs/design/hub-and-spoke/lag/delete/delete-lag-port-fsm.png differ diff --git a/docs/design/hub-and-spoke/lag/delete/delete-lag-port-fsm.puml b/docs/design/hub-and-spoke/lag/delete/delete-lag-port-fsm.puml index e3244c4a25b..7086dba55ba 100644 --- a/docs/design/hub-and-spoke/lag/delete/delete-lag-port-fsm.puml +++ b/docs/design/hub-and-spoke/lag/delete/delete-lag-port-fsm.puml @@ -3,12 +3,21 @@ title LAG port delete FSM [*] --> START -START --> CREATE_GRPC_COMMAND : next +START --> VALIDATE_REMOVE_REQUEST : next START --> FINISHED_WITH_ERROR : error -CREATE_GRPC_COMMAND --> REMOVE_LAG_FROM_DB : lag_removed -CREATE_GRPC_COMMAND --> FINISHED_WITH_ERROR : error -CREATE_GRPC_COMMAND : enter / validate delete LAG port request, create GRPC command +VALIDATE_REMOVE_REQUEST --> SPEAKER_COMMAND_SEND : next +VALIDATE_REMOVE_REQUEST --> FINISHED_WITH_ERROR : error +VALIDATE_REMOVE_REQUEST : enter / validate delete LAG port request, create GRPC command + +SPEAKER_COMMAND_SEND --> GRPC_COMMAND_SEND : skip_speaker_entities_removal +SPEAKER_COMMAND_SEND --> GRPC_COMMAND_SEND : speaker_entities_removed +SPEAKER_COMMAND_SEND --> FINISHED_WITH_ERROR : error +SPEAKER_COMMAND_SEND : enter / send remove commands to speaker + +GRPC_COMMAND_SEND --> REMOVE_LAG_FROM_DB : lag_removed +GRPC_COMMAND_SEND --> FINISHED_WITH_ERROR : error +GRPC_COMMAND_SEND : enter / send command to GRPC REMOVE_LAG_FROM_DB --> FINISHED : next REMOVE_LAG_FROM_DB --> FINISHED_WITH_ERROR : error diff --git a/docs/design/hub-and-spoke/lag/delete/h&s-delete-lag-port.png b/docs/design/hub-and-spoke/lag/delete/h&s-delete-lag-port.png index 2a7a59d9770..4e3868e2d4a 100644 Binary files a/docs/design/hub-and-spoke/lag/delete/h&s-delete-lag-port.png and b/docs/design/hub-and-spoke/lag/delete/h&s-delete-lag-port.png differ diff --git a/docs/design/hub-and-spoke/lag/delete/h&s-delete-lag-port.puml b/docs/design/hub-and-spoke/lag/delete/h&s-delete-lag-port.puml index b8bba348e80..3be665e4a9a 100644 --- a/docs/design/hub-and-spoke/lag/delete/h&s-delete-lag-port.puml +++ b/docs/design/hub-and-spoke/lag/delete/h&s-delete-lag-port.puml @@ -5,6 +5,7 @@ actor User boundary Northbound as NB participant SwitchManager << Hub >> participant SpeakerWorker +participant Floodlight participant GRPC database DB @@ -14,8 +15,15 @@ activate NB NB -> SwitchManager : DeleteLagPortRequest activate SwitchManager SwitchManager -> SwitchManager : Request validation -SwitchManager -> DB : Delete LAG from DB -DB -> SwitchManager +SwitchManager -> SpeakerWorker : OfCommands +activate SpeakerWorker +SpeakerWorker -> Floodlight : OfCommands +deactivate SpeakerWorker +Floodlight -> Floodlight : Remove rules\nand meter +Floodlight -> SpeakerWorker : OfResponse +activate SpeakerWorker +SpeakerWorker -> SwitchManager : OfResponse +deactivate SpeakerWorker SwitchManager -> SpeakerWorker : DeleteLogicalPortRequest activate SpeakerWorker SpeakerWorker -> GRPC : DeleteLogicalPortRequest @@ -25,6 +33,8 @@ GRPC -> SpeakerWorker : DeleteLogicalPortResponse activate SpeakerWorker SpeakerWorker -> SwitchManager : DeleteLogicalPortResponse deactivate SpeakerWorker +SwitchManager -> DB : Delete LAG from DB +DB -> SwitchManager SwitchManager ->> NB: LagPortResponse deactivate SwitchManager NB -> User: LagPortDto diff --git a/docs/design/multi-table-pipelines/cookies.md b/docs/design/multi-table-pipelines/cookies.md index ddd361e27bc..b2df1f12ced 100644 --- a/docs/design/multi-table-pipelines/cookies.md +++ b/docs/design/multi-table-pipelines/cookies.md @@ -153,6 +153,7 @@ Constraints: |`0x8000_0000_0000_001C`|`SERVER_42_ISL_RTT_OUTPUT_COOKIE`|Sends ISL RTT packet back to server42| |`0x8000_0000_0000_001D`|`SERVER_42_ISL_RTT_TURNING_COOKIE`|Sends ISL RTT packet back to IN_PORT| |`0x8000_0000_0000_001E`|`SERVER_42_FLOW_RTT_VXLAN_TURNING_COOKIE`|Catches flow RTT packet for VXLAN Flows, swaps ETH src and dst and sends back to IN_PORT| +|`0x8000_0000_0000_001F`|`DROP_SLOW_PROTOCOLS_LOOP_COOKIE`| Catches and drops LACP reply packet if ETH_SRC of this packet is equal to switch ID (packet returned to switch which sent it. It means we have a loop)| |`0x8010_0000_XXXX_XXXX`|`LLDP_INPUT_CUSTOMER`|Marks LLDP packets from port XXX by metadata| |`0x8020_0000_XXXX_XXXX`|`MULTI_TABLE_ISL_VLAN_EGRESS`|Moves Vlan packets received from ISL port XXX from input table to egress table| |`0x8030_0000_XXXX_XXXX`|`MULTI_TABLE_ISL_VXLAN_EGRESS`|Moves VXLAN packets received from ISL port XXX from input table to egress table| @@ -164,6 +165,7 @@ Constraints: |`0x8090_0000_XXXX_XXXX`|`SERVER_42_FLOW_RTT_INPUT`|Receives server42 flow RTT packet from port XXX, puts timestamp into packet (if switch has such support) and move packet to pre-ingress table| |`0x80A0_0000_0000_0000`|`APPLICATION_MIRROR_FLOW`|Flow mirror traffic for application purposes.| |`0x80D0_0000_XXXX_XXXX`|`SERVER_42_ISL_RTT_INPUT`|Forwards server42 ISL RTT packet to ISL port XXX| +|`0x80E0_0000_XXXX_XXXX`|`LACP_REPLY_INPUT`|Catches LACP request packet from port XXX, sends it to Floodlight for modiffication and sending back to port| |`0x4000_0000_000X_XXXX`|`INGRESS_FORWARD`|Receives Customer packets, push transit encapsulation if needed and sends to port. Path direction - forward, XXX - path unmasked cookie| |`0x2000_0000_000X_XXXX`|`INGRESS_REVERSE`|Receives Customer packets, push transit encapsulation if needed and sends to port. Path direction - reverse, XXX - path unmasked cookie| |`0x4008_0000_000X_XXXX`|`FLOW_LOOP_FORWARD`|Makes flow loop for forward direction (sends all customer traffic back to IN_PORT). XXX - path unmasked cookie| diff --git a/src-java/floodlight-service/floodlight-api/src/main/java/org/openkilda/floodlight/api/request/rulemanager/OfCommand.java b/src-java/floodlight-service/floodlight-api/src/main/java/org/openkilda/floodlight/api/request/rulemanager/OfCommand.java index e9b910beb6b..db4034fe350 100644 --- a/src-java/floodlight-service/floodlight-api/src/main/java/org/openkilda/floodlight/api/request/rulemanager/OfCommand.java +++ b/src-java/floodlight-service/floodlight-api/src/main/java/org/openkilda/floodlight/api/request/rulemanager/OfCommand.java @@ -15,13 +15,23 @@ package org.openkilda.floodlight.api.request.rulemanager; +import static java.lang.String.format; + import org.openkilda.model.SwitchId; +import org.openkilda.rulemanager.FlowSpeakerData; +import org.openkilda.rulemanager.GroupSpeakerData; +import org.openkilda.rulemanager.MeterSpeakerData; +import org.openkilda.rulemanager.SpeakerData; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes.Type; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + @JsonTypeInfo(use = Id.NAME, property = "clazz") @JsonSubTypes({ @Type(value = FlowCommand.class, @@ -38,4 +48,23 @@ public abstract class OfCommand { public abstract void buildModify(OfEntityBatch builder, SwitchId switchId); public abstract void buildDelete(OfEntityBatch builder, SwitchId switchId); + + + /** + * Converts SpeakerData to OfCommand. + */ + public static OfCommand toOfCommand(SpeakerData speakerData) { + if (speakerData instanceof FlowSpeakerData) { + return new FlowCommand((FlowSpeakerData) speakerData); + } else if (speakerData instanceof MeterSpeakerData) { + return new MeterCommand((MeterSpeakerData) speakerData); + } else if (speakerData instanceof GroupSpeakerData) { + return new GroupCommand((GroupSpeakerData) speakerData); + } + throw new IllegalStateException(format("Unknown speaker data type %s", speakerData)); + } + + public static List toOfCommands(Collection speakerData) { + return speakerData.stream().map(OfCommand::toOfCommand).collect(Collectors.toList()); + } } diff --git a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/KildaCore.java b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/KildaCore.java index 83d7b343641..b731305b5bb 100644 --- a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/KildaCore.java +++ b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/KildaCore.java @@ -20,6 +20,7 @@ import org.openkilda.floodlight.service.FeatureDetectorService; import org.openkilda.floodlight.service.IService; import org.openkilda.floodlight.service.connected.ConnectedDevicesService; +import org.openkilda.floodlight.service.lacp.LacpService; import org.openkilda.floodlight.service.of.InputService; import org.openkilda.floodlight.service.session.SessionService; import org.openkilda.floodlight.service.zookeeper.ZooKeeperService; @@ -55,6 +56,7 @@ public KildaCore() { .put(SessionService.class, new SessionService()) .put(FeatureDetectorService.class, new FeatureDetectorService()) .put(ConnectedDevicesService.class, new ConnectedDevicesService()) + .put(LacpService.class, new LacpService()) .put(ZooKeeperService.class, new ZooKeeperService()) .build(); } diff --git a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/KildaCoreConfig.java b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/KildaCoreConfig.java index da6dbf2e54b..e8efd201f18 100644 --- a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/KildaCoreConfig.java +++ b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/KildaCoreConfig.java @@ -22,6 +22,9 @@ import com.sabre.oss.conf4j.annotation.Default; import com.sabre.oss.conf4j.annotation.Key; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; + public interface KildaCoreConfig { @Key("command-processor-workers-count") @Default("4") @@ -58,15 +61,18 @@ public interface KildaCoreConfig { @Converter(EnumLowerCaseConverter.class) FloodlightRole getRole(); - /** - * This offset is used for encoding ISL in_port number into udp_src port of Server 42 ISL RTT packets. - */ - @Key("server42-isl-rtt-udp-port-offset") - @Default("10000") - int getServer42IslRttUdpPortOffset(); + @Key("lacp-system-id") + @Default("00:00:00:00:00:01") + @NotBlank + String getLacpSystemId(); - @Key("server42-isl-rtt-magic-mac-address") - @Default("00:26:E1:FF:FF:FD") - String getServer42IslRttMagicMacAddress(); + @Key("lacp-system-priority") + @Min(1) + @Default("1") + int getLacpSystemPriority(); + @Key("lacp-port-priority") + @Min(1) + @Default("1") + int getLacpPortPriority(); } diff --git a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/service/lacp/LacpService.java b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/service/lacp/LacpService.java new file mode 100644 index 00000000000..8f51ef64306 --- /dev/null +++ b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/service/lacp/LacpService.java @@ -0,0 +1,242 @@ +/* Copyright 2022 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.floodlight.service.lacp; + +import org.openkilda.floodlight.KafkaChannel; +import org.openkilda.floodlight.KildaCore; +import org.openkilda.floodlight.KildaCoreConfig; +import org.openkilda.floodlight.command.Command; +import org.openkilda.floodlight.command.CommandContext; +import org.openkilda.floodlight.model.OfInput; +import org.openkilda.floodlight.service.IService; +import org.openkilda.floodlight.service.kafka.KafkaUtilityService; +import org.openkilda.floodlight.service.of.IInputTranslator; +import org.openkilda.floodlight.service.of.InputService; +import org.openkilda.floodlight.shared.packet.Lacp; +import org.openkilda.floodlight.shared.packet.Lacp.ActorPartnerInfo; +import org.openkilda.floodlight.shared.packet.SlowProtocols; +import org.openkilda.model.SwitchId; +import org.openkilda.model.cookie.Cookie; +import org.openkilda.model.cookie.CookieBase.CookieType; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.internal.IOFSwitchService; +import net.floodlightcontroller.core.module.FloodlightModuleContext; +import net.floodlightcontroller.packet.Ethernet; +import net.floodlightcontroller.packet.IPacket; +import net.floodlightcontroller.util.OFMessageUtils; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketOut; +import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.match.MatchField; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.EthType; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.U64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LacpService implements IService, IInputTranslator { + private static final Logger logger = LoggerFactory.getLogger(LacpService.class); + + private IOFSwitchService switchService; + private MacAddress systemId; + private int systemPriority; + private int portPriority; + + static { + try { + logger.info("Force loading of {}", Class.forName(SlowProtocols.class.getName())); + } catch (ClassNotFoundException e) { + logger.error(String.format("Couldn't load class SlowProtocols %s", e.getMessage()), e); + } + } + + @Override + public Command makeCommand(CommandContext context, OfInput input) { + return new Command(context) { + @Override + public Command call() { + handlePacketIn(input); + return null; + } + }; + } + + @VisibleForTesting + Lacp deserializeLacp(Ethernet eth, SwitchId switchId, long cookie) { + try { + IPacket payload = eth.getPayload(); + + if (payload instanceof SlowProtocols) { + SlowProtocols slowProtocol = ((SlowProtocols) payload); + if (slowProtocol.getPayload() instanceof Lacp) { + return (Lacp) slowProtocol.getPayload(); + } else { + if (logger.isTraceEnabled()) { + logger.trace("Got unknown slow protocol packet {} on switch {}", + slowProtocol.getSubtype(), switchId); + } + return null; + } + } + } catch (Exception e) { + logger.info(String.format("Could not deserialize lacp packet %s on switch %s. Deserialization failure: %s", + eth, switchId, e.getMessage()), e); + return null; + } + logger.info("Got invalid lacp packet: {} on switch {}. Cookie {}", eth, switchId, cookie); + return null; + } + + private void handlePacketIn(OfInput input) { + U64 rawCookie = input.packetInCookie(); + + if (rawCookie == null) { + return; + } + + Cookie cookie = new Cookie(rawCookie.getValue()); + SwitchId switchId = new SwitchId(input.getDpId().getLong()); + + if (cookie.getType() == CookieType.LACP_REPLY_INPUT) { + if (logger.isDebugEnabled()) { + logger.debug("Receive LACP packet from {} OF-xid:{}, cookie: {}", + input.getDpId(), input.getMessage().getXid(), cookie); + } + handleLacp(input, switchId, cookie.getValue(), getInPort((OFPacketIn) input.getMessage())); + } + } + + private void handleLacp(OfInput input, SwitchId switchId, long cookie, OFPort inPort) { + Ethernet ethernet = input.getPacketInPayload(); + Lacp lacpRequest = deserializeLacp(ethernet, switchId, cookie); + if (lacpRequest == null) { + return; + } + if (logger.isDebugEnabled()) { + logger.debug("Switch {} received LACP request from port {}. Request: {}", switchId, inPort, lacpRequest); + } + Lacp lacpReply = modifyLacpRequest(lacpRequest); + sendLacpReply(DatapathId.of(switchId.getId()), inPort, lacpReply); + } + + @VisibleForTesting + Lacp modifyLacpRequest(Lacp lacpRequest) { + lacpRequest.setPartner(new ActorPartnerInfo(lacpRequest.getActor())); + + lacpRequest.getActor().getState().setActive(false); + lacpRequest.getActor().getState().setSynchronised(true); + lacpRequest.getActor().getState().setAggregatable(true); + lacpRequest.getActor().getState().setExpired(false); + lacpRequest.getActor().getState().setDefaulted(false); + lacpRequest.getActor().setPortPriority(portPriority); + lacpRequest.getActor().setSystemPriority(systemPriority); + lacpRequest.getActor().setSystemId(systemId); + return lacpRequest; + } + + OFPacketOut generateLacpPacket(IOFSwitch sw, Lacp lacp, OFPort outPort) { + try { + SlowProtocols slowProtocols = new SlowProtocols(); + slowProtocols.setSubtype(SlowProtocols.LACP_SUBTYPE); + slowProtocols.setPayload(lacp); + + Ethernet l2 = new Ethernet().setSourceMACAddress(new SwitchId(sw.getId().getLong()).toMacAddress()) + .setDestinationMACAddress(org.openkilda.model.MacAddress.SLOW_PROTOCOLS.getAddress()) + .setEtherType(EthType.SLOW_PROTOCOLS); + l2.setPayload(slowProtocols); + + byte[] data = l2.serialize(); + OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut() + .setBufferId(OFBufferId.NO_BUFFER) + .setActions( + Lists.newArrayList( + sw.getOFFactory().actions().buildOutput().setPort(outPort).build())) + .setData(data); + OFMessageUtils.setInPort(pob, OFPort.CONTROLLER); + + return pob.build(); + } catch (Exception e) { + logger.error(String.format("Error during generation of LACP packet: %s", e.getMessage()), e); + } + return null; + } + + private void sendLacpReply(DatapathId dpId, OFPort port, Lacp lacpReply) { + try { + IOFSwitch sw = switchService.getSwitch(dpId); + SwitchId switchId = new SwitchId(dpId.getLong()); + if (sw != null && sw.getPort(port) != null) { + OFPacketOut ofPacketOut = generateLacpPacket(sw, lacpReply, port); + boolean result = false; + if (ofPacketOut != null) { + if (logger.isDebugEnabled()) { + logger.debug("Sending LACP reply out {}/{}: {}", switchId, port.getPortNumber(), lacpReply); + } + result = sw.write(ofPacketOut); + } else { + logger.warn("Received null from generateLacpPacket. switch: {}, port: {}", switchId, port); + } + + if (!result) { + logger.error("Failed to send PACKET_OUT(LACP reply packet) via {}-{}. Packet {}", + sw.getId(), port.getPortNumber(), lacpReply); + } + } else { + logger.error("Couldn't find switch {} to send LACP reply", switchId); + } + } catch (Exception exception) { + logger.error(String.format("Unhandled exception in %s", getClass().getName()), exception); + } + } + + private static OFPort getInPort(OFPacketIn packetIn) { + if (packetIn.getVersion().compareTo(OFVersion.OF_12) < 0) { + return packetIn.getInPort(); + } + + if (packetIn.getMatch().supports(MatchField.IN_PHY_PORT)) { + OFPort inPort = packetIn.getMatch().get(MatchField.IN_PHY_PORT); + if (inPort != null) { + return inPort; + } + } + return packetIn.getMatch().get(MatchField.IN_PORT); + } + + @Override + public void setup(FloodlightModuleContext context) { + logger.info("Stating {}", LacpService.class.getCanonicalName()); + KafkaChannel kafkaChannel = context.getServiceImpl(KafkaUtilityService.class).getKafkaChannel(); + logger.info("region: {}", kafkaChannel.getRegion()); + + switchService = context.getServiceImpl(IOFSwitchService.class); + + KildaCoreConfig coreConfig = context.getServiceImpl(KildaCore.class).getConfig(); + systemId = MacAddress.of(coreConfig.getLacpSystemId()); + systemPriority = coreConfig.getLacpSystemPriority(); + portPriority = coreConfig.getLacpPortPriority(); + + InputService inputService = context.getServiceImpl(InputService.class); + inputService.addTranslator(OFType.PACKET_IN, this); + } +} diff --git a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/shared/packet/Lacp.java b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/shared/packet/Lacp.java new file mode 100644 index 00000000000..0b04ba229ce --- /dev/null +++ b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/shared/packet/Lacp.java @@ -0,0 +1,268 @@ +/* Copyright 2022 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.floodlight.shared.packet; + +import static java.lang.Short.toUnsignedInt; +import static java.lang.String.format; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.floodlightcontroller.packet.BasePacket; +import net.floodlightcontroller.packet.IPacket; +import net.floodlightcontroller.packet.PacketParsingException; +import org.projectfloodlight.openflow.types.MacAddress; + +import java.nio.ByteBuffer; + +@Slf4j +@Data +@EqualsAndHashCode(callSuper = false) +public class Lacp extends BasePacket { + public static byte LACP_VERSION = 0x01; + public static byte ACTOR_TYPE = 0x01; + public static byte PARTNER_TYPE = 0x02; + public static byte TERMINATOR_PAD_COUNT = 52; + public static byte LACP_LENGTH = 109; + + private ActorPartnerInfo actor; + private ActorPartnerInfo partner; + private CollectorInformation collectorInformation; + + @Override + public byte[] serialize() { + byte[] data = new byte[LACP_LENGTH]; + + ByteBuffer bb = ByteBuffer.wrap(data); + bb.put(LACP_VERSION); + bb.put(actor.serialize(ACTOR_TYPE)); + bb.put(partner.serialize(PARTNER_TYPE)); + bb.put(collectorInformation.serialize()); + for (int i = 0; i < TERMINATOR_PAD_COUNT; i++) { + bb.put((byte) 0); + } + return data; + } + + @Override + public IPacket deserialize(byte[] data, int offset, int length) throws PacketParsingException { + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + if (bb.remaining() < LACP_LENGTH) { + throw new PacketParsingException(format("Data %s is too short for LACP. " + + "Length of it must be at least %s", bytesToHex(data, offset, length), LACP_LENGTH)); + } + byte version = bb.get(); + if (version != LACP_VERSION) { + throw new PacketParsingException(format("Unknown LACP version %02X. Expected LACP version %02X", + version, LACP_VERSION)); + } + + byte[] actorBytes = new byte[ActorPartnerInfo.LENGTH]; + bb.get(actorBytes); + actor = new ActorPartnerInfo(); + actor.deserialize(actorBytes); + + byte[] partnerBytes = new byte[ActorPartnerInfo.LENGTH]; + bb.get(partnerBytes); + partner = new ActorPartnerInfo(); + partner.deserialize(partnerBytes); + + byte[] collectorBytes = new byte[CollectorInformation.LENGTH]; + bb.get(collectorBytes); + collectorInformation = new CollectorInformation(); + collectorInformation.deserialize(collectorBytes); + + return this; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class ActorPartnerInfo { + public static final byte TLV_TYPE_ACTOR = 0x01; + public static final byte TLV_TYPE_PARTNER = 0x02; + public static final byte LENGTH = 20; + private static final int RESERVED_LENGTH_BYTES = 3; + + private int systemPriority; + private MacAddress systemId; + private int key; + private int portPriority; + private int portNumber; + private State state; + + public ActorPartnerInfo(ActorPartnerInfo info) { + this(info.systemPriority, MacAddress.of(info.systemId.getLong()), info.key, info.portPriority, + info.portNumber, new State(info.state)); + } + + byte[] serialize(byte type) { + byte[] data = new byte[LENGTH]; + ByteBuffer bb = ByteBuffer.wrap(data); + bb.put(type); + bb.put(LENGTH); + bb.putShort((short) systemPriority); + bb.put(systemId.getBytes()); + bb.putShort((short) key); + bb.putShort((short) portPriority); + bb.putShort((short) portNumber); + bb.put(state.serialize()); + for (int i = 0; i < RESERVED_LENGTH_BYTES; i++) { + bb.put((byte) 0); + } + return data; + } + + void deserialize(byte[] data) throws PacketParsingException { + if (LENGTH > data.length) { + throw new PacketParsingException(format("Data %s is too short for ActorPartnerInfo. " + + "Length of it must be at least %s", bytesToHex(data, 0, data.length), LENGTH)); + } + ByteBuffer bb = ByteBuffer.wrap(data); + bb.get(); // Actor or Partner type + byte length = bb.get(); + if (length != LENGTH) { + throw new PacketParsingException(format("Invalid length %s for ActorPartnerInfo. It must be %s", + length, LENGTH)); + } + systemPriority = toUnsignedInt(bb.getShort()); + + byte[] macAsBytes = new byte[MacAddress.FULL_MASK.getLength()]; + bb.get(macAsBytes); + systemId = MacAddress.of(macAsBytes); + key = toUnsignedInt(bb.getShort()); + portPriority = toUnsignedInt(bb.getShort()); + portNumber = toUnsignedInt(bb.getShort()); + + state = new State(); + state.deserialize(bb.get()); + } + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class State { + private static final int ACTIVE_SHIFT = 0; + private static final int SHORT_TIME_SHIFT = 1; + private static final int AGGREGATABLE_SHIFT = 2; + private static final int SYNCHRONISED_SHIFT = 3; + private static final int COLLECTING_SHIFT = 4; + private static final int DISTRIBUTING_SHIFT = 5; + private static final int DEFAULTED_SHIFT = 6; + private static final int EXPIRED_SHIFT = 7; + + private boolean active; + private boolean shortTimeout; + private boolean aggregatable; + private boolean synchronised; + private boolean collecting; + private boolean distributing; + private boolean defaulted; + private boolean expired; + + public State(State state) { + this(state.active, state.shortTimeout, state.aggregatable, state.synchronised, state.collecting, + state.distributing, state.defaulted, state.expired); + } + + byte serialize() { + byte data = 0; + data = setField(data, active, ACTIVE_SHIFT); + data = setField(data, shortTimeout, SHORT_TIME_SHIFT); + data = setField(data, aggregatable, AGGREGATABLE_SHIFT); + data = setField(data, synchronised, SYNCHRONISED_SHIFT); + data = setField(data, collecting, COLLECTING_SHIFT); + data = setField(data, distributing, DISTRIBUTING_SHIFT); + data = setField(data, defaulted, DEFAULTED_SHIFT); + data = setField(data, expired, EXPIRED_SHIFT); + return data; + } + + void deserialize(byte data) { + active = getField(data, ACTIVE_SHIFT); + shortTimeout = getField(data, SHORT_TIME_SHIFT); + aggregatable = getField(data, AGGREGATABLE_SHIFT); + synchronised = getField(data, SYNCHRONISED_SHIFT); + collecting = getField(data, COLLECTING_SHIFT); + distributing = getField(data, DISTRIBUTING_SHIFT); + defaulted = getField(data, DEFAULTED_SHIFT); + expired = getField(data, EXPIRED_SHIFT); + } + + private byte setField(byte data, boolean value, int shift) { + if (value) { + data |= 1 << shift; + } + return data; + } + + private boolean getField(byte data, int shift) { + return (data & (1 << shift)) != 0; + } + } + + @Data + public static class CollectorInformation { + public static final byte TLV_TYPE_COLLECTOR_INFO = 0x03; + + public static final byte LENGTH = 16; + private static final int RESERVED_LENGTH_BYTES = 12; + + private byte type; + private int maxDelay; + + byte[] serialize() { + byte[] data = new byte[LENGTH]; + ByteBuffer bb = ByteBuffer.wrap(data); + bb.put(type); + bb.put(LENGTH); + bb.putShort((short) maxDelay); + for (int i = 0; i < RESERVED_LENGTH_BYTES; i++) { + bb.put((byte) 0); + } + return data; + } + + void deserialize(byte[] data) throws PacketParsingException { + if (LENGTH > data.length) { + throw new PacketParsingException(format("Data %s is too short for CollectorInformation. " + + "Length of it must be at least %s", bytesToHex(data, 0, LENGTH), LENGTH)); + } + ByteBuffer bb = ByteBuffer.wrap(data, 0, LENGTH); + type = bb.get(); + byte length = bb.get(); + if (length != LENGTH) { + throw new PacketParsingException(format("Invalid length %s for ActorPartnerInfo. It must be %s", + length, LENGTH)); + } + maxDelay = toUnsignedInt(bb.getShort()); + } + } + + private static String bytesToHex(byte[] bytes, int offset, int length) { + StringBuilder sb = new StringBuilder(); + for (int i = offset; i < offset + length && i < bytes.length; i++) { + if (i > offset) { + sb.append(' '); + } + sb.append(format("%02X", bytes[i])); + } + return sb.toString(); + } +} diff --git a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/shared/packet/SlowProtocols.java b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/shared/packet/SlowProtocols.java new file mode 100644 index 00000000000..b7dc8d8e34d --- /dev/null +++ b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/shared/packet/SlowProtocols.java @@ -0,0 +1,80 @@ +/* Copyright 2020 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.floodlight.shared.packet; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import net.floodlightcontroller.packet.BasePacket; +import net.floodlightcontroller.packet.Ethernet; +import net.floodlightcontroller.packet.IPacket; +import org.projectfloodlight.openflow.types.EthType; + +import java.nio.ByteBuffer; + +@Slf4j +@Data +@EqualsAndHashCode(callSuper = false) +public class SlowProtocols extends BasePacket { + public static final int SLOW_PROTOCOL_LENGTH_IN_BYTES = 1; + public static final byte LACP_SUBTYPE = 0x01; + + private byte subtype; + + static { + Ethernet.etherTypeClassMap.put((short) EthType.SLOW_PROTOCOLS.getValue(), SlowProtocols.class); + } + + @Override + public byte[] serialize() { + byte[] payloadData = new byte[0]; + if (payload != null) { + payload.setParent(this); + payloadData = payload.serialize(); + } + ByteBuffer bb = ByteBuffer.allocate(SLOW_PROTOCOL_LENGTH_IN_BYTES + payloadData.length); + bb.put(subtype); + bb.put(payloadData); + return bb.array(); + } + + @Override + public IPacket deserialize(byte[] data, int offset, int length) { + if (length == 0) { + return null; + } + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + + subtype = bb.get(); + if (subtype == LACP_SUBTYPE) { + try { + payload = new Lacp().deserialize(data, bb.position(), bb.limit() - bb.position()); + } catch (Exception e) { + if (log.isTraceEnabled()) { + log.trace(String.format("Failed to parse Slow Protocols with subtype LACP: %s", e.getMessage()), e); + } + } + } + if (payload == null) { + byte[] buf = new byte[bb.remaining()]; + bb.get(buf); + payload = new net.floodlightcontroller.packet.Data(buf); + } + + payload.setParent(this); + return this; + } +} diff --git a/src-java/floodlight-service/floodlight-modules/src/main/resources/floodlightkilda.properties.example b/src-java/floodlight-service/floodlight-modules/src/main/resources/floodlightkilda.properties.example index 7681a094741..bb15dd11bf1 100644 --- a/src-java/floodlight-service/floodlight-modules/src/main/resources/floodlightkilda.properties.example +++ b/src-java/floodlight-service/floodlight-modules/src/main/resources/floodlightkilda.properties.example @@ -64,8 +64,9 @@ org.openkilda.floodlight.KildaCore.role = management #org.openkilda.floodlight.KildaCore.command-processor-idle-workers-keep-alive-seconds = 300 #org.openkilda.floodlight.KildaCore.flow-ping-magic-src-mac-address=00:26:E1:FF:FF:FE org.openkilda.floodlight.KildaCore.server42-flow-rtt-udp-port-offset=5000 -org.openkilda.floodlight.KildaCore.server42-isl-rtt-udp-port-offset=10000 -org.openkilda.floodlight.KildaCore.server42-isl-rtt-magic-mac-address=00:26:E1:FF:FF:FD +org.openkilda.floodlight.KildaCore.lacp-system-id=00:00:00:00:00:01 +org.openkilda.floodlight.KildaCore.lacp-system-priority=1 +org.openkilda.floodlight.KildaCore.lacp-port-priority=1 org.openkilda.floodlight.KafkaChannel.environment-naming-prefix= org.openkilda.floodlight.KafkaChannel.bootstrap-servers=kafka.pendev:9092 org.openkilda.floodlight.KafkaChannel.zookeeper-connect-string=zookeeper.pendev/kilda diff --git a/src-java/floodlight-service/floodlight-modules/src/test/java/org/openkilda/floodlight/service/lacp/LacpServiceTest.java b/src-java/floodlight-service/floodlight-modules/src/test/java/org/openkilda/floodlight/service/lacp/LacpServiceTest.java new file mode 100644 index 00000000000..b7cafca8ca1 --- /dev/null +++ b/src-java/floodlight-service/floodlight-modules/src/test/java/org/openkilda/floodlight/service/lacp/LacpServiceTest.java @@ -0,0 +1,119 @@ +/* Copyright 2022 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.floodlight.service.lacp; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.openkilda.floodlight.KafkaChannel; +import org.openkilda.floodlight.KildaCore; +import org.openkilda.floodlight.KildaCoreConfig; +import org.openkilda.floodlight.service.kafka.KafkaUtilityService; +import org.openkilda.floodlight.service.of.InputService; +import org.openkilda.floodlight.shared.packet.Lacp; +import org.openkilda.floodlight.shared.packet.Lacp.ActorPartnerInfo; +import org.openkilda.floodlight.shared.packet.Lacp.CollectorInformation; +import org.openkilda.floodlight.shared.packet.Lacp.State; +import org.openkilda.floodlight.test.standard.PacketTestBase; + +import net.floodlightcontroller.core.module.FloodlightModuleContext; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.projectfloodlight.openflow.types.MacAddress; + +public class LacpServiceTest extends PacketTestBase { + public static final int ACTOR_SYSTEM_PRIORITY = 1; + public static final MacAddress ACTOR_SYSTEM_ID = MacAddress.of(2); + public static final int ACTOR_KEY = 3; + public static final int ACTOR_PORT_PRIORITY = 4; + public static final int ACTOR_PORT_NUMBER = 5; + + public static final int PARTNER_SYSTEM_PRIORITY = 6; + public static final MacAddress PARTNER_SYSTEM_ID = MacAddress.of(7); + public static final int PARTNER_PORT_PRIORITY = 8; + public static final byte COLLECTOR_TYPE = 9; + public static final byte COLLECTOR_MAX_DELAY = 10; + + LacpService service; + + @Before + public void setUp() { + service = new LacpService(); + KafkaChannel kafkaChannel = mock(KafkaChannel.class); + when(kafkaChannel.getRegion()).thenReturn("region"); + + KafkaUtilityService kafkaUtilityService = mock(KafkaUtilityService.class); + when(kafkaUtilityService.getKafkaChannel()).thenReturn(kafkaChannel); + + FloodlightModuleContext context = mock(FloodlightModuleContext.class); + when(context.getServiceImpl(Mockito.eq(KafkaUtilityService.class))).thenReturn(kafkaUtilityService); + + KildaCoreConfig kildaCoreConfig = mock(KildaCoreConfig.class); + when(kildaCoreConfig.getLacpSystemId()).thenReturn(PARTNER_SYSTEM_ID.toString()); + when(kildaCoreConfig.getLacpSystemPriority()).thenReturn(PARTNER_SYSTEM_PRIORITY); + when(kildaCoreConfig.getLacpPortPriority()).thenReturn(PARTNER_PORT_PRIORITY); + + KildaCore kildaCore = mock(KildaCore.class); + when(kildaCore.getConfig()).thenReturn(kildaCoreConfig); + when(context.getServiceImpl(Mockito.eq(KildaCore.class))).thenReturn(kildaCore); + + InputService inputService = mock(InputService.class); + doNothing().when(inputService).addTranslator(any(), any()); + when(context.getServiceImpl(Mockito.eq(InputService.class))).thenReturn(inputService); + + service.setup(context); + } + + @Test + public void serializeEthWithLacpTest() { + State actorState = new State(true, true, true, false, true, false, true, true); + ActorPartnerInfo actorInfo = new ActorPartnerInfo( + ACTOR_SYSTEM_PRIORITY, ACTOR_SYSTEM_ID, ACTOR_KEY, ACTOR_PORT_PRIORITY, ACTOR_PORT_NUMBER, actorState); + + ActorPartnerInfo emptyPartnerInfo = new ActorPartnerInfo(0, MacAddress.of(0), 0, 0, 0, new State()); + Lacp.CollectorInformation collectorInformation = new CollectorInformation(); + collectorInformation.setType(COLLECTOR_TYPE); + collectorInformation.setType(COLLECTOR_MAX_DELAY); + + Lacp lacpRequest = new Lacp(); + lacpRequest.setActor(actorInfo); + lacpRequest.setPartner(emptyPartnerInfo); + lacpRequest.setCollectorInformation(collectorInformation); + + Lacp lacpReply = service.modifyLacpRequest(lacpRequest); + + // we must copy Actor data to partner + assertEquals(ACTOR_SYSTEM_PRIORITY, lacpReply.getPartner().getSystemPriority()); + assertEquals(ACTOR_SYSTEM_ID, lacpReply.getPartner().getSystemId()); + assertEquals(ACTOR_KEY, lacpReply.getPartner().getKey()); + assertEquals(ACTOR_PORT_PRIORITY, lacpReply.getPartner().getPortPriority()); + assertEquals(ACTOR_PORT_NUMBER, lacpReply.getPartner().getPortNumber()); + assertEquals(new State(true, true, true, false, true, false, true, true), lacpReply.getPartner().getState()); + + // we must keep key and port number equal for actor and partner + assertEquals(ACTOR_KEY, lacpReply.getActor().getKey()); + assertEquals(ACTOR_PORT_NUMBER, lacpReply.getActor().getPortNumber()); + + assertEquals(PARTNER_SYSTEM_PRIORITY, lacpReply.getActor().getSystemPriority()); + assertEquals(PARTNER_SYSTEM_ID, lacpReply.getActor().getSystemId()); + assertEquals(PARTNER_PORT_PRIORITY, lacpReply.getActor().getPortPriority()); + assertEquals(new State(false, true, true, true, true, false, false, false), lacpReply.getActor().getState()); + } +} diff --git a/src-java/floodlight-service/floodlight-modules/src/test/java/org/openkilda/floodlight/service/lacp/LacpTest.java b/src-java/floodlight-service/floodlight-modules/src/test/java/org/openkilda/floodlight/service/lacp/LacpTest.java new file mode 100644 index 00000000000..6a14ca7c401 --- /dev/null +++ b/src-java/floodlight-service/floodlight-modules/src/test/java/org/openkilda/floodlight/service/lacp/LacpTest.java @@ -0,0 +1,116 @@ +/* Copyright 2022 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.floodlight.service.lacp; + +import static org.junit.Assert.assertEquals; + +import org.openkilda.floodlight.shared.packet.Lacp; +import org.openkilda.floodlight.test.standard.PacketTestBase; + +import net.floodlightcontroller.packet.Ethernet; +import net.floodlightcontroller.packet.PacketParsingException; +import org.junit.Assert; +import org.junit.Test; +import org.projectfloodlight.openflow.types.EthType; +import org.projectfloodlight.openflow.types.MacAddress; + +public class LacpTest extends PacketTestBase { + private final byte[] srcAndDstMacAddresses = new byte[] { + 0x1, (byte) 0x80, (byte) 0xC2, 0x0, 0x0, 0xE, // src mac address + 0x10, 0x4E, (byte) 0xF1, (byte) 0xF9, 0x6D, (byte) 0xFA // dst mac address + }; + + private final byte[] lacpPacket = new byte[] { + 0x01, // LACP version + 0x01, // TLV type: actor + 0x14, // TLV length + (byte) 0x91, (byte) 0xf4, // Actor system priority + 0x00, 0x04, (byte) 0x96, 0x1f, 0x50, 0x6a, // Actor system ID + (byte) 0x80, 0x00, // Actor key + 0x00, 0x11, // Actor port priority + 0x00, 0x12, // Actor port + 0x47, // Actor state + 0x00, 0x00, 0x00, // Reserved + 0x02, // TLV type: partner + 0x14, // TLV length + 0x12, 0x34, // Partner system priority + 0x12, 0x34, 0x56, 0x78, (byte) 0x9a, 0x77, // Partner system ID + 0x44, 0x55, // Partner key + 0x11, 0x22, // Partner port priority + 0x00, 0x17, // Partner port + 0x3b, // Partner state + 0x00, 0x00, 0x00, // Reserved + 0x03, // TLV type: collector information + 0x10, // TLV length + 0x00, 0x02, // Max delay + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Reserved + 0x00, // TLV type: terminator + 0x00, // TLV length + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Zeros + + }; + + LacpService service = new LacpService(); + + @Test + public void deserializeLacpTest() throws PacketParsingException { + Lacp lacp = buildLacpPacket(lacpPacket); + assertLacp(lacp); + Assert.assertArrayEquals(lacpPacket, lacp.serialize()); + } + + @Test + public void serializeLacpTest() throws PacketParsingException { + Lacp lacp = buildLacpPacket(lacpPacket); + lacp.getPartner().setPortPriority(0xffff); + Lacp lacp1 = (Lacp) new Lacp().deserialize(lacp.serialize(), 0, lacp.serialize().length); + assertEquals(lacp, lacp1); + } + + @Test + public void serializeEthWithLacpTest() { + Ethernet ethernet = buildEthernet(srcAndDstMacAddresses, + ethTypeToByteArray(EthType.SLOW_PROTOCOLS), new byte[] {0x01}, lacpPacket); + + Lacp lacp = service.deserializeLacp(ethernet, null, 0); + assertLacp(lacp); + } + + private void assertLacp(Lacp lacp) { + assertEquals(0x91f4, lacp.getActor().getSystemPriority()); + assertEquals(MacAddress.of("00:04:96:1f:50:6a"), lacp.getActor().getSystemId()); + assertEquals(0x8000, lacp.getActor().getKey()); + assertEquals(0x11, lacp.getActor().getPortPriority()); + assertEquals(0x12, lacp.getActor().getPortNumber()); + + assertEquals(0x1234, lacp.getPartner().getSystemPriority()); + assertEquals(MacAddress.of("12:34:56:78:9a:77"), lacp.getPartner().getSystemId()); + assertEquals(0x4455, lacp.getPartner().getKey()); + assertEquals(0x1122, lacp.getPartner().getPortPriority()); + assertEquals(0x17, lacp.getPartner().getPortNumber()); + + assertEquals(3, lacp.getCollectorInformation().getType()); + assertEquals(2, lacp.getCollectorInformation().getMaxDelay()); + } + + static Lacp buildLacpPacket(byte[] packet) throws PacketParsingException { + Lacp lacp = new Lacp(); + lacp.deserialize(packet, 0, packet.length); + return lacp; + } +} diff --git a/src-java/kilda-model/src/main/java/org/openkilda/model/LagLogicalPort.java b/src-java/kilda-model/src/main/java/org/openkilda/model/LagLogicalPort.java index 580b459cad1..7406b91f6b1 100644 --- a/src-java/kilda-model/src/main/java/org/openkilda/model/LagLogicalPort.java +++ b/src-java/kilda-model/src/main/java/org/openkilda/model/LagLogicalPort.java @@ -73,8 +73,9 @@ public LagLogicalPort(@NonNull LagLogicalPort entityToClone) { data = LagLogicalPortCloner.INSTANCE.deepCopy(entityToClone.getData(), this); } - public LagLogicalPort(@NonNull SwitchId switchId, int logicalPortNumber, Collection physicalPortNumbers) { - this(switchId, logicalPortNumber, new ArrayList()); + public LagLogicalPort(@NonNull SwitchId switchId, int logicalPortNumber, Collection physicalPortNumbers, + boolean lacpReply) { + this(switchId, logicalPortNumber, new ArrayList(), lacpReply); if (physicalPortNumbers != null) { data.setPhysicalPorts(physicalPortNumbers.stream() .map(port -> new PhysicalPort(switchId, port, this)) @@ -83,10 +84,12 @@ public LagLogicalPort(@NonNull SwitchId switchId, int logicalPortNumber, Collect } @Builder - public LagLogicalPort(@NonNull SwitchId switchId, int logicalPortNumber, List physicalPorts) { + public LagLogicalPort(@NonNull SwitchId switchId, int logicalPortNumber, List physicalPorts, + boolean lacpReply) { data = LagLogicalPortDataImpl.builder() .switchId(switchId) .logicalPortNumber(logicalPortNumber) + .lacpReply(lacpReply) .build(); // The reference is used to link physical ports back to the LAG port. See {@link #setPhysicalPorts(List)}. ((LagLogicalPortDataImpl) data).lagLogicalPort = this; @@ -135,6 +138,10 @@ public interface LagLogicalPortData { List getPhysicalPorts(); void setPhysicalPorts(List physicalPorts); + + boolean isLacpReply(); + + void setLacpReply(boolean lacpReply); } /** @@ -149,6 +156,8 @@ static final class LagLogicalPortDataImpl implements LagLogicalPortData, Seriali int logicalPortNumber; @NonNull SwitchId switchId; + boolean lacpReply; + @Builder.Default @ToString.Exclude @EqualsAndHashCode.Exclude diff --git a/src-java/kilda-model/src/main/java/org/openkilda/model/MacAddress.java b/src-java/kilda-model/src/main/java/org/openkilda/model/MacAddress.java index 577c49abbd4..6e02d0ade03 100644 --- a/src-java/kilda-model/src/main/java/org/openkilda/model/MacAddress.java +++ b/src-java/kilda-model/src/main/java/org/openkilda/model/MacAddress.java @@ -25,6 +25,7 @@ public class MacAddress implements Serializable { private static final long serialVersionUID = 5506319046146663699L; private static final String MAC_ADDRESS_REGEXP = "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"; + public static final MacAddress SLOW_PROTOCOLS = new MacAddress("01:80:C2:00:00:02"); String address; diff --git a/src-java/kilda-model/src/main/java/org/openkilda/model/MeterId.java b/src-java/kilda-model/src/main/java/org/openkilda/model/MeterId.java index 05e6f5d9fcf..25ef9b088de 100644 --- a/src-java/kilda-model/src/main/java/org/openkilda/model/MeterId.java +++ b/src-java/kilda-model/src/main/java/org/openkilda/model/MeterId.java @@ -78,6 +78,7 @@ public final class MeterId implements Comparable, Serializable { */ public static final int MAX_SYSTEM_RULE_METER_ID = 31; + public static final MeterId LACP_REPLY_METER_ID = new MeterId(31); public static final long VERIFICATION_BROADCAST_METER_ID = defaultCookieToMeterId(VERIFICATION_BROADCAST_RULE_COOKIE); public static final long VERIFICATION_UNICAST_METER_ID = defaultCookieToMeterId(VERIFICATION_UNICAST_RULE_COOKIE); diff --git a/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/Cookie.java b/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/Cookie.java index 8797b76be81..30178b14973 100644 --- a/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/Cookie.java +++ b/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/Cookie.java @@ -86,6 +86,8 @@ public class Cookie extends CookieBase implements Comparable { ServiceCookie.ServiceCookieTag.SERVER_42_ISL_RTT_TURNING_COOKIE).getValue(); public static final long SERVER_42_FLOW_RTT_VXLAN_TURNING_COOKIE = new ServiceCookie( ServiceCookieTag.SERVER_42_FLOW_RTT_VXLAN_TURNING_COOKIE).getValue(); + public static final long DROP_SLOW_PROTOCOLS_LOOP_COOKIE = new ServiceCookie( + ServiceCookieTag.DROP_SLOW_PROTOCOLS_LOOP_COOKIE).getValue(); @JsonCreator diff --git a/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/CookieBase.java b/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/CookieBase.java index e6d7535aa59..7583f087136 100644 --- a/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/CookieBase.java +++ b/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/CookieBase.java @@ -156,6 +156,7 @@ public enum CookieType implements NumericEnumField { EXCLUSION_FLOW(0x0B), SERVER_42_FLOW_RTT_INGRESS(0x00C), SERVER_42_ISL_RTT_INPUT(0x00D), + LACP_REPLY_INPUT(0x00E), // This do not consume any value from allowed address space - you can define another field with -1 value. // (must be last entry) diff --git a/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/PortColourCookie.java b/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/PortColourCookie.java index 83daa70f9d6..0241e11035f 100644 --- a/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/PortColourCookie.java +++ b/src-java/kilda-model/src/main/java/org/openkilda/model/cookie/PortColourCookie.java @@ -32,7 +32,8 @@ public class PortColourCookie extends CookieBase implements Comparable { Collection findAll(); @@ -29,4 +32,6 @@ public interface LagLogicalPortRepository extends Repository { Optional findBySwitchIdAndPortNumber(SwitchId switchId, int portNumber); Optional findUnassignedPortInRange(SwitchId switchId, int portFirst, int portLast); + + Map> findBySwitchIds(Set switchIds); } diff --git a/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/frames/LagLogicalPortFrame.java b/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/frames/LagLogicalPortFrame.java index 17ae79996e0..c7b59e1e313 100644 --- a/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/frames/LagLogicalPortFrame.java +++ b/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/frames/LagLogicalPortFrame.java @@ -41,6 +41,7 @@ public abstract class LagLogicalPortFrame extends KildaBaseVertexFrame implement public static final String COMPRISES_PHYSICAL_PORT_EDGE = "comprises"; public static final String SWITCH_ID_PROPERTY = "switch_id"; public static final String LOGICAL_PORT_NUMBER_PROPERTY = "logical_port_number"; + public static final String LACP_REPLY_PROPERTY = "lacp_reply"; private List physicalPorts; @@ -54,6 +55,14 @@ public abstract class LagLogicalPortFrame extends KildaBaseVertexFrame implement @Convert(SwitchIdConverter.class) public abstract void setSwitchId(@NonNull SwitchId switchId); + @Override + @Property(LACP_REPLY_PROPERTY) + public abstract boolean isLacpReply(); + + @Override + @Property(LACP_REPLY_PROPERTY) + public abstract void setLacpReply(boolean lacpReply); + @Override @Property(LOGICAL_PORT_NUMBER_PROPERTY) public abstract int getLogicalPortNumber(); diff --git a/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortRepository.java b/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortRepository.java index ea6438ec826..ef60ea1cec9 100644 --- a/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortRepository.java +++ b/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortRepository.java @@ -15,6 +15,9 @@ package org.openkilda.persistence.ferma.repositories; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toSet; + import org.openkilda.model.LagLogicalPort; import org.openkilda.model.LagLogicalPort.LagLogicalPortData; import org.openkilda.model.SwitchId; @@ -32,9 +35,12 @@ import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; /** @@ -127,6 +133,21 @@ public Optional findUnassignedPortInRange(SwitchId switchId, int portFi return Optional.empty(); } + @Override + public Map> findBySwitchIds(Set switchIds) { + Set graphSwitchIds = switchIds.stream() + .map(SwitchIdConverter.INSTANCE::toGraphProperty) + .collect(toSet()); + List result = new ArrayList<>(); + framedGraph().traverse(g -> g.V() + .hasLabel(LagLogicalPortFrame.FRAME_LABEL) + .has(LagLogicalPortFrame.SWITCH_ID_PROPERTY, P.within(graphSwitchIds))) + .toListExplicit(LagLogicalPortFrame.class).stream() + .map(LagLogicalPort::new) + .forEach(result::add); + return result.stream().collect(groupingBy(LagLogicalPort::getSwitchId, Collectors.toList())); + } + @Override protected LagLogicalPortFrame doAdd(LagLogicalPortData data) { LagLogicalPortFrame frame = KildaBaseVertexFrame.addNewFramedVertex(framedGraph(), diff --git a/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortTest.java b/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortTest.java index 06722f7562a..3dade063f2d 100644 --- a/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortTest.java +++ b/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortTest.java @@ -27,6 +27,7 @@ import org.openkilda.persistence.repositories.PhysicalPortRepository; import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import org.junit.Before; import org.junit.Test; @@ -34,17 +35,20 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Optional; public class FermaLagLogicalPortTest extends InMemoryGraphBasedTest { static final SwitchId SWITCH_ID_1 = new SwitchId(1); static final SwitchId SWITCH_ID_2 = new SwitchId(2); static final SwitchId SWITCH_ID_3 = new SwitchId(3); + static final SwitchId SWITCH_ID_4 = new SwitchId(4); static final int LOGICAL_PORT_NUMBER_1 = 1; static final int LOGICAL_PORT_NUMBER_2 = 2; - static final int LOGICAL_PORT_NUMBER_3 = 2; - static final int PHYSICAL_PORT_NUMBER_1 = 4; - static final int PHYSICAL_PORT_NUMBER_2 = 5; + static final int LOGICAL_PORT_NUMBER_3 = 3; + static final int LOGICAL_PORT_NUMBER_4 = 4; + static final int PHYSICAL_PORT_NUMBER_1 = 5; + static final int PHYSICAL_PORT_NUMBER_2 = 6; LagLogicalPortRepository lagLogicalPortRepository; PhysicalPortRepository physicalPortRepository; @@ -57,9 +61,9 @@ public void setUp() { @Test public void createLogicalPortWithoutPhysicalPortsTest() { - createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1); - createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_2); - createLogicalPort(SWITCH_ID_2, LOGICAL_PORT_NUMBER_3); + createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, true); + createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_2, false); + createLogicalPort(SWITCH_ID_2, LOGICAL_PORT_NUMBER_3, true); List ports = new ArrayList<>(lagLogicalPortRepository.findAll()); @@ -77,11 +81,15 @@ public void createLogicalPortWithoutPhysicalPortsTest() { assertEquals(0, ports.get(0).getPhysicalPorts().size()); assertEquals(0, ports.get(1).getPhysicalPorts().size()); assertEquals(0, ports.get(2).getPhysicalPorts().size()); + + assertTrue(ports.get(0).isLacpReply()); + assertFalse(ports.get(1).isLacpReply()); + assertTrue(ports.get(2).isLacpReply()); } @Test public void createLogicalPortWithPhysicalPortsTest() { - LagLogicalPort logicalPort = createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, + LagLogicalPort logicalPort = createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, true, PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2); List ports = new ArrayList<>(lagLogicalPortRepository.findAll()); @@ -96,11 +104,13 @@ public void createLogicalPortWithPhysicalPortsTest() { assertEquals(logicalPort, ports.get(0).getPhysicalPorts().get(0).getLagLogicalPort()); assertEquals(logicalPort, ports.get(0).getPhysicalPorts().get(1).getLagLogicalPort()); + + assertTrue(ports.get(0).isLacpReply()); } @Test public void createLogicalPortAndSetPhysicalPortsTest() { - LagLogicalPort logicalPort = createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1); + LagLogicalPort logicalPort = createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, false); assertEquals(0, lagLogicalPortRepository.findAll().iterator().next().getPhysicalPorts().size()); PhysicalPort physicalPort1 = createPhysicalPort(SWITCH_ID_1, PHYSICAL_PORT_NUMBER_1, logicalPort); @@ -123,15 +133,15 @@ public void createLogicalPortAndSetPhysicalPortsTest() { @Test public void removeLogicalPortTest() { - LagLogicalPort logicalPort1 = createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, + LagLogicalPort logicalPort1 = createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, true, PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2); - LagLogicalPort logicalPort2 = createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1); + LagLogicalPort logicalPort2 = createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_2, false); transactionManager.doInTransaction(() -> lagLogicalPortRepository.remove(logicalPort2)); List ports = new ArrayList<>(lagLogicalPortRepository.findAll()); assertEquals(1, ports.size()); - assertEquals(2, ports.get(0).getPhysicalPorts().size()); + basicLagAssert(LOGICAL_PORT_NUMBER_1, 2, true, ports.get(0)); assertEquals(2, physicalPortRepository.findAll().size()); transactionManager.doInTransaction(() -> lagLogicalPortRepository.remove(logicalPort1)); @@ -142,44 +152,64 @@ public void removeLogicalPortTest() { @Test public void findBySwitchIdTest() { - createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2); - createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_2); - createLogicalPort(SWITCH_ID_3, LOGICAL_PORT_NUMBER_3); + createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, false, PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2); + createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_2, true); + createLogicalPort(SWITCH_ID_3, LOGICAL_PORT_NUMBER_3, false); List foundPorts = new ArrayList<>(lagLogicalPortRepository.findBySwitchId(SWITCH_ID_1)); foundPorts.sort(Comparator.comparingInt(LagLogicalPort::getLogicalPortNumber)); - assertEquals(LOGICAL_PORT_NUMBER_1, foundPorts.get(0).getLogicalPortNumber()); - assertEquals(LOGICAL_PORT_NUMBER_2, foundPorts.get(1).getLogicalPortNumber()); - assertEquals(2, foundPorts.get(0).getPhysicalPorts().size()); - assertEquals(0, foundPorts.get(1).getPhysicalPorts().size()); + basicLagAssert(LOGICAL_PORT_NUMBER_1, 2, false, foundPorts.get(0)); + basicLagAssert(LOGICAL_PORT_NUMBER_2, 0, true, foundPorts.get(1)); assertEquals(0, lagLogicalPortRepository.findBySwitchId(SWITCH_ID_2).size()); } + @Test + public void findBySwitchIdsTest() { + createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, true, PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2); + createLogicalPort(SWITCH_ID_2, LOGICAL_PORT_NUMBER_2, false); + createLogicalPort(SWITCH_ID_2, LOGICAL_PORT_NUMBER_3, true); + createLogicalPort(SWITCH_ID_3, LOGICAL_PORT_NUMBER_4, false); + + Map> foundPorts = lagLogicalPortRepository.findBySwitchIds( + Sets.newHashSet(SWITCH_ID_1, SWITCH_ID_2)); + + assertEquals(2, foundPorts.size()); + + assertEquals(1, foundPorts.get(SWITCH_ID_1).size()); + basicLagAssert(LOGICAL_PORT_NUMBER_1, 2, true, foundPorts.get(SWITCH_ID_1).get(0)); + + assertEquals(2, foundPorts.get(SWITCH_ID_2).size()); + foundPorts.get(SWITCH_ID_2).sort(Comparator.comparingInt(LagLogicalPort::getLogicalPortNumber)); + basicLagAssert(LOGICAL_PORT_NUMBER_2, 0, false, foundPorts.get(SWITCH_ID_2).get(0)); + basicLagAssert(LOGICAL_PORT_NUMBER_3, 0, true, foundPorts.get(SWITCH_ID_2).get(1)); + + assertEquals(0, lagLogicalPortRepository.findBySwitchIds(Sets.newHashSet(SWITCH_ID_4)).size()); + } + @Test public void findBySwitchIdAndPortNumberTest() { - createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2); - createLogicalPort(SWITCH_ID_2, LOGICAL_PORT_NUMBER_2); + createLogicalPort(SWITCH_ID_1, LOGICAL_PORT_NUMBER_1, true, PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2); + createLogicalPort(SWITCH_ID_2, LOGICAL_PORT_NUMBER_2, false); Optional foundPort1 = lagLogicalPortRepository.findBySwitchIdAndPortNumber( SWITCH_ID_1, LOGICAL_PORT_NUMBER_1); assertTrue(foundPort1.isPresent()); - assertEquals(LOGICAL_PORT_NUMBER_1, foundPort1.get().getLogicalPortNumber()); - assertEquals(2, foundPort1.get().getPhysicalPorts().size()); + basicLagAssert(LOGICAL_PORT_NUMBER_1, 2, true, foundPort1.get()); Optional foundPort2 = lagLogicalPortRepository.findBySwitchIdAndPortNumber( SWITCH_ID_2, LOGICAL_PORT_NUMBER_2); assertTrue(foundPort2.isPresent()); - assertEquals(LOGICAL_PORT_NUMBER_2, foundPort2.get().getLogicalPortNumber()); - assertEquals(0, foundPort2.get().getPhysicalPorts().size()); + basicLagAssert(LOGICAL_PORT_NUMBER_2, 0, false, foundPort2.get()); assertFalse(lagLogicalPortRepository.findBySwitchIdAndPortNumber( SWITCH_ID_3, LOGICAL_PORT_NUMBER_3).isPresent()); } - private LagLogicalPort createLogicalPort(SwitchId switchId, int logicalPortNumber, Integer... physicalPorts) { - LagLogicalPort port = new LagLogicalPort(switchId, logicalPortNumber, Arrays.asList(physicalPorts)); + private LagLogicalPort createLogicalPort( + SwitchId switchId, int logicalPortNumber, boolean lacpReply, Integer... physicalPorts) { + LagLogicalPort port = new LagLogicalPort(switchId, logicalPortNumber, Arrays.asList(physicalPorts), lacpReply); lagLogicalPortRepository.add(port); return port; } @@ -189,4 +219,11 @@ private PhysicalPort createPhysicalPort(SwitchId switchId, int physicalPortNumbe physicalPortRepository.add(port); return port; } + + private void basicLagAssert( + int expectedPortNumber, int expectedPhysPortCount, boolean expectedLacpReply, LagLogicalPort port) { + assertEquals(expectedPortNumber, port.getLogicalPortNumber()); + assertEquals(expectedPhysPortCount, port.getPhysicalPorts().size()); + assertEquals(expectedLacpReply, port.isLacpReply()); + } } diff --git a/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaPhysicalPortTest.java b/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaPhysicalPortTest.java index 4bab45e560d..15a3e143dc1 100644 --- a/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaPhysicalPortTest.java +++ b/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaPhysicalPortTest.java @@ -120,7 +120,7 @@ public void findPortNumbersBySwitchIdTest() { } private LagLogicalPort createLogicalPort(SwitchId switchId, int logicalPortNumber) { - LagLogicalPort port = new LagLogicalPort(switchId, logicalPortNumber, new ArrayList()); + LagLogicalPort port = new LagLogicalPort(switchId, logicalPortNumber, new ArrayList(), true); lagLogicalPortRepository.add(port); return port; } diff --git a/src-java/nbworker-topology/nbworker-messaging/src/main/java/org/openkilda/messaging/nbtopology/response/LagPortDto.java b/src-java/nbworker-topology/nbworker-messaging/src/main/java/org/openkilda/messaging/nbtopology/response/LagPortDto.java index df68f21ea9e..4265d6182c7 100644 --- a/src-java/nbworker-topology/nbworker-messaging/src/main/java/org/openkilda/messaging/nbtopology/response/LagPortDto.java +++ b/src-java/nbworker-topology/nbworker-messaging/src/main/java/org/openkilda/messaging/nbtopology/response/LagPortDto.java @@ -27,6 +27,7 @@ public class LagPortDto implements Serializable { private static final long serialVersionUID = 998335534425508496L; - private int logicalPortNumber; - private List portNumbers; + int logicalPortNumber; + List portNumbers; + boolean lacpReply; } diff --git a/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/share/mappers/LagPortMapperTest.java b/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/share/mappers/LagPortMapperTest.java index d97d1d43c17..fd02c455f73 100644 --- a/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/share/mappers/LagPortMapperTest.java +++ b/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/share/mappers/LagPortMapperTest.java @@ -16,6 +16,7 @@ package org.openkilda.wfm.share.mappers; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import org.openkilda.messaging.nbtopology.response.LagPortDto; import org.openkilda.model.LagLogicalPort; @@ -33,11 +34,12 @@ public class LagPortMapperTest { @Test public void mapLagPortToDtoTest() { LagLogicalPort lagLogicalPort = new LagLogicalPort(SWITCH_ID, LAG_PORT, - Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2)); + Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2), true); LagPortDto dto = LagPortMapper.INSTANCE.map(lagLogicalPort); assertEquals(LAG_PORT, dto.getLogicalPortNumber()); assertEquals(PHYSICAL_PORT_1, dto.getPortNumbers().get(0).intValue()); assertEquals(PHYSICAL_PORT_2, dto.getPortNumbers().get(1).intValue()); + assertTrue(dto.isLacpReply()); } } diff --git a/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/topology/nbworker/services/SwitchOperationsServiceTest.java b/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/topology/nbworker/services/SwitchOperationsServiceTest.java index 0c15e7649df..12061b4686d 100644 --- a/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/topology/nbworker/services/SwitchOperationsServiceTest.java +++ b/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/topology/nbworker/services/SwitchOperationsServiceTest.java @@ -650,7 +650,7 @@ public void shouldReturnLagPorts() throws SwitchNotFoundException { createSwitch(TEST_SWITCH_ID); LagLogicalPort lagLogicalPort = new LagLogicalPort(TEST_SWITCH_ID, LAG_LOGICAL_PORT, - Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2)); + Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2), true); lagLogicalPortRepository.add(lagLogicalPort); Collection ports = switchOperationsService.getSwitchLagPorts(TEST_SWITCH_ID); @@ -659,6 +659,7 @@ public void shouldReturnLagPorts() throws SwitchNotFoundException { assertEquals(LAG_LOGICAL_PORT, ports.iterator().next().getLogicalPortNumber()); assertEquals(PHYSICAL_PORT_1, ports.iterator().next().getPhysicalPorts().get(0).getPortNumber()); assertEquals(PHYSICAL_PORT_2, ports.iterator().next().getPhysicalPorts().get(1).getPortNumber()); + assertTrue(ports.iterator().next().isLacpReply()); } @Test(expected = SwitchNotFoundException.class) diff --git a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/switches/LagPortRequest.java b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/switches/LagPortRequest.java index 2bcca8dc78b..8d4362bd642 100644 --- a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/switches/LagPortRequest.java +++ b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/switches/LagPortRequest.java @@ -18,6 +18,8 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Builder.Default; import lombok.Data; import lombok.NoArgsConstructor; @@ -27,6 +29,9 @@ @NoArgsConstructor @AllArgsConstructor @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) +@Builder public class LagPortRequest { private Set portNumbers; + @Default + private Boolean lacpReply = true; } diff --git a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/switches/LagPortResponse.java b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/switches/LagPortResponse.java index d85e63a31fc..e4e6e690e8c 100644 --- a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/switches/LagPortResponse.java +++ b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/switches/LagPortResponse.java @@ -30,4 +30,5 @@ public class LagPortResponse { private int logicalPortNumber; private List portNumbers; + private boolean lacpReply; } diff --git a/src-java/northbound-service/northbound/src/main/java/org/openkilda/northbound/service/impl/SwitchServiceImpl.java b/src-java/northbound-service/northbound/src/main/java/org/openkilda/northbound/service/impl/SwitchServiceImpl.java index f66197c3ba4..5fc895efbfe 100644 --- a/src-java/northbound-service/northbound/src/main/java/org/openkilda/northbound/service/impl/SwitchServiceImpl.java +++ b/src-java/northbound-service/northbound/src/main/java/org/openkilda/northbound/service/impl/SwitchServiceImpl.java @@ -587,9 +587,11 @@ public CompletableFuture getSwitchConnections(SwitchI @Override public CompletableFuture createLag(SwitchId switchId, LagPortRequest lagPortDto) { - logger.info("Create Link aggregation group on switch {}, ports {}", switchId, lagPortDto.getPortNumbers()); + logger.info("Create Link aggregation group on switch {}, ports {}, LACP reply {}", + switchId, lagPortDto.getPortNumbers(), lagPortDto.getLacpReply()); - CreateLagPortRequest data = new CreateLagPortRequest(switchId, lagPortDto.getPortNumbers()); + CreateLagPortRequest data = new CreateLagPortRequest(switchId, lagPortDto.getPortNumbers(), + lagPortDto.getLacpReply()); CommandMessage request = new CommandMessage(data, System.currentTimeMillis(), RequestCorrelationId.getId()); return messagingChannel.sendAndGet(switchManagerTopic, request) @@ -617,7 +619,8 @@ public CompletableFuture updateLagPort( SwitchId switchId, int logicalPortNumber, LagPortRequest payload) { logger.info("Updating LAG logical port {} on {} with {}", logicalPortNumber, switchId, payload); - UpdateLagPortRequest request = new UpdateLagPortRequest(switchId, logicalPortNumber, payload.getPortNumbers()); + UpdateLagPortRequest request = new UpdateLagPortRequest(switchId, logicalPortNumber, payload.getPortNumbers(), + payload.getLacpReply()); CommandMessage message = new CommandMessage(request, System.currentTimeMillis(), RequestCorrelationId.getId()); return messagingChannel.sendAndGet(switchManagerTopic, message) .thenApply(org.openkilda.messaging.swmanager.response.LagPortResponse.class::cast) diff --git a/src-java/northbound-service/northbound/src/test/java/org/openkilda/northbound/converter/LagPortMapperTest.java b/src-java/northbound-service/northbound/src/test/java/org/openkilda/northbound/converter/LagPortMapperTest.java index 71a5f6115c4..99b31e89d58 100644 --- a/src-java/northbound-service/northbound/src/test/java/org/openkilda/northbound/converter/LagPortMapperTest.java +++ b/src-java/northbound-service/northbound/src/test/java/org/openkilda/northbound/converter/LagPortMapperTest.java @@ -16,6 +16,8 @@ package org.openkilda.northbound.converter; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import org.openkilda.messaging.nbtopology.response.LagPortDto; import org.openkilda.messaging.swmanager.response.LagPortResponse; @@ -41,23 +43,25 @@ public class LagPortMapperTest { @Test public void mapLagPortDtoTest() { LagPortDto response = new LagPortDto(LOGICAL_PORT_NUMBER_1, - Lists.newArrayList(PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2)); + Lists.newArrayList(PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2), true); org.openkilda.northbound.dto.v2.switches.LagPortResponse dto = lagMapper.map(response); assertEquals(LOGICAL_PORT_NUMBER_1, dto.getLogicalPortNumber()); assertEquals(PHYSICAL_PORT_NUMBER_1, dto.getPortNumbers().get(0).intValue()); assertEquals(PHYSICAL_PORT_NUMBER_2, dto.getPortNumbers().get(1).intValue()); + assertTrue(dto.isLacpReply()); } @Test public void mapLagResponseTest() { LagPortResponse response = new LagPortResponse(LOGICAL_PORT_NUMBER_1, - Sets.newHashSet(PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2)); + Sets.newHashSet(PHYSICAL_PORT_NUMBER_1, PHYSICAL_PORT_NUMBER_2), false); org.openkilda.northbound.dto.v2.switches.LagPortResponse dto = lagMapper.map(response); assertEquals(LOGICAL_PORT_NUMBER_1, dto.getLogicalPortNumber()); assertEquals(PHYSICAL_PORT_NUMBER_1, dto.getPortNumbers().get(0).intValue()); assertEquals(PHYSICAL_PORT_NUMBER_2, dto.getPortNumbers().get(1).intValue()); + assertFalse(dto.isLacpReply()); } @TestConfiguration diff --git a/src-java/rule-manager/rule-manager-api/src/main/java/org/openkilda/rulemanager/ProtoConstants.java b/src-java/rule-manager/rule-manager-api/src/main/java/org/openkilda/rulemanager/ProtoConstants.java index 24276a3d750..4708f1042fa 100644 --- a/src-java/rule-manager/rule-manager-api/src/main/java/org/openkilda/rulemanager/ProtoConstants.java +++ b/src-java/rule-manager/rule-manager-api/src/main/java/org/openkilda/rulemanager/ProtoConstants.java @@ -30,6 +30,7 @@ public static final class EthType { public static final long IPv4 = 0x0800; public static final long LLDP = 0x88CC; public static final long ARP = 0x0806; + public static final long SLOW_PROTOCOLS = 0x8809; } public static final class IpProto { diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/Constants.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/Constants.java index 7b0af324af9..5d8acf60881 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/Constants.java +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/Constants.java @@ -82,6 +82,8 @@ public static final class Priority { public static final int DEFAULT_FLOW_PRIORITY = FLOW_PRIORITY - 1; public static final int DEFAULT_FLOW_VLAN_STATS_PRIORITY = FLOW_PRIORITY - 10; public static final int DOUBLE_VLAN_FLOW_PRIORITY = FLOW_PRIORITY + 10; + public static final int LACP_RULE_PRIORITY = INGRESS_CUSTOMER_PORT_RULE_PRIORITY_MULTITABLE + 200; + public static final int DROP_LOOP_SLOW_PROTOCOLS_PRIORITY = LACP_RULE_PRIORITY + 10; public static final int MIRROR_FLOW_PRIORITY = FLOW_PRIORITY + 50; public static final int MIRROR_DOUBLE_VLAN_FLOW_PRIORITY = MIRROR_FLOW_PRIORITY + 10; diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/DataAdapter.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/DataAdapter.java index 24fa3efef04..35dd02a427e 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/DataAdapter.java +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/DataAdapter.java @@ -19,12 +19,14 @@ import org.openkilda.model.FlowPath; import org.openkilda.model.FlowTransitEncapsulation; import org.openkilda.model.KildaFeatureToggles; +import org.openkilda.model.LagLogicalPort; import org.openkilda.model.PathId; import org.openkilda.model.Switch; import org.openkilda.model.SwitchId; import org.openkilda.model.SwitchProperties; import org.openkilda.model.YFlow; +import java.util.List; import java.util.Map; import java.util.Set; @@ -44,5 +46,7 @@ public interface DataAdapter { Set getSwitchIslPorts(SwitchId switchId); + List getLagLogicalPorts(SwitchId switchId); + YFlow getYFlow(PathId pathId); } diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManager.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManager.java index 66e5ba7e0e3..7d7ee0b9a63 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManager.java +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManager.java @@ -57,4 +57,6 @@ List buildRulesForFlowPath(FlowPath flowPath, boolean filterOutUsed * Build all required service rules for ISL on specified port. */ List buildIslServiceRules(SwitchId switchId, int port, DataAdapter adapter); + + List buildLacpRules(SwitchId switchId, int logicalPort, DataAdapter adapter); } diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManagerConfig.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManagerConfig.java index be54838d4d2..f735fdc3f22 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManagerConfig.java +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManagerConfig.java @@ -46,6 +46,10 @@ public interface RuleManagerConfig extends Serializable { @Default("1") int getArpRateLimit(); // rate in packets per second + @Key("lacp-rate-limit") + @Default("100") + int getLacpRateLimit(); // rate in packets per second + // Size of packets used for discovery. Used to calculate service meters rate in KBPS @Key("disco-packet-size") @Default("250") @@ -59,6 +63,10 @@ public interface RuleManagerConfig extends Serializable { @Default("100") int getArpPacketSize(); // rate in bytes + @Key("lacp-packet-size") + @Default("150") + int getLacpPacketSize(); // rate in bytes + @Key("flow-meter-burst-coefficient") @Default("1.05") @Description("This coefficient is used to calculate burst size for flow meters. " @@ -92,6 +100,12 @@ public interface RuleManagerConfig extends Serializable { @Description("This is burst size for ARP rule meters in packets.") long getArpMeterBurstSizeInPackets(); + @Key("lacp-meter-burst-size-in-packets") + @Default("4096") + @Min(0) + @Description("This is burst size for LACP rule meters in packets.") + long getLacpMeterBurstSizeInPackets(); + @Key("flow-ping-magic-src-mac-address") @Default("00:26:E1:FF:FF:FE") String getFlowPingMagicSrcMacAddress(); diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManagerImpl.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManagerImpl.java index 62426b61e1d..a1b7dceb8fe 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManagerImpl.java +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RuleManagerImpl.java @@ -35,6 +35,7 @@ import org.openkilda.model.FlowPath; import org.openkilda.model.FlowTransitEncapsulation; import org.openkilda.model.KildaFeatureToggles; +import org.openkilda.model.LagLogicalPort; import org.openkilda.model.MacAddress; import org.openkilda.model.MeterId; import org.openkilda.model.PathId; @@ -52,6 +53,7 @@ import org.openkilda.rulemanager.utils.Utils; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; import lombok.AllArgsConstructor; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -200,6 +202,22 @@ List getServiceRuleGenerators(SwitchId switchId, DataAdapter adap islPorts.forEach(islPort -> generators.addAll(getIslServiceRuleGenerators(islPort))); } + List lacpPorts = adapter.getLagLogicalPorts(switchId) + .stream().filter(LagLogicalPort::isLacpReply).collect(toList()); + + if (!lacpPorts.isEmpty()) { + generators.add(serviceRulesFactory.getDropSlowProtocolsLoopRuleGenerator()); + + // we need to create only one meter for all Lacp reply rules. So first generator will receive parameter + // switchHasOtherLacpPackets = false + generators.add(serviceRulesFactory.getLacpReplyRuleGenerator( + lacpPorts.get(0).getLogicalPortNumber(), false)); + for (int i = 1; i < lacpPorts.size(); i++) { + generators.add(serviceRulesFactory.getLacpReplyRuleGenerator( + lacpPorts.get(i).getLogicalPortNumber(), true)); + } + } + Integer server42Port = switchProperties.getServer42Port(); Integer server42Vlan = switchProperties.getServer42Vlan(); MacAddress server42MacAddress = switchProperties.getServer42MacAddress(); @@ -436,9 +454,23 @@ public List buildIslServiceRules(SwitchId switchId, int port, DataA generators.add(serviceRulesFactory .getServer42IslRttInputRuleGenerator(switchProperties.getServer42Port(), port)); } - return generators.stream() - .flatMap(g -> g.generateCommands(sw).stream()) - .collect(toList()); + return generateRules(sw, generators); + } + + @Override + public List buildLacpRules(SwitchId switchId, int logicalPort, DataAdapter adapter) { + boolean switchHasOtherLacpPorts = adapter.getLagLogicalPorts(switchId).stream() + .filter(LagLogicalPort::isLacpReply) + .anyMatch(port -> port.getLogicalPortNumber() != logicalPort); + + List generators = Lists.newArrayList( + serviceRulesFactory.getLacpReplyRuleGenerator(logicalPort, switchHasOtherLacpPorts)); + if (!switchHasOtherLacpPorts) { + generators.add(serviceRulesFactory.getDropSlowProtocolsLoopRuleGenerator()); + } + + Switch sw = adapter.getSwitch(switchId); + return postProcessCommands(generateRules(sw, generators)); } private List buildSharedEndpointYFlowCommands(List flowPaths, DataAdapter adapter) { diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/adapter/InMemoryDataAdapter.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/adapter/InMemoryDataAdapter.java index 49b45260ce0..ba2c9b91021 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/adapter/InMemoryDataAdapter.java +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/adapter/InMemoryDataAdapter.java @@ -21,6 +21,7 @@ import org.openkilda.model.FlowPath; import org.openkilda.model.FlowTransitEncapsulation; import org.openkilda.model.KildaFeatureToggles; +import org.openkilda.model.LagLogicalPort; import org.openkilda.model.PathId; import org.openkilda.model.Switch; import org.openkilda.model.SwitchId; @@ -31,6 +32,8 @@ import lombok.Builder; import lombok.Value; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; @@ -44,6 +47,7 @@ public class InMemoryDataAdapter implements DataAdapter { Map switches; Map switchProperties; Map> switchIslPorts; + Map> switchLagPorts; KildaFeatureToggles featureToggles; Map yFlows; @@ -103,4 +107,13 @@ public Set getSwitchIslPorts(SwitchId switchId) { } return result; } + + @Override + public List getLagLogicalPorts(SwitchId switchId) { + List result = switchLagPorts.getOrDefault(switchId, Collections.emptyList()); + if (result == null) { + throw new IllegalStateException(format("Switch lag ports for '%s' not found.", switchId)); + } + return result; + } } diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/adapter/PersistenceDataAdapter.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/adapter/PersistenceDataAdapter.java index 30d39b4aed4..378dc4766e0 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/adapter/PersistenceDataAdapter.java +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/adapter/PersistenceDataAdapter.java @@ -20,6 +20,7 @@ import org.openkilda.model.FlowPath; import org.openkilda.model.FlowTransitEncapsulation; import org.openkilda.model.KildaFeatureToggles; +import org.openkilda.model.LagLogicalPort; import org.openkilda.model.PathId; import org.openkilda.model.Switch; import org.openkilda.model.SwitchFeature; @@ -32,6 +33,7 @@ import org.openkilda.persistence.repositories.FlowRepository; import org.openkilda.persistence.repositories.IslRepository; import org.openkilda.persistence.repositories.KildaFeatureTogglesRepository; +import org.openkilda.persistence.repositories.LagLogicalPortRepository; import org.openkilda.persistence.repositories.RepositoryFactory; import org.openkilda.persistence.repositories.SwitchPropertiesRepository; import org.openkilda.persistence.repositories.SwitchRepository; @@ -43,6 +45,7 @@ import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -60,6 +63,7 @@ public class PersistenceDataAdapter implements DataAdapter { private final TransitVlanRepository transitVlanRepository; private final VxlanRepository vxlanRepository; private final IslRepository islRepository; + private final LagLogicalPortRepository lagLogicalPortRepository; private final KildaFeatureTogglesRepository featureTogglesRepository; private final Set pathIds; @@ -71,6 +75,7 @@ public class PersistenceDataAdapter implements DataAdapter { private Map switchCache; private Map switchPropertiesCache; private Map> switchIslPortsCache; + private Map> switchLagPortsCache; private KildaFeatureToggles featureToggles; private Map yFlowCache; @@ -88,6 +93,7 @@ public PersistenceDataAdapter(PersistenceManager persistenceManager, transitVlanRepository = repositoryFactory.createTransitVlanRepository(); vxlanRepository = repositoryFactory.createVxlanRepository(); islRepository = repositoryFactory.createIslRepository(); + lagLogicalPortRepository = repositoryFactory.createLagLogicalPortRepository(); featureTogglesRepository = repositoryFactory.createFeatureTogglesRepository(); this.pathIds = pathIds; @@ -178,6 +184,14 @@ public Set getSwitchIslPorts(SwitchId switchId) { return switchIslPortsCache.getOrDefault(switchId, Collections.emptySet()); } + @Override + public List getLagLogicalPorts(SwitchId switchId) { + if (switchLagPortsCache == null) { + switchLagPortsCache = lagLogicalPortRepository.findBySwitchIds(switchIds); + } + return switchLagPortsCache.getOrDefault(switchId, Collections.emptyList()); + } + @Override public YFlow getYFlow(PathId pathId) { if (yFlowCache == null) { diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/ServiceRulesGeneratorFactory.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/ServiceRulesGeneratorFactory.java index dd1cccb60b1..7152d2a0d45 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/ServiceRulesGeneratorFactory.java +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/ServiceRulesGeneratorFactory.java @@ -35,6 +35,8 @@ import org.openkilda.rulemanager.factory.generator.service.isl.EgressIslVlanRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.isl.EgressIslVxlanRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.isl.TransitIslVxlanRuleGenerator; +import org.openkilda.rulemanager.factory.generator.service.lacp.DropSlowProtocolsLoopRuleGenerator; +import org.openkilda.rulemanager.factory.generator.service.lacp.LacpReplyRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.lldp.LldpIngressRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.lldp.LldpInputPreDropRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.lldp.LldpPostIngressOneSwitchRuleGenerator; @@ -337,4 +339,22 @@ public TransitIslVxlanRuleGenerator getTransitIslVxlanRuleGenerator(int islPort) .islPort(islPort) .build(); } + + /** + * Get LACP reply rule generator. + */ + public LacpReplyRuleGenerator getLacpReplyRuleGenerator(int inPort, boolean switchHasOtherLacpPorts) { + return LacpReplyRuleGenerator.builder() + .config(config) + .inPort(inPort) + .switchHasOtherLacpPorts(switchHasOtherLacpPorts) + .build(); + } + + /** + * Get drop slow protocol loop rule generator. + */ + public DropSlowProtocolsLoopRuleGenerator getDropSlowProtocolsLoopRuleGenerator() { + return DropSlowProtocolsLoopRuleGenerator.builder().build(); + } } diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/generator/service/lacp/DropSlowProtocolsLoopRuleGenerator.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/generator/service/lacp/DropSlowProtocolsLoopRuleGenerator.java new file mode 100644 index 00000000000..ab0ad105e8f --- /dev/null +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/generator/service/lacp/DropSlowProtocolsLoopRuleGenerator.java @@ -0,0 +1,67 @@ +/* Copyright 2021 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.rulemanager.factory.generator.service.lacp; + +import static org.openkilda.rulemanager.Constants.Priority.DROP_LOOP_SLOW_PROTOCOLS_PRIORITY; +import static org.openkilda.rulemanager.OfVersion.OF_12; + +import org.openkilda.model.MacAddress; +import org.openkilda.model.Switch; +import org.openkilda.model.cookie.ServiceCookie; +import org.openkilda.model.cookie.ServiceCookie.ServiceCookieTag; +import org.openkilda.rulemanager.Field; +import org.openkilda.rulemanager.FlowSpeakerData; +import org.openkilda.rulemanager.Instructions; +import org.openkilda.rulemanager.OfTable; +import org.openkilda.rulemanager.OfVersion; +import org.openkilda.rulemanager.ProtoConstants.EthType; +import org.openkilda.rulemanager.SpeakerData; +import org.openkilda.rulemanager.factory.RuleGenerator; +import org.openkilda.rulemanager.match.FieldMatch; + +import com.google.common.collect.Sets; +import lombok.Builder; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +@Builder +public class DropSlowProtocolsLoopRuleGenerator implements RuleGenerator { + + @Override + public List generateCommands(Switch sw) { + OfVersion ofVersion = OfVersion.of(sw.getOfVersion()); + if (ofVersion == OF_12) { + return Collections.emptyList(); + } + + Set match = Sets.newHashSet( + FieldMatch.builder().field(Field.ETH_TYPE).value(EthType.SLOW_PROTOCOLS).build(), + FieldMatch.builder().field(Field.ETH_SRC).value(sw.getSwitchId().toLong()).build(), + FieldMatch.builder().field(Field.ETH_DST).value(MacAddress.SLOW_PROTOCOLS.toLong()).build()); + + return Collections.singletonList(FlowSpeakerData.builder() + .switchId(sw.getSwitchId()) + .ofVersion(ofVersion) + .cookie(new ServiceCookie(ServiceCookieTag.DROP_SLOW_PROTOCOLS_LOOP_COOKIE)) + .table(OfTable.INPUT) + .priority(DROP_LOOP_SLOW_PROTOCOLS_PRIORITY) + .match(match) + .instructions(Instructions.builder().build()) + .build()); + } +} diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/generator/service/lacp/LacpReplyRuleGenerator.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/generator/service/lacp/LacpReplyRuleGenerator.java new file mode 100644 index 00000000000..78d681a48cf --- /dev/null +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/factory/generator/service/lacp/LacpReplyRuleGenerator.java @@ -0,0 +1,114 @@ +/* Copyright 2021 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.rulemanager.factory.generator.service.lacp; + +import static org.openkilda.model.MeterId.LACP_REPLY_METER_ID; +import static org.openkilda.rulemanager.Constants.Priority.LACP_RULE_PRIORITY; +import static org.openkilda.rulemanager.OfTable.INPUT; + +import org.openkilda.model.MacAddress; +import org.openkilda.model.MeterId; +import org.openkilda.model.Switch; +import org.openkilda.model.cookie.CookieBase.CookieType; +import org.openkilda.model.cookie.PortColourCookie; +import org.openkilda.rulemanager.Field; +import org.openkilda.rulemanager.FlowSpeakerData; +import org.openkilda.rulemanager.Instructions; +import org.openkilda.rulemanager.OfVersion; +import org.openkilda.rulemanager.ProtoConstants.EthType; +import org.openkilda.rulemanager.ProtoConstants.PortNumber; +import org.openkilda.rulemanager.ProtoConstants.PortNumber.SpecialPortType; +import org.openkilda.rulemanager.RuleManagerConfig; +import org.openkilda.rulemanager.SpeakerData; +import org.openkilda.rulemanager.action.PortOutAction; +import org.openkilda.rulemanager.factory.generator.service.MeteredServiceRuleGenerator; +import org.openkilda.rulemanager.match.FieldMatch; + +import com.google.common.collect.Sets; +import lombok.Builder; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class LacpReplyRuleGenerator extends MeteredServiceRuleGenerator { + + private final int inPort; + private final boolean switchHasOtherLacpPorts; + + @Builder + public LacpReplyRuleGenerator(RuleManagerConfig config, int inPort, boolean switchHasOtherLacpPorts) { + super(config); + this.inPort = inPort; + this.switchHasOtherLacpPorts = switchHasOtherLacpPorts; + } + + @Override + public List generateCommands(Switch sw) { + List commands = new ArrayList<>(); + Instructions instructions = Instructions.builder() + .applyActions(new ArrayList<>()) + .build(); + FlowSpeakerData flowCommand = buildRule(sw, instructions); + if (flowCommand == null) { + return Collections.emptyList(); + } else { + commands.add(flowCommand); + } + + MeterId meterId = LACP_REPLY_METER_ID; + SpeakerData meterCommand = generateMeterCommandForServiceRule(sw, meterId, config.getLacpRateLimit(), + config.getLacpMeterBurstSizeInPackets(), config.getLacpPacketSize()); + if (meterCommand != null) { + if (!switchHasOtherLacpPorts) { // meter was already created by other rules + commands.add(meterCommand); + } + addMeterToInstructions(meterId, sw, instructions); + } + instructions.getApplyActions().add(new PortOutAction(new PortNumber(SpecialPortType.CONTROLLER))); + if (meterCommand != null && !switchHasOtherLacpPorts) { + flowCommand.getDependsOn().add(meterCommand.getUuid()); + } + + return commands; + } + + private FlowSpeakerData buildRule(Switch sw, Instructions instructions) { + OfVersion ofVersion = OfVersion.of(sw.getOfVersion()); + if (ofVersion == OfVersion.OF_12) { + return null; + } + + return FlowSpeakerData.builder() + .switchId(sw.getSwitchId()) + .ofVersion(ofVersion) + .cookie(new PortColourCookie(CookieType.LACP_REPLY_INPUT, inPort)) + .table(INPUT) + .priority(LACP_RULE_PRIORITY) + .match(buildMatch()) + .instructions(instructions) + .dependsOn(new ArrayList<>()) + .build(); + } + + private Set buildMatch() { + return Sets.newHashSet( + FieldMatch.builder().field(Field.IN_PORT).value(inPort).build(), + FieldMatch.builder().field(Field.ETH_TYPE).value(EthType.SLOW_PROTOCOLS).build(), + FieldMatch.builder().field(Field.ETH_DST).value(MacAddress.SLOW_PROTOCOLS.toLong()).build()); + } +} diff --git a/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/RuleManagerServiceRulesTest.java b/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/RuleManagerServiceRulesTest.java index e7e21963566..cc512b4659a 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/RuleManagerServiceRulesTest.java +++ b/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/RuleManagerServiceRulesTest.java @@ -19,10 +19,12 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.openkilda.rulemanager.Utils.LAG_PORTS; import static org.openkilda.rulemanager.Utils.buildSwitch; import static org.openkilda.rulemanager.Utils.buildSwitchProperties; import org.openkilda.model.KildaFeatureToggles; +import org.openkilda.model.LagLogicalPort; import org.openkilda.model.Switch; import org.openkilda.model.SwitchId; import org.openkilda.model.SwitchProperties; @@ -42,6 +44,8 @@ import org.openkilda.rulemanager.factory.generator.service.arp.ArpPostIngressRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.arp.ArpPostIngressVxlanRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.arp.ArpTransitRuleGenerator; +import org.openkilda.rulemanager.factory.generator.service.lacp.DropSlowProtocolsLoopRuleGenerator; +import org.openkilda.rulemanager.factory.generator.service.lacp.LacpReplyRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.lldp.LldpIngressRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.lldp.LldpInputPreDropRuleGenerator; import org.openkilda.rulemanager.factory.generator.service.lldp.LldpPostIngressOneSwitchRuleGenerator; @@ -92,9 +96,9 @@ public void shouldUseCorrectServiceRuleGeneratorsForSwitchInSingleTableMode() { SwitchProperties switchProperties = buildSwitchProperties(sw, false); List generators = ruleManager.getServiceRuleGenerators( - switchId, buildAdapter(switchId, switchProperties, new HashSet<>(), false)); + switchId, buildAdapter(switchId, switchProperties, new HashSet<>(), false, LAG_PORTS)); - assertEquals(7, generators.size()); + assertEquals(10, generators.size()); assertTrue(generators.stream().anyMatch(g -> g instanceof TableDefaultRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof BroadCastDiscoveryRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof UniCastDiscoveryRuleGenerator)); @@ -102,6 +106,8 @@ public void shouldUseCorrectServiceRuleGeneratorsForSwitchInSingleTableMode() { assertTrue(generators.stream().anyMatch(g -> g instanceof BfdCatchRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof RoundTripLatencyRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof UnicastVerificationVxlanRuleGenerator)); + assertTrue(generators.stream().anyMatch(g -> g instanceof DropSlowProtocolsLoopRuleGenerator)); + assertEquals(2, generators.stream().filter(g -> g instanceof LacpReplyRuleGenerator).count()); } @Test @@ -111,9 +117,9 @@ public void shouldUseCorrectServiceRuleGeneratorsForSwitchInMultiTableMode() { SwitchProperties switchProperties = buildSwitchProperties(sw, true); List generators = ruleManager.getServiceRuleGenerators( - switchId, buildAdapter(switchId, switchProperties, new HashSet<>(), false)); + switchId, buildAdapter(switchId, switchProperties, new HashSet<>(), false, LAG_PORTS)); - assertEquals(18, generators.size()); + assertEquals(21, generators.size()); assertTrue(generators.stream().anyMatch(g -> g instanceof BroadCastDiscoveryRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof UniCastDiscoveryRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof DropDiscoveryLoopRuleGenerator)); @@ -123,6 +129,8 @@ public void shouldUseCorrectServiceRuleGeneratorsForSwitchInMultiTableMode() { assertEquals(4, generators.stream().filter(g -> g instanceof TableDefaultRuleGenerator).count()); assertEquals(2, generators.stream().filter(g -> g instanceof TablePassThroughDefaultRuleGenerator).count()); + assertEquals(1, generators.stream().filter(g -> g instanceof DropSlowProtocolsLoopRuleGenerator).count()); + assertEquals(2, generators.stream().filter(g -> g instanceof LacpReplyRuleGenerator).count()); assertTrue(generators.stream().anyMatch(g -> g instanceof LldpPostIngressRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof LldpPostIngressVxlanRuleGenerator)); @@ -139,7 +147,7 @@ public void shouldUseCorrectServiceRuleGeneratorsForSwitchInMultiTableModeWithSw SwitchProperties switchProperties = buildSwitchProperties(sw, true, true, true); List generators = ruleManager.getServiceRuleGenerators( - switchId, buildAdapter(switchId, switchProperties, new HashSet<>(), false)); + switchId, buildAdapter(switchId, switchProperties, new HashSet<>(), false, null)); assertEquals(24, generators.size()); assertTrue(generators.stream().anyMatch(g -> g instanceof BroadCastDiscoveryRuleGenerator)); @@ -171,15 +179,18 @@ public void shouldUseCorrectServiceRuleGeneratorsForSwitchInMultiTableModeWithAl SwitchProperties switchProperties = buildSwitchProperties(sw, true, true, true, true, RttState.ENABLED); List generators = ruleManager.getServiceRuleGenerators( - switchId, buildAdapter(switchId, switchProperties, Sets.newHashSet(ISL_PORT), true)); + switchId, buildAdapter(switchId, switchProperties, Sets.newHashSet(ISL_PORT), true, LAG_PORTS)); - assertEquals(34, generators.size()); + assertEquals(37, generators.size()); assertTrue(generators.stream().anyMatch(g -> g instanceof BroadCastDiscoveryRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof UniCastDiscoveryRuleGenerator)); assertEquals(4, generators.stream().filter(g -> g instanceof TableDefaultRuleGenerator).count()); assertEquals(2, generators.stream().filter(g -> g instanceof TablePassThroughDefaultRuleGenerator).count()); + assertEquals(4, generators.stream().filter(g -> g instanceof TableDefaultRuleGenerator).count()); + assertEquals(2, generators.stream().filter(g -> g instanceof TablePassThroughDefaultRuleGenerator).count()); + assertTrue(generators.stream().anyMatch(g -> g instanceof LldpPostIngressRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof LldpPostIngressVxlanRuleGenerator)); assertTrue(generators.stream().anyMatch(g -> g instanceof LldpPostIngressOneSwitchRuleGenerator)); @@ -206,11 +217,16 @@ public void shouldUseCorrectServiceRuleGeneratorsForSwitchInMultiTableModeWithAl } private DataAdapter buildAdapter( - SwitchId switchId, SwitchProperties switchProperties, Set islPorts, boolean server42) { + SwitchId switchId, SwitchProperties switchProperties, Set islPorts, boolean server42, + List lagLogicalPorts) { Map switchPropertiesMap = new HashMap<>(); switchPropertiesMap.put(switchId, switchProperties); Map> islMap = new HashMap<>(); islMap.putIfAbsent(switchId, islPorts); + Map> lagMap = new HashMap<>(); + if (lagLogicalPorts != null) { + lagMap.put(switchId, lagLogicalPorts); + } return InMemoryDataAdapter.builder() .switchProperties(switchPropertiesMap) .switchIslPorts(islMap) @@ -218,6 +234,7 @@ private DataAdapter buildAdapter( .server42FlowRtt(server42) .server42IslRtt(server42) .build()) + .switchLagPorts(lagMap) .build(); } } diff --git a/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/Utils.java b/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/Utils.java index 01dddc82518..1aabe8617ed 100644 --- a/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/Utils.java +++ b/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/Utils.java @@ -18,6 +18,7 @@ import static java.lang.String.format; import static java.util.stream.Collectors.toSet; +import org.openkilda.model.LagLogicalPort; import org.openkilda.model.MacAddress; import org.openkilda.model.Switch; import org.openkilda.model.SwitchFeature; @@ -28,9 +29,11 @@ import org.openkilda.rulemanager.match.FieldMatch; import org.openkilda.rulemanager.utils.RoutingMetadata; +import com.google.common.collect.Lists; import lombok.Value; import org.junit.Assert; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -42,6 +45,23 @@ public final class Utils { public static final int SERVER_42_PORT = 42; public static final int SERVER_42_VLAN = 142; public static final MacAddress SERVER_42_MAC_ADDRESS = new MacAddress("42:42:42:42:42:42"); + public static final SwitchId SWITCH_ID = new SwitchId(1L); + public static final int LAG_PORT_NUMBER_1 = 1; + public static final int LAG_PORT_NUMBER_2 = 2; + public static final int LAG_PORT_NUMBER_3 = 3; + public static final int PHYS_PORT_1 = 3; + public static final int PHYS_PORT_2 = 3; + public static final int PHYS_PORT_3 = 3; + public static final int PHYS_PORT_4 = 3; + + public static final LagLogicalPort LAG_PORT_1 = new LagLogicalPort(SWITCH_ID, LAG_PORT_NUMBER_1, + Lists.newArrayList(PHYS_PORT_1, PHYS_PORT_2), true); + public static final LagLogicalPort LAG_PORT_2 = new LagLogicalPort(SWITCH_ID, LAG_PORT_NUMBER_2, + new ArrayList(), true); + public static final LagLogicalPort LAG_PORT_3 = new LagLogicalPort(SWITCH_ID, LAG_PORT_NUMBER_3, + Lists.newArrayList(PHYS_PORT_3, PHYS_PORT_4), false); + public static final List LAG_PORTS = Lists.newArrayList(LAG_PORT_1, LAG_PORT_2, LAG_PORT_3); + /** * Build switch object for tests. @@ -57,7 +77,7 @@ public static Switch buildSwitch(SwitchId switchId, String version, Set features) { - return buildSwitch(new SwitchId(1L), version, features); + return buildSwitch(SWITCH_ID, version, features); } public static Switch buildSwitch(SwitchId switchId, Set features) { diff --git a/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/factory/generator/service/lacp/DropSlowProtocolsLoopRuleGeneratorTest.java b/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/factory/generator/service/lacp/DropSlowProtocolsLoopRuleGeneratorTest.java new file mode 100644 index 00000000000..8daa7bfb86c --- /dev/null +++ b/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/factory/generator/service/lacp/DropSlowProtocolsLoopRuleGeneratorTest.java @@ -0,0 +1,76 @@ +/* Copyright 2022 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.rulemanager.factory.generator.service.lacp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.openkilda.rulemanager.Constants.Priority.DROP_LOOP_SLOW_PROTOCOLS_PRIORITY; +import static org.openkilda.rulemanager.Utils.assertEqualsMatch; +import static org.openkilda.rulemanager.Utils.buildSwitch; +import static org.openkilda.rulemanager.Utils.getCommand; + +import org.openkilda.model.MacAddress; +import org.openkilda.model.Switch; +import org.openkilda.model.cookie.ServiceCookie; +import org.openkilda.model.cookie.ServiceCookie.ServiceCookieTag; +import org.openkilda.rulemanager.Field; +import org.openkilda.rulemanager.FlowSpeakerData; +import org.openkilda.rulemanager.Instructions; +import org.openkilda.rulemanager.OfTable; +import org.openkilda.rulemanager.ProtoConstants.EthType; +import org.openkilda.rulemanager.SpeakerData; +import org.openkilda.rulemanager.match.FieldMatch; + +import com.google.common.collect.Sets; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class DropSlowProtocolsLoopRuleGeneratorTest { + private DropSlowProtocolsLoopRuleGenerator generator; + + @Before + public void setup() { + generator = DropSlowProtocolsLoopRuleGenerator.builder().build(); + } + + @Test + public void shouldBuildCorrectRule() { + Switch sw = buildSwitch("OF_13", Collections.emptySet()); + List commands = generator.generateCommands(sw); + + assertEquals(1, commands.size()); + + FlowSpeakerData flowCommandData = getCommand(FlowSpeakerData.class, commands); + assertEquals(sw.getSwitchId(), flowCommandData.getSwitchId()); + assertEquals(sw.getOfVersion(), flowCommandData.getOfVersion().toString()); + assertTrue(flowCommandData.getDependsOn().isEmpty()); + + assertEquals(new ServiceCookie(ServiceCookieTag.DROP_SLOW_PROTOCOLS_LOOP_COOKIE), flowCommandData.getCookie()); + assertEquals(OfTable.INPUT, flowCommandData.getTable()); + assertEquals(DROP_LOOP_SLOW_PROTOCOLS_PRIORITY, flowCommandData.getPriority()); + assertEquals(Instructions.builder().build(), flowCommandData.getInstructions()); + + Set expectedMatch = Sets.newHashSet( + FieldMatch.builder().field(Field.ETH_TYPE).value(EthType.SLOW_PROTOCOLS).build(), + FieldMatch.builder().field(Field.ETH_SRC).value(sw.getSwitchId().toLong()).build(), + FieldMatch.builder().field(Field.ETH_DST).value(MacAddress.SLOW_PROTOCOLS.toLong()).build()); + assertEqualsMatch(expectedMatch, flowCommandData.getMatch()); + } +} diff --git a/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/factory/generator/service/lacp/LacpReplyRuleGeneratorTest.java b/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/factory/generator/service/lacp/LacpReplyRuleGeneratorTest.java new file mode 100644 index 00000000000..7a2d9aa0e61 --- /dev/null +++ b/src-java/rule-manager/rule-manager-implementation/src/test/java/org/openkilda/rulemanager/factory/generator/service/lacp/LacpReplyRuleGeneratorTest.java @@ -0,0 +1,134 @@ +/* Copyright 2022 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.rulemanager.factory.generator.service.lacp; + +import static com.google.common.collect.Lists.newArrayList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.openkilda.model.SwitchFeature.METERS; +import static org.openkilda.model.SwitchFeature.PKTPS_FLAG; +import static org.openkilda.rulemanager.Constants.Priority.LACP_RULE_PRIORITY; +import static org.openkilda.rulemanager.Utils.assertEqualsMatch; +import static org.openkilda.rulemanager.Utils.buildSwitch; +import static org.openkilda.rulemanager.Utils.getCommand; + +import org.openkilda.model.MacAddress; +import org.openkilda.model.MeterId; +import org.openkilda.model.Switch; +import org.openkilda.model.cookie.CookieBase.CookieType; +import org.openkilda.model.cookie.PortColourCookie; +import org.openkilda.rulemanager.Field; +import org.openkilda.rulemanager.FlowSpeakerData; +import org.openkilda.rulemanager.Instructions; +import org.openkilda.rulemanager.MeterFlag; +import org.openkilda.rulemanager.MeterSpeakerData; +import org.openkilda.rulemanager.OfTable; +import org.openkilda.rulemanager.ProtoConstants.EthType; +import org.openkilda.rulemanager.ProtoConstants.PortNumber; +import org.openkilda.rulemanager.ProtoConstants.PortNumber.SpecialPortType; +import org.openkilda.rulemanager.RuleManagerConfig; +import org.openkilda.rulemanager.SpeakerData; +import org.openkilda.rulemanager.action.PortOutAction; +import org.openkilda.rulemanager.match.FieldMatch; + +import com.google.common.collect.Sets; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +public class LacpReplyRuleGeneratorTest { + public static final int LOGICAL_PORT = 1234; + private static RuleManagerConfig config; + + @BeforeClass + public static void init() { + config = mock(RuleManagerConfig.class); + when(config.getLacpPacketSize()).thenReturn(150); + when(config.getLacpRateLimit()).thenReturn(100); + when(config.getLacpMeterBurstSizeInPackets()).thenReturn(1000L); + } + + @Test + public void shouldBuildCorrectRuleWithoutOtherLacpPorts() { + LacpReplyRuleGenerator generator = buildGenerator(false); + Switch sw = buildSwitch("OF_13", Sets.newHashSet(METERS, PKTPS_FLAG)); + List commands = generator.generateCommands(sw); + + assertEquals(2, commands.size()); + + FlowSpeakerData flowCommandData = getCommand(FlowSpeakerData.class, commands); + MeterSpeakerData meterCommandData = getCommand(MeterSpeakerData.class, commands); + + assertLacpReplyFlow(sw, flowCommandData, newArrayList(meterCommandData.getUuid())); + + assertEquals(sw.getSwitchId(), meterCommandData.getSwitchId()); + assertEquals(MeterId.LACP_REPLY_METER_ID, meterCommandData.getMeterId()); + assertEquals(config.getLacpRateLimit(), meterCommandData.getRate()); + assertEquals(config.getLacpMeterBurstSizeInPackets(), meterCommandData.getBurst()); + assertEquals(3, meterCommandData.getFlags().size()); + assertTrue(Sets.newHashSet(MeterFlag.BURST, MeterFlag.STATS, MeterFlag.PKTPS) + .containsAll(meterCommandData.getFlags())); + assertTrue(meterCommandData.getDependsOn().isEmpty()); + } + + @Test + public void shouldBuildCorrectRuleWithOtherLacpPorts() { + LacpReplyRuleGenerator generator = buildGenerator(true); + Switch sw = buildSwitch("OF_13", Sets.newHashSet(METERS, PKTPS_FLAG)); + List commands = generator.generateCommands(sw); + + assertEquals(1, commands.size()); + + FlowSpeakerData flowCommandData = getCommand(FlowSpeakerData.class, commands); + assertLacpReplyFlow(sw, flowCommandData, newArrayList()); + } + + private static void assertLacpReplyFlow(Switch sw, FlowSpeakerData flowCommandData, List expectedDependsOn) { + assertEquals(sw.getSwitchId(), flowCommandData.getSwitchId()); + assertEquals(sw.getOfVersion(), flowCommandData.getOfVersion().toString()); + assertEquals(expectedDependsOn, new ArrayList<>(flowCommandData.getDependsOn())); + + assertEquals(new PortColourCookie(CookieType.LACP_REPLY_INPUT, LOGICAL_PORT), flowCommandData.getCookie()); + assertEquals(OfTable.INPUT, flowCommandData.getTable()); + assertEquals(LACP_RULE_PRIORITY, flowCommandData.getPriority()); + + Instructions expectedInstructions = Instructions.builder() + .goToMeter(MeterId.LACP_REPLY_METER_ID) + .applyActions(newArrayList(new PortOutAction(new PortNumber(SpecialPortType.CONTROLLER)))) + .build(); + assertEquals(expectedInstructions, flowCommandData.getInstructions()); + + Set expectedMatch = Sets.newHashSet( + FieldMatch.builder().field(Field.IN_PORT).value(LOGICAL_PORT).build(), + FieldMatch.builder().field(Field.ETH_TYPE).value(EthType.SLOW_PROTOCOLS).build(), + FieldMatch.builder().field(Field.ETH_DST).value(MacAddress.SLOW_PROTOCOLS.toLong()).build()); + assertEqualsMatch(expectedMatch, flowCommandData.getMatch()); + } + + private LacpReplyRuleGenerator buildGenerator(boolean hasOtherLacpPorts) { + return LacpReplyRuleGenerator.builder() + .inPort(LOGICAL_PORT) + .config(config) + .switchHasOtherLacpPorts(hasOtherLacpPorts) + .build(); + } +} diff --git a/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/MeterStatsHandler.java b/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/MeterStatsHandler.java index a3c69fbe787..c57c05f7f12 100644 --- a/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/MeterStatsHandler.java +++ b/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/MeterStatsHandler.java @@ -21,6 +21,7 @@ import org.openkilda.model.FlowPathDirection; import org.openkilda.model.MeterId; import org.openkilda.model.SwitchId; +import org.openkilda.model.cookie.CookieBase.CookieType; import org.openkilda.model.cookie.FlowSegmentCookie; import org.openkilda.model.cookie.ServiceCookie; import org.openkilda.wfm.share.utils.MetricFormatter; @@ -100,8 +101,12 @@ public void handleStatsEntry(StatVlanDescriptor descriptor) { @Override public void handleStatsEntry(DummyMeterDescriptor descriptor) { + //TODO(snikitin) Need to find some way find cookie by meterId TagsFormatter tags = initTags(); - if (isMeterIdOfDefaultRule(statsEntry.getMeterId())) { + if (statsEntry.getMeterId() == MeterId.LACP_REPLY_METER_ID.getValue()) { + tags.addCookieHexTag(getCookieTagForPortColorCookie(CookieType.LACP_REPLY_INPUT)); + emitServiceMeterPoints(tags); + } else if (isMeterIdOfDefaultRule(statsEntry.getMeterId())) { tags.addCookieHexTag(new ServiceCookie(new MeterId(statsEntry.getMeterId()))); emitServiceMeterPoints(tags); } else { @@ -157,4 +162,8 @@ private TagsFormatter initTags() { tags.addMeterIdTag(statsEntry.getMeterId()); return tags; } + + static String getCookieTagForPortColorCookie(CookieType cookieType) { + return String.format("0x8%02X00000XXXXXXXX", cookieType.getValue()); + } } diff --git a/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/TagsFormatter.java b/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/TagsFormatter.java index 43ad3113ade..1107417f07c 100644 --- a/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/TagsFormatter.java +++ b/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/TagsFormatter.java @@ -64,8 +64,12 @@ public void addCookieTag(Long cookie) { tags.put("cookie", cookie != null ? String.valueOf(cookie) : UNKNOWN_COOKIE_TAG_VALUE); } + public void addCookieHexTag(String cookieTag) { + tags.put("cookieHex", cookieTag); + } + public void addCookieHexTag(CookieBase cookie) { - tags.put("cookieHex", cookie != null ? String.valueOf(cookie) : UNKNOWN_COOKIE_TAG_VALUE); + addCookieHexTag(cookie != null ? String.valueOf(cookie) : UNKNOWN_COOKIE_TAG_VALUE); } public void addCookieTypeTag(CookieType type) { diff --git a/src-java/stats-topology/stats-storm-topology/src/test/java/org/openkilda/wfm/topology/stats/service/MeterStatsHandlerTest.java b/src-java/stats-topology/stats-storm-topology/src/test/java/org/openkilda/wfm/topology/stats/service/MeterStatsHandlerTest.java new file mode 100644 index 00000000000..5e598ebc458 --- /dev/null +++ b/src-java/stats-topology/stats-storm-topology/src/test/java/org/openkilda/wfm/topology/stats/service/MeterStatsHandlerTest.java @@ -0,0 +1,33 @@ +/* Copyright 2022 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.wfm.topology.stats.service; + +import static org.junit.Assert.assertEquals; + +import org.openkilda.model.cookie.CookieBase.CookieType; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class MeterStatsHandlerTest { + @Test + public void shouldRefreshCommonFlowsCookieCache() { + assertEquals("0x80E00000XXXXXXXX", MeterStatsHandler.getCookieTagForPortColorCookie( + CookieType.LACP_REPLY_INPUT)); + } +} diff --git a/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/request/CreateLagPortRequest.java b/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/request/CreateLagPortRequest.java index ba797f991ed..c23aaf7b931 100644 --- a/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/request/CreateLagPortRequest.java +++ b/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/request/CreateLagPortRequest.java @@ -28,5 +28,6 @@ public class CreateLagPortRequest extends CommandData { SwitchId switchId; Set portNumbers; + boolean lacpReply; } diff --git a/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/request/UpdateLagPortRequest.java b/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/request/UpdateLagPortRequest.java index b7ff5058100..d6ac4743c20 100644 --- a/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/request/UpdateLagPortRequest.java +++ b/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/request/UpdateLagPortRequest.java @@ -29,4 +29,5 @@ public class UpdateLagPortRequest extends CommandData { SwitchId switchId; int logicalPortNumber; Set targetPorts; + boolean lacpReply; } diff --git a/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/response/LagPortResponse.java b/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/response/LagPortResponse.java index 9779eed9e57..cbddad1bd12 100644 --- a/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/response/LagPortResponse.java +++ b/src-java/swmanager-topology/swmanager-messaging/src/main/java/org/openkilda/messaging/swmanager/response/LagPortResponse.java @@ -32,4 +32,5 @@ public class LagPortResponse extends InfoData { int logicalPortNumber; Set portNumbers; + boolean lacpReply; } diff --git a/src-java/swmanager-topology/swmanager-messaging/src/test/java/org/openkilda/messaging/swmanager/request/CreateLagPortRequestTest.java b/src-java/swmanager-topology/swmanager-messaging/src/test/java/org/openkilda/messaging/swmanager/request/CreateLagPortRequestTest.java index b98ba31f9bf..2db9ec8e649 100644 --- a/src-java/swmanager-topology/swmanager-messaging/src/test/java/org/openkilda/messaging/swmanager/request/CreateLagPortRequestTest.java +++ b/src-java/swmanager-topology/swmanager-messaging/src/test/java/org/openkilda/messaging/swmanager/request/CreateLagPortRequestTest.java @@ -39,6 +39,6 @@ public void serializeLoop() throws Exception { } public static CreateLagPortRequest makeRequest() { - return new CreateLagPortRequest(new SwitchId(1), Sets.newHashSet(1, 2, 3)); + return new CreateLagPortRequest(new SwitchId(1), Sets.newHashSet(1, 2, 3), true); } } diff --git a/src-java/swmanager-topology/swmanager-messaging/src/test/java/org/openkilda/messaging/swmanager/response/LagPortResponseTest.java b/src-java/swmanager-topology/swmanager-messaging/src/test/java/org/openkilda/messaging/swmanager/response/LagPortResponseTest.java index cf24ccce9c7..ce6e8e2f304 100644 --- a/src-java/swmanager-topology/swmanager-messaging/src/test/java/org/openkilda/messaging/swmanager/response/LagPortResponseTest.java +++ b/src-java/swmanager-topology/swmanager-messaging/src/test/java/org/openkilda/messaging/swmanager/response/LagPortResponseTest.java @@ -38,6 +38,6 @@ public void serializeLoop() throws Exception { } public static LagPortResponse makeResponse() { - return new LagPortResponse(1, Sets.newHashSet(1, 2, 3)); + return new LagPortResponse(1, Sets.newHashSet(1, 2, 3), false); } } diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/HeavyOperationBolt.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/HeavyOperationBolt.java index f38efcdf644..2187f6d818a 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/HeavyOperationBolt.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/HeavyOperationBolt.java @@ -74,6 +74,8 @@ private void handleBuildExpectedEntities(Tuple input) throws PipelineException { SwitchEntities expectedEntities = new SwitchEntities(validationService.buildExpectedEntities(switchId)); message = new InfoMessage(expectedEntities, getCommandContext().getCorrelationId(), messageCookie); } catch (Exception e) { + log.error(String.format("Enable to build expected switch entities for switch %s. Error: %s", + switchId, e.getMessage()), e); ErrorData errorData = new ErrorData( ErrorType.INTERNAL_ERROR, "Enable to build expected switch entities.", e.getMessage()); message = new ErrorMessage(errorData, getCommandContext().getCorrelationId(), messageCookie); diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/SwitchManagerHub.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/SwitchManagerHub.java index cea99ddd850..16c20f68eba 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/SwitchManagerHub.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/SwitchManagerHub.java @@ -140,8 +140,8 @@ public void init() { super.init(); LagPortOperationConfig config = new LagPortOperationConfig( - persistenceManager.getRepositoryFactory(), persistenceManager.getTransactionManager(), - topologyConfig.getBfdPortOffset(), topologyConfig.getBfdPortMaxNumber(), + persistenceManager, topologyConfig.getBfdPortOffset(), + topologyConfig.getBfdPortMaxNumber(), topologyConfig.getLagPortOffset(), topologyConfig.getLagPortMaxNumber(), topologyConfig.getLagPortPoolChunksCount(), topologyConfig.getLagPortPoolCacheSize()); log.info("LAG logical ports service config: {}", config); @@ -166,11 +166,14 @@ public void init() { serviceRegistry, "switch-rules", this, carrier -> new SwitchRuleService(carrier, persistenceManager, ruleManager)); createLagPortService = registerService( - serviceRegistry, "lag-create", this, carrier -> new CreateLagPortService(carrier, config)); + serviceRegistry, "lag-create", this, carrier -> new CreateLagPortService( + carrier, config, ruleManager)); updateLagPortService = registerService( - serviceRegistry, "lag-update", this, carrier -> new UpdateLagPortService(carrier, config)); + serviceRegistry, "lag-update", this, carrier -> new UpdateLagPortService( + carrier, config, ruleManager)); deleteLagPortService = registerService( - serviceRegistry, "lag-delete", this, carrier -> new DeleteLagPortService(carrier, config)); + serviceRegistry, "lag-delete", this, carrier -> new DeleteLagPortService( + carrier, config, ruleManager)); } @Override diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/CreateLagPortFsm.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/CreateLagPortFsm.java index b87d85c45b2..0e259d251cf 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/CreateLagPortFsm.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/CreateLagPortFsm.java @@ -20,18 +20,23 @@ import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagEvent.ERROR; import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagEvent.LAG_INSTALLED; import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagEvent.NEXT; +import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagEvent.SKIP_SPEAKER_COMMANDS_INSTALLATION; +import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagEvent.SPEAKER_ENTITIES_INSTALLED; import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagState.CREATE_LAG_IN_DB; import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagState.FINISHED; import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagState.FINISHED_WITH_ERROR; import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagState.GRPC_COMMAND_SEND; +import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagState.SPEAKER_COMMAND_SEND; import static org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagState.START; +import org.openkilda.floodlight.api.request.rulemanager.OfCommand; import org.openkilda.messaging.command.grpc.CreateOrUpdateLogicalPortRequest; import org.openkilda.messaging.info.InfoMessage; import org.openkilda.messaging.model.grpc.LogicalPort; import org.openkilda.messaging.swmanager.request.CreateLagPortRequest; import org.openkilda.messaging.swmanager.response.LagPortResponse; import org.openkilda.model.SwitchId; +import org.openkilda.wfm.topology.switchmanager.bolt.SwitchManagerHub.OfCommandAction; import org.openkilda.wfm.topology.switchmanager.error.InconsistentDataException; import org.openkilda.wfm.topology.switchmanager.error.InvalidDataException; import org.openkilda.wfm.topology.switchmanager.error.SwitchManagerException; @@ -52,6 +57,7 @@ import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine; import java.util.HashSet; +import java.util.List; @Slf4j public class CreateLagPortFsm extends AbstractStateMachine< @@ -97,9 +103,16 @@ public static StateMachineBuilder targetPorts = new HashSet<>(request.getPortNumbers()); - lagLogicalPortNumber = lagPortOperationService.createLagPort(switchId, targetPorts); + lagLogicalPortNumber = lagPortOperationService.createLagPort(switchId, targetPorts, request.isLacpReply()); String ipAddress = lagPortOperationService.getSwitchIpAddress(switchId); grpcRequest = new CreateOrUpdateLogicalPortRequest( @@ -133,13 +146,36 @@ void sendGrpcRequest(CreateLagState from, CreateLagState to, CreateLagEvent even carrier.sendCommandToSpeaker(key, grpcRequest); } + void sendSpeakerCommands(CreateLagState from, CreateLagState to, CreateLagEvent event, CreateLagContext context) { + if (!request.isLacpReply()) { + log.info("Skip sending OF commands to switch {} because LAG port {} doesn't require LACP. Key={}", + switchId, lagLogicalPortNumber, key); + fire(CreateLagEvent.SKIP_SPEAKER_COMMANDS_INSTALLATION); + return; + } + + log.info("Creating LACP commands for switch {} LAG port {}. Key={}", switchId, lagLogicalPortNumber, key); + List commands = lagPortOperationService + .buildLacpSpeakerCommands(switchId, lagLogicalPortNumber); + + log.info("Sending LACP commands {} to switch {} LAG port {}. Key={}", + commands, switchId, lagLogicalPortNumber, key); + carrier.sendOfCommandsToSpeaker(key, commands, OfCommandAction.INSTALL, switchId); + } + void lagInstalled(CreateLagState from, CreateLagState to, CreateLagEvent event, CreateLagContext context) { log.info("LAG {} successfully installed on switch {}. Key={}", context.createdLogicalPort, switchId, key); } + void speakerCommandsInstalled( + CreateLagState from, CreateLagState to, CreateLagEvent event, CreateLagContext context) { + log.info("Rules for LAG {} successfully installed on switch {}. Key={}", + context.createdLogicalPort, switchId, key); + } + void finishedEnter(CreateLagState from, CreateLagState to, CreateLagEvent event, CreateLagContext context) { LagPortResponse response = new LagPortResponse( - grpcRequest.getLogicalPortNumber(), grpcRequest.getPortNumbers()); + grpcRequest.getLogicalPortNumber(), grpcRequest.getPortNumbers(), request.isLacpReply()); InfoMessage message = new InfoMessage(response, System.currentTimeMillis(), key); carrier.response(key, message); } @@ -191,6 +227,7 @@ public enum CreateLagState { START, CREATE_LAG_IN_DB, GRPC_COMMAND_SEND, + SPEAKER_COMMAND_SEND, FINISHED_WITH_ERROR, FINISHED } @@ -198,6 +235,8 @@ public enum CreateLagState { public enum CreateLagEvent { NEXT, LAG_INSTALLED, + SKIP_SPEAKER_COMMANDS_INSTALLATION, + SPEAKER_ENTITIES_INSTALLED, ERROR } diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/DeleteLagPortFsm.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/DeleteLagPortFsm.java index cb82912eac2..77a68efca34 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/DeleteLagPortFsm.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/DeleteLagPortFsm.java @@ -19,13 +19,17 @@ import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagEvent.ERROR; import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagEvent.LAG_REMOVED; import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagEvent.NEXT; -import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState.CREATE_GRPC_COMMAND; +import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagEvent.SKIP_SPEAKER_ENTITIES_REMOVAL; +import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagEvent.SPEAKER_ENTITIES_REMOVED; import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState.FINISHED; import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState.FINISHED_WITH_ERROR; import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState.GRPC_COMMAND_SEND; import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState.REMOVE_LAG_FROM_DB; +import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState.SPEAKER_COMMAND_SEND; import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState.START; +import static org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState.VALIDATE_REMOVE_REQUEST; +import org.openkilda.floodlight.api.request.rulemanager.OfCommand; import org.openkilda.messaging.command.grpc.DeleteLogicalPortRequest; import org.openkilda.messaging.info.InfoMessage; import org.openkilda.messaging.swmanager.request.DeleteLagPortRequest; @@ -33,6 +37,7 @@ import org.openkilda.model.LagLogicalPort; import org.openkilda.model.PhysicalPort; import org.openkilda.model.SwitchId; +import org.openkilda.wfm.topology.switchmanager.bolt.SwitchManagerHub.OfCommandAction; import org.openkilda.wfm.topology.switchmanager.error.InconsistentDataException; import org.openkilda.wfm.topology.switchmanager.error.InvalidDataException; import org.openkilda.wfm.topology.switchmanager.error.LagPortNotFoundException; @@ -54,6 +59,7 @@ import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine; import java.util.Collections; +import java.util.List; import java.util.stream.Collectors; @Slf4j @@ -68,6 +74,7 @@ public class DeleteLagPortFsm extends AbstractStateMachine< private final DeleteLagPortRequest request; private final SwitchManagerCarrier carrier; private DeleteLogicalPortRequest grpcRequest; + private LagLogicalPort initialLagPort; private LagLogicalPort removedLagPort; public DeleteLagPortFsm(SwitchManagerCarrier carrier, String key, DeleteLagPortRequest request, @@ -94,12 +101,18 @@ public static StateMachineBuilder commands = lagPortOperationService + .buildLacpSpeakerCommands(switchId, initialLagPort.getLogicalPortNumber()); + + log.info("Sending LACP commands {} to switch {} LAG port {}. Key={}", + commands, switchId, initialLagPort.getLogicalPortNumber(), key); + carrier.sendOfCommandsToSpeaker(key, commands, OfCommandAction.DELETE, switchId); + } + void sendGrpcRequest(DeleteLagState from, DeleteLagState to, DeleteLagEvent event, DeleteLagContext context) { log.info("Sending delete LAG request {} to switch {}. Key={}", grpcRequest, switchId, key); carrier.sendCommandToSpeaker(key, grpcRequest); @@ -157,12 +188,12 @@ void finishedEnter(DeleteLagState from, DeleteLagState to, DeleteLagEvent event, if (removedLagPort != null) { response = new LagPortResponse( removedLagPort.getLogicalPortNumber(), removedLagPort.getPhysicalPorts().stream() - .map(PhysicalPort::getPortNumber).collect(Collectors.toSet())); + .map(PhysicalPort::getPortNumber).collect(Collectors.toSet()), removedLagPort.isLacpReply()); } else { // dummy response entity // TODO(surabujin): weird behaviour, can we be more correct? - response = new LagPortResponse(request.getLogicalPortNumber(), Collections.emptySet()); + response = new LagPortResponse(request.getLogicalPortNumber(), Collections.emptySet(), false); } InfoMessage message = new InfoMessage(response, System.currentTimeMillis(), key); @@ -202,7 +233,8 @@ protected void afterTransitionCausedException(DeleteLagState fromState, DeleteLa public enum DeleteLagState { START, REMOVE_LAG_FROM_DB, - CREATE_GRPC_COMMAND, + VALIDATE_REMOVE_REQUEST, + SPEAKER_COMMAND_SEND, GRPC_COMMAND_SEND, FINISHED_WITH_ERROR, FINISHED @@ -211,6 +243,8 @@ public enum DeleteLagState { public enum DeleteLagEvent { NEXT, LAG_REMOVED, + SKIP_SPEAKER_ENTITIES_REMOVAL, + SPEAKER_ENTITIES_REMOVED, ERROR } diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/SwitchSyncFsm.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/SwitchSyncFsm.java index fb2df371db8..4934bea2dae 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/SwitchSyncFsm.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/SwitchSyncFsm.java @@ -40,9 +40,6 @@ import static org.openkilda.wfm.topology.switchmanager.fsm.SwitchSyncFsm.SwitchSyncState.SEND_MODIFY_COMMANDS; import static org.openkilda.wfm.topology.switchmanager.fsm.SwitchSyncFsm.SwitchSyncState.SEND_REMOVE_COMMANDS; -import org.openkilda.floodlight.api.request.rulemanager.FlowCommand; -import org.openkilda.floodlight.api.request.rulemanager.GroupCommand; -import org.openkilda.floodlight.api.request.rulemanager.MeterCommand; import org.openkilda.floodlight.api.request.rulemanager.OfCommand; import org.openkilda.messaging.command.grpc.CreateOrUpdateLogicalPortRequest; import org.openkilda.messaging.command.grpc.DeleteLogicalPortRequest; @@ -616,7 +613,7 @@ List> cleanupDependenciesAndBuildCommandBatches(List(); } - currentBatch.addAll(group.stream().map(this::toCommand).collect(Collectors.toList())); + currentBatch.addAll(OfCommand.toOfCommands(group)); } if (!currentBatch.isEmpty()) { batches.add(currentBatch); @@ -625,17 +622,6 @@ List> cleanupDependenciesAndBuildCommandBatches(List physicalPorts; + boolean lacpReply; +} diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/CreateLagPortService.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/CreateLagPortService.java index 9da75f6b962..acf70b495f4 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/CreateLagPortService.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/CreateLagPortService.java @@ -15,11 +15,15 @@ package org.openkilda.wfm.topology.switchmanager.service; +import org.openkilda.floodlight.api.response.SpeakerResponse; +import org.openkilda.floodlight.api.response.rulemanager.SpeakerCommandResponse; import org.openkilda.messaging.MessageCookie; import org.openkilda.messaging.error.ErrorData; +import org.openkilda.messaging.error.ErrorType; import org.openkilda.messaging.info.InfoData; import org.openkilda.messaging.info.grpc.CreateOrUpdateLogicalPortResponse; import org.openkilda.messaging.swmanager.request.CreateLagPortRequest; +import org.openkilda.rulemanager.RuleManager; import org.openkilda.wfm.error.MessageDispatchException; import org.openkilda.wfm.error.UnexpectedInputException; import org.openkilda.wfm.share.utils.FsmExecutor; @@ -52,8 +56,8 @@ public class CreateLagPortService implements SwitchManagerHubService { private boolean active = true; - public CreateLagPortService(SwitchManagerCarrier carrier, LagPortOperationConfig config) { - this.lagPortOperationService = new LagPortOperationService(config); + public CreateLagPortService(SwitchManagerCarrier carrier, LagPortOperationConfig config, RuleManager ruleManager) { + this.lagPortOperationService = new LagPortOperationService(config, ruleManager); this.builder = CreateLagPortFsm.builder(); this.fsmExecutor = new FsmExecutor<>(CreateLagEvent.NEXT); this.carrier = carrier; @@ -89,7 +93,7 @@ public boolean isAllOperationsCompleted() { @Override public void timeout(@NonNull MessageCookie cookie) throws MessageDispatchException { - OperationTimeoutException error = new OperationTimeoutException("LAG create operation timeout"); + OperationTimeoutException error = new OperationTimeoutException("Create operation timeout"); fireFsmEvent(cookie, CreateLagEvent.ERROR, CreateLagContext.builder().error(error).build()); } @@ -103,6 +107,24 @@ public void dispatchWorkerMessage(InfoData payload, MessageCookie cookie) } } + @Override + public void dispatchWorkerMessage(SpeakerResponse payload, MessageCookie cookie) + throws UnexpectedInputException, MessageDispatchException { + if (payload instanceof SpeakerCommandResponse) { + SpeakerCommandResponse response = (SpeakerCommandResponse) payload; + if (response.isSuccess()) { + fireFsmEvent(cookie, CreateLagEvent.SPEAKER_ENTITIES_INSTALLED, CreateLagContext.builder().build()); + } else { + ErrorData errorData = new ErrorData(ErrorType.INTERNAL_ERROR, "OpenFlow commands failed", + response.getFailedCommandIds().values().toString()); + fireFsmEvent(cookie, CreateLagEvent.ERROR, + CreateLagContext.builder().error(new SpeakerFailureException(errorData)).build()); + } + } else { + throw new UnexpectedInputException(payload); + } + } + @Override public void dispatchErrorMessage(ErrorData payload, MessageCookie cookie) throws MessageDispatchException { diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/DeleteLagPortService.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/DeleteLagPortService.java index fba6b5659f7..6076cd9aa04 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/DeleteLagPortService.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/DeleteLagPortService.java @@ -15,12 +15,15 @@ package org.openkilda.wfm.topology.switchmanager.service; +import org.openkilda.floodlight.api.response.SpeakerResponse; +import org.openkilda.floodlight.api.response.rulemanager.SpeakerCommandResponse; import org.openkilda.messaging.MessageCookie; import org.openkilda.messaging.error.ErrorData; import org.openkilda.messaging.error.ErrorType; import org.openkilda.messaging.info.InfoData; import org.openkilda.messaging.info.grpc.DeleteLogicalPortResponse; import org.openkilda.messaging.swmanager.request.DeleteLagPortRequest; +import org.openkilda.rulemanager.RuleManager; import org.openkilda.wfm.error.MessageDispatchException; import org.openkilda.wfm.error.UnexpectedInputException; import org.openkilda.wfm.share.utils.FsmExecutor; @@ -53,8 +56,8 @@ public class DeleteLagPortService implements SwitchManagerHubService { private boolean active = true; - public DeleteLagPortService(SwitchManagerCarrier carrier, LagPortOperationConfig config) { - this.lagOperationService = new LagPortOperationService(config); + public DeleteLagPortService(SwitchManagerCarrier carrier, LagPortOperationConfig config, RuleManager ruleManager) { + this.lagOperationService = new LagPortOperationService(config, ruleManager); this.builder = DeleteLagPortFsm.builder(); this.fsmExecutor = new FsmExecutor<>(DeleteLagEvent.NEXT); this.carrier = carrier; @@ -88,6 +91,24 @@ public void dispatchWorkerMessage(InfoData payload, MessageCookie cookie) } } + @Override + public void dispatchWorkerMessage(SpeakerResponse payload, MessageCookie cookie) + throws UnexpectedInputException, MessageDispatchException { + if (payload instanceof SpeakerCommandResponse) { + SpeakerCommandResponse response = (SpeakerCommandResponse) payload; + if (response.isSuccess()) { + fireFsmEvent(cookie, DeleteLagEvent.SPEAKER_ENTITIES_REMOVED, DeleteLagContext.builder().build()); + } else { + ErrorData errorData = new ErrorData(ErrorType.INTERNAL_ERROR, "OpenFlow commands failed", + response.getFailedCommandIds().values().toString()); + fireFsmEvent(cookie, DeleteLagEvent.ERROR, + DeleteLagContext.builder().error(new SpeakerFailureException(errorData)).build()); + } + } else { + throw new UnexpectedInputException(payload); + } + } + @Override public void dispatchErrorMessage(ErrorData payload, MessageCookie cookie) throws MessageDispatchException { DeleteLagContext context = DeleteLagContext.builder() diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortOperationService.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortOperationService.java index eaeeda00fda..37f2606d62b 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortOperationService.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortOperationService.java @@ -18,6 +18,7 @@ import static java.lang.String.format; import static org.openkilda.model.SwitchFeature.BFD; +import org.openkilda.floodlight.api.request.rulemanager.OfCommand; import org.openkilda.model.Flow; import org.openkilda.model.FlowMirrorPath; import org.openkilda.model.IpSocketAddress; @@ -41,11 +42,16 @@ import org.openkilda.persistence.repositories.SwitchPropertiesRepository; import org.openkilda.persistence.repositories.SwitchRepository; import org.openkilda.persistence.tx.TransactionManager; +import org.openkilda.rulemanager.DataAdapter; +import org.openkilda.rulemanager.RuleManager; +import org.openkilda.rulemanager.adapter.PersistenceDataAdapter; import org.openkilda.wfm.share.utils.PoolManager; import org.openkilda.wfm.topology.switchmanager.error.InconsistentDataException; import org.openkilda.wfm.topology.switchmanager.error.InvalidDataException; import org.openkilda.wfm.topology.switchmanager.error.LagPortNotFoundException; import org.openkilda.wfm.topology.switchmanager.error.SwitchNotFoundException; +import org.openkilda.wfm.topology.switchmanager.model.LagRollbackData; +import org.openkilda.wfm.topology.switchmanager.model.LagRollbackData.LagRollbackDataBuilder; import org.openkilda.wfm.topology.switchmanager.service.configs.LagPortOperationConfig; import com.google.common.collect.Sets; @@ -78,13 +84,17 @@ public class LagPortOperationService { private final FlowRepository flowRepository; private final FlowMirrorPathRepository flowMirrorPathRepository; private final PortRepository portRepository; + private final RuleManager ruleManager; private final LagPortOperationConfig config; private final LRUMap> portNumberPool; - public LagPortOperationService(LagPortOperationConfig config) { + + + public LagPortOperationService(LagPortOperationConfig config, RuleManager ruleManager) { this.transactionManager = config.getTransactionManager(); + this.ruleManager = ruleManager; @NonNull RepositoryFactory repositoryFactory = config.getRepositoryFactory(); this.switchRepository = repositoryFactory.createSwitchRepository(); @@ -104,23 +114,28 @@ public LagPortOperationService(LagPortOperationConfig config) { /** * Create LAG logical port. */ - public int createLagPort(SwitchId switchId, Set targetPorts) { + public int createLagPort(SwitchId switchId, Set targetPorts, boolean lacpReply) { verifyTargetPortsInput(targetPorts); LagLogicalPort port = transactionManager.doInTransaction( - newCreateRetryPolicy(switchId), () -> createTransaction(switchId, targetPorts)); + newCreateRetryPolicy(switchId), () -> createTransaction(switchId, targetPorts, lacpReply)); return port.getLogicalPortNumber(); } /** * Update LAG logical port. */ - public Set updateLagPort(SwitchId switchId, int logicalPortNumber, Set targetPorts) { + public LagRollbackData updateLagPort( + SwitchId switchId, int logicalPortNumber, Set targetPorts, boolean lacpReply) { verifyTargetPortsInput(targetPorts); - Set targetsBeforeUpdate = new HashSet<>(); + LagRollbackDataBuilder rollbackDataBuilder = LagRollbackData.builder(); transactionManager.doInTransaction( newUpdateRetryPolicy(switchId), - () -> targetsBeforeUpdate.addAll(updateTransaction(switchId, logicalPortNumber, targetPorts))); - return targetsBeforeUpdate; + () -> { + LagRollbackData data = updateTransaction(switchId, logicalPortNumber, targetPorts, lacpReply); + rollbackDataBuilder.physicalPorts(data.getPhysicalPorts()); + rollbackDataBuilder.lacpReply(data.isLacpReply()); + }); + return rollbackDataBuilder.build(); } /** @@ -224,18 +239,31 @@ public String getSwitchIpAddress(SwitchId switchId) throws InvalidDataException, format("Switch %s has invalid IP address %s", sw, sw.getSocketAddress()))); } + /** + * Builds LACP commands witch will be sent to speaker. + */ + public List buildLacpSpeakerCommands(SwitchId switchId, int port) { + DataAdapter dataAdapter = PersistenceDataAdapter.builder() + .switchIds(Collections.singleton(switchId)) + .pathIds(Collections.emptySet()) + .persistenceManager(config.getPersistenceManager()) + .build(); + return OfCommand.toOfCommands(ruleManager.buildLacpRules(switchId, port, dataAdapter)); + } + private void verifyTargetPortsInput(Set targetPorts) { if (targetPorts == null || targetPorts.isEmpty()) { throw new InvalidDataException("Physical ports list is empty"); } } - private LagLogicalPort createTransaction(SwitchId switchId, Set targetPorts) { + private LagLogicalPort createTransaction(SwitchId switchId, Set targetPorts, boolean lacpReply) { Switch sw = querySwitch(switchId); // locate switch first to produce correct error if switch is missing ensureLagDataValid(sw, targetPorts); ensureNoLagCollisions(sw.getSwitchId(), targetPorts); - LagLogicalPort port = queryPoolManager(switchId).allocate(entityId -> newLagLogicalPort(switchId, entityId)); + LagLogicalPort port = queryPoolManager(switchId).allocate(entityId -> newLagLogicalPort( + switchId, entityId, lacpReply)); replacePhysicalPorts(port, switchId, targetPorts); log.info("Adding new LAG logical port entry into DB: {}", port); @@ -243,28 +271,32 @@ private LagLogicalPort createTransaction(SwitchId switchId, Set targetP return port; } - private Set updateTransaction(SwitchId switchId, int logicalPortNumber, Set targetPorts) { + private LagRollbackData updateTransaction( + SwitchId switchId, int logicalPortNumber, Set targetPorts, boolean lacpReply) { Switch sw = querySwitch(switchId); ensureLagDataValid(sw, targetPorts); ensureNoLagCollisions(switchId, targetPorts, logicalPortNumber); ensureTargetPortsBandwidthValid(sw, targetPorts, logicalPortNumber); LagLogicalPort port = queryLagPort(sw.getSwitchId(), logicalPortNumber); - Set targetsBeforeUpdate = port.getPhysicalPorts().stream() + LagRollbackData rollbackData = new LagRollbackData(port.getPhysicalPorts().stream() .map(PhysicalPort::getPortNumber) - .collect(Collectors.toSet()); + .collect(Collectors.toSet()), port.isLacpReply()); log.info( - "Updating LAG logical port #{} on {} entry desired target ports set {}, current target ports set {}", + "Updating LAG logical port #{} on {} entry desired target ports set {}, current target ports set {}. " + + "Desired LACP reply {}, current LACP reply {}", logicalPortNumber, switchId, - formatPortNumbersSet(targetPorts), formatPortNumbersSet(targetsBeforeUpdate)); + formatPortNumbersSet(targetPorts), formatPortNumbersSet(rollbackData.getPhysicalPorts()), + lacpReply, rollbackData.isLacpReply()); replacePhysicalPorts(port, switchId, targetPorts); + port.setLacpReply(lacpReply); - return targetsBeforeUpdate; + return rollbackData; } private LagLogicalPort deleteTransaction(SwitchId switchId, int logicalPortNumber) { LagLogicalPort port = ensureDeleteIsPossible(switchId, logicalPortNumber); - log.info("Removing LAG logical port entry into DB: {}", port); + log.info("Removing LAG logical port entry from DB: {}", port); lagLogicalPortRepository.remove(port); return port; } @@ -346,8 +378,8 @@ private Switch querySwitch(SwitchId switchId) throws SwitchNotFoundException { return switchRepository.findById(switchId).orElseThrow(() -> new SwitchNotFoundException(switchId)); } - private LagLogicalPort newLagLogicalPort(SwitchId switchId, long portNumber) { - return new LagLogicalPort(switchId, (int) portNumber, Collections.emptyList()); + private LagLogicalPort newLagLogicalPort(SwitchId switchId, long portNumber, boolean lacpReply) { + return new LagLogicalPort(switchId, (int) portNumber, Collections.emptyList(), lacpReply); } private LagLogicalPort queryLagPort(SwitchId switchId, int logicalPortNumber) { diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/SwitchRuleService.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/SwitchRuleService.java index 5632bf3bb6a..c6106b58cbb 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/SwitchRuleService.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/SwitchRuleService.java @@ -22,8 +22,6 @@ import static org.openkilda.wfm.topology.switchmanager.service.impl.PredicateBuilder.isInstallServiceRulesRequired; import org.openkilda.floodlight.api.request.rulemanager.FlowCommand; -import org.openkilda.floodlight.api.request.rulemanager.GroupCommand; -import org.openkilda.floodlight.api.request.rulemanager.MeterCommand; import org.openkilda.floodlight.api.request.rulemanager.OfCommand; import org.openkilda.floodlight.api.response.SpeakerResponse; import org.openkilda.floodlight.api.response.rulemanager.SpeakerCommandResponse; @@ -46,8 +44,6 @@ import org.openkilda.persistence.repositories.SwitchRepository; import org.openkilda.rulemanager.DataAdapter; import org.openkilda.rulemanager.FlowSpeakerData; -import org.openkilda.rulemanager.GroupSpeakerData; -import org.openkilda.rulemanager.MeterSpeakerData; import org.openkilda.rulemanager.RuleManager; import org.openkilda.rulemanager.SpeakerData; import org.openkilda.rulemanager.adapter.PersistenceDataAdapter; @@ -155,7 +151,7 @@ public void deleteRules(String key, SwitchRulesDeleteRequest request) { RuleManagerHelper.reverseDependencies(speakerData); List removeCommands = speakerData.stream() .filter(data -> toRemove.contains(data.getUuid())) - .map(this::toCommand) + .map(OfCommand::toOfCommand) .collect(Collectors.toList()); CommandsBatch deleteCommandsBatch = new CommandsBatch(OfCommandAction.DELETE, removeCommands); List commandsBatches = Lists.newArrayList(deleteCommandsBatch); @@ -167,7 +163,7 @@ public void deleteRules(String key, SwitchRulesDeleteRequest request) { .collect(Collectors.toList()); List installCommands = speakerData.stream() .filter(data -> toInstall.contains(data.getUuid())) - .map(this::toCommand) + .map(OfCommand::toOfCommand) .collect(Collectors.toList()); CommandsBatch installCommandsBatch = new CommandsBatch(OfCommandAction.INSTALL_IF_NOT_EXIST, installCommands); @@ -205,7 +201,7 @@ public void installRules(String key, SwitchRulesInstallRequest request) { List commands = speakerData.stream() .filter(data -> toInstall.contains(data.getUuid())) - .map(this::toCommand) + .map(OfCommand::toOfCommand) .collect(Collectors.toList()); CommandsBatch commandsBatch = new CommandsBatch(OfCommandAction.INSTALL_IF_NOT_EXIST, commands); CommandsQueue commandsQueue = new CommandsQueue(switchId, singletonList(commandsBatch)); @@ -234,17 +230,6 @@ private void processCommandsBatch(String key, CommandsQueue commandsQueue) { commandsQueue.getSwitchId()); } - private OfCommand toCommand(SpeakerData speakerData) { - if (speakerData instanceof FlowSpeakerData) { - return new FlowCommand((FlowSpeakerData) speakerData); - } else if (speakerData instanceof MeterSpeakerData) { - return new MeterCommand((MeterSpeakerData) speakerData); - } else if (speakerData instanceof GroupSpeakerData) { - return new GroupCommand((GroupSpeakerData) speakerData); - } - throw new IllegalStateException(format("Unknown speaker data type %s", speakerData)); - } - private void handleRulesResponse(String key, SwitchRulesResponse response) { carrier.cancelTimeoutCallback(key); InfoMessage message = new InfoMessage(response, System.currentTimeMillis(), key); diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/UpdateLagPortService.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/UpdateLagPortService.java index 581c5dcfeeb..ebf6e7cb09d 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/UpdateLagPortService.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/UpdateLagPortService.java @@ -15,12 +15,15 @@ package org.openkilda.wfm.topology.switchmanager.service; +import org.openkilda.floodlight.api.response.SpeakerResponse; +import org.openkilda.floodlight.api.response.rulemanager.SpeakerCommandResponse; import org.openkilda.messaging.MessageCookie; import org.openkilda.messaging.error.ErrorData; import org.openkilda.messaging.error.ErrorType; import org.openkilda.messaging.info.InfoData; import org.openkilda.messaging.info.grpc.CreateOrUpdateLogicalPortResponse; import org.openkilda.messaging.swmanager.request.UpdateLagPortRequest; +import org.openkilda.rulemanager.RuleManager; import org.openkilda.wfm.error.MessageDispatchException; import org.openkilda.wfm.error.UnexpectedInputException; import org.openkilda.wfm.topology.switchmanager.error.InconsistentDataException; @@ -49,8 +52,8 @@ public class UpdateLagPortService implements SwitchManagerHubService { private boolean active = true; - public UpdateLagPortService(SwitchManagerCarrier carrier, LagPortOperationConfig config) { - this(carrier, new LagPortOperationService(config)); + public UpdateLagPortService(SwitchManagerCarrier carrier, LagPortOperationConfig config, RuleManager ruleManager) { + this(carrier, new LagPortOperationService(config, ruleManager)); } public UpdateLagPortService(SwitchManagerCarrier carrier, LagPortOperationService operationService) { @@ -98,9 +101,26 @@ public void dispatchWorkerMessage(InfoData payload, MessageCookie cookie) } } + @Override + public void dispatchWorkerMessage(SpeakerResponse payload, MessageCookie cookie) + throws UnexpectedInputException, MessageDispatchException { + if (payload instanceof SpeakerCommandResponse) { + SpeakerCommandResponse response = (SpeakerCommandResponse) payload; + if (response.isSuccess()) { + process(cookie, (h, nested) -> h.dispatchFloodlightResponse(payload, nested)); + } else { + ErrorData errorData = new ErrorData(ErrorType.INTERNAL_ERROR, "OpenFlow commands failed", + response.getFailedCommandIds().values().toString()); + dispatchErrorMessage(errorData, cookie); + } + } else { + throw new UnexpectedInputException(payload); + } + } + @Override public void dispatchErrorMessage(ErrorData payload, MessageCookie cookie) throws MessageDispatchException { - process(cookie, (h, nested) -> h.dispatchGrpcResponse(payload, nested)); + process(cookie, (h, nested) -> h.dispatchErrorResponse(payload, nested)); } @Override diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/configs/LagPortOperationConfig.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/configs/LagPortOperationConfig.java index 6b96f37c6a9..9d81bd3e66b 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/configs/LagPortOperationConfig.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/configs/LagPortOperationConfig.java @@ -15,6 +15,7 @@ package org.openkilda.wfm.topology.switchmanager.service.configs; +import org.openkilda.persistence.PersistenceManager; import org.openkilda.persistence.repositories.RepositoryFactory; import org.openkilda.persistence.tx.TransactionManager; import org.openkilda.wfm.share.utils.PoolManager; @@ -26,6 +27,9 @@ @Value public class LagPortOperationConfig { + @NonNull + PersistenceManager persistenceManager; + @NonNull RepositoryFactory repositoryFactory; @@ -41,11 +45,11 @@ public class LagPortOperationConfig { @Builder public LagPortOperationConfig( - @NonNull RepositoryFactory repositoryFactory, @NonNull TransactionManager transactionManager, - int bfdPortOffset, int bfdPortMaxNumber, + @NonNull PersistenceManager persistenceManager, int bfdPortOffset, int bfdPortMaxNumber, int portNumberFirst, int portNumberLast, int poolChunksCount, int poolCacheSize) { - this.repositoryFactory = repositoryFactory; - this.transactionManager = transactionManager; + this.persistenceManager = persistenceManager; + this.repositoryFactory = persistenceManager.getRepositoryFactory(); + this.transactionManager = persistenceManager.getTransactionManager(); Preconditions.checkArgument(0 < bfdPortOffset, String.format( "BFD logical port offset bfdPortOffset==%d must be greater than 0", bfdPortOffset)); diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/handler/LagPortUpdateHandler.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/handler/LagPortUpdateHandler.java index 3f9689c5388..8a3af5a9c5c 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/handler/LagPortUpdateHandler.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/handler/LagPortUpdateHandler.java @@ -15,8 +15,9 @@ package org.openkilda.wfm.topology.switchmanager.service.handler; +import org.openkilda.floodlight.api.request.rulemanager.OfCommand; +import org.openkilda.floodlight.api.response.SpeakerResponse; import org.openkilda.messaging.MessageCookie; -import org.openkilda.messaging.MessageData; import org.openkilda.messaging.command.grpc.CreateOrUpdateLogicalPortRequest; import org.openkilda.messaging.error.ErrorData; import org.openkilda.messaging.error.ErrorType; @@ -24,7 +25,9 @@ import org.openkilda.messaging.model.grpc.LogicalPortType; import org.openkilda.messaging.swmanager.request.UpdateLagPortRequest; import org.openkilda.messaging.swmanager.response.LagPortResponse; +import org.openkilda.wfm.topology.switchmanager.bolt.SwitchManagerHub.OfCommandAction; import org.openkilda.wfm.topology.switchmanager.error.SwitchManagerException; +import org.openkilda.wfm.topology.switchmanager.model.LagRollbackData; import org.openkilda.wfm.topology.switchmanager.service.LagPortOperationService; import org.openkilda.wfm.topology.switchmanager.service.SwitchManagerCarrier; @@ -34,6 +37,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -55,7 +59,7 @@ public class LagPortUpdateHandler { private final UpdateLagPortRequest goal; @VisibleForTesting - Set rollbackTargets; + LagRollbackData rollbackData; private final Set pendingSpeakerRequests = new HashSet<>(); @@ -73,14 +77,15 @@ public LagPortUpdateHandler( */ public void start() { Set targetPorts = new HashSet<>(goal.getTargetPorts()); - rollbackTargets = operationService.updateLagPort(goal.getSwitchId(), goal.getLogicalPortNumber(), targetPorts); + rollbackData = operationService.updateLagPort( + goal.getSwitchId(), goal.getLogicalPortNumber(), targetPorts, goal.isLacpReply()); CreateOrUpdateLogicalPortRequest request = newGrpcRequest(targetPorts); MessageCookie cookie = newMessageCookie(); log.info( - "Going to update {}, target ports set: {}", - formatLagPortReference(), formatTargetPorts(goal.getTargetPorts())); + "Going to update {}, target ports set: {}, target LACP reply {}", + formatLagPortReference(), formatTargetPorts(goal.getTargetPorts()), goal.isLacpReply()); carrier.sendCommandToSpeaker(request, cookie); pendingSpeakerRequests.add(cookie); } @@ -94,14 +99,42 @@ public void dispatchGrpcResponse(CreateOrUpdateLogicalPortResponse response, Mes return; } - log.info("{} have been updated", formatLagPortReference()); - carrier.response(requestKey, new LagPortResponse(goal.getLogicalPortNumber(), goal.getTargetPorts())); + if (rollbackData.isLacpReply() == goal.isLacpReply()) { + sendResponseToNorthbound(); + } else { + sendCommandsToFloodlight(); + } + } + + private void sendCommandsToFloodlight() { + List commands = operationService.buildLacpSpeakerCommands( + goal.getSwitchId(), goal.getLogicalPortNumber()); + MessageCookie floodlightMessageCookie = newMessageCookie(); + OfCommandAction action = goal.isLacpReply() ? OfCommandAction.INSTALL : OfCommandAction.DELETE; + + log.info("Sending {} commands {} to switch {} to update LAG port {} from {} to {}", + action, commands, goal.getSwitchId(), goal.getLogicalPortNumber(), rollbackData, goal); + carrier.sendOfCommandsToSpeaker(commands, action, goal.getSwitchId(), floodlightMessageCookie); + pendingSpeakerRequests.add(floodlightMessageCookie); + } + + /** + * Handle Floodlight response. + */ + public void dispatchFloodlightResponse(SpeakerResponse response, MessageCookie cookie) { + if (!pendingSpeakerRequests.remove(cookie)) { + logUnwantedResponse(response); + return; + } + + log.info("OpenFLow rules for {} have been updated", formatLagPortReference()); + sendResponseToNorthbound(); } /** - * Handle GRPC error response. + * Handle error response. */ - public void dispatchGrpcResponse(ErrorData response, MessageCookie cookie) { + public void dispatchErrorResponse(ErrorData response, MessageCookie cookie) { if (!pendingSpeakerRequests.remove(cookie)) { logUnwantedResponse(response); return; @@ -127,6 +160,12 @@ public boolean isCompleted() { return pendingSpeakerRequests.isEmpty(); } + private void sendResponseToNorthbound() { + log.info("{} have been updated", formatLagPortReference()); + carrier.response(requestKey, new LagPortResponse( + goal.getLogicalPortNumber(), goal.getTargetPorts(), goal.isLacpReply())); + } + private void fail(ErrorType type, String description) { String errorMessage; if (rollback()) { @@ -143,7 +182,8 @@ private void fail(ErrorType type, String description) { private boolean rollback() { try { - operationService.updateLagPort(goal.getSwitchId(), goal.getLogicalPortNumber(), rollbackTargets); + operationService.updateLagPort(goal.getSwitchId(), goal.getLogicalPortNumber(), + rollbackData.getPhysicalPorts(), rollbackData.isLacpReply()); } catch (SwitchManagerException e) { log.error("Unable to rollback DB update for {}: {}", formatLagPortReference(), e.getMessage()); return false; @@ -151,8 +191,8 @@ private boolean rollback() { return true; } - private void logUnwantedResponse(MessageData payload) { - log.info("Got unwanted/outdated GRPC response: {}", payload); + private void logUnwantedResponse(Serializable response) { + log.info("Got unwanted/outdated GRPC response: {}", response); } private MessageCookie newMessageCookie() { diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/mappers/LogicalPortMapperTest.java b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/mappers/LogicalPortMapperTest.java index 19d6ac360e0..933cd4c0a63 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/mappers/LogicalPortMapperTest.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/mappers/LogicalPortMapperTest.java @@ -37,7 +37,7 @@ public class LogicalPortMapperTest { @Test public void mapLagLogicalPortTest() { LagLogicalPort lagLogicalPort = new LagLogicalPort(SWITCH_ID, LAG_PORT, - Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2)); + Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2), false); LogicalPortInfoEntry port = INSTANCE.map(lagLogicalPort); assertEquals(LAG_PORT, port.getLogicalPortNumber().intValue()); diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/mappers/ValidationMapperTest.java b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/mappers/ValidationMapperTest.java index bf7b8785b32..fd291053f71 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/mappers/ValidationMapperTest.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/mappers/ValidationMapperTest.java @@ -204,7 +204,7 @@ private static MeterSpeakerData initializeMeterSpeakerData(MeterId uniqueMeterId private static LagLogicalPort initializeLogicalPortData(SwitchId uniqueSwitchIdField) { return new LagLogicalPort(uniqueSwitchIdField, LAG_PORT, - Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2)); + Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2), true); } private static List missingGroups = new LinkedList<>(); diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/UpdateLagPortServiceTest.java b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/UpdateLagPortServiceTest.java index ed83fa60a52..3d8700cd027 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/UpdateLagPortServiceTest.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/UpdateLagPortServiceTest.java @@ -20,8 +20,7 @@ import org.openkilda.messaging.error.ErrorType; import org.openkilda.messaging.swmanager.request.UpdateLagPortRequest; import org.openkilda.model.SwitchId; -import org.openkilda.persistence.repositories.RepositoryFactory; -import org.openkilda.persistence.tx.TransactionManager; +import org.openkilda.persistence.PersistenceManager; import org.openkilda.wfm.topology.switchmanager.error.InconsistentDataException; import org.openkilda.wfm.topology.switchmanager.error.InvalidDataException; import org.openkilda.wfm.topology.switchmanager.error.SwitchNotFoundException; @@ -47,10 +46,7 @@ public class UpdateLagPortServiceTest { private LagPortOperationService operationService; @Mock - RepositoryFactory repositoryFactory; - - @Mock - TransactionManager transactionManager; + PersistenceManager persistenceManager; @Test public void testKeepHandlerOnRequestKeyCollision() { @@ -61,13 +57,13 @@ public void testKeepHandlerOnRequestKeyCollision() { Assert.assertFalse(subject.activeHandlers.containsKey(requestKey)); UpdateLagPortRequest request = new UpdateLagPortRequest( - new SwitchId(1), (int) config.getPoolConfig().getIdMinimum(), Sets.newHashSet(1, 2, 3)); + new SwitchId(1), (int) config.getPoolConfig().getIdMinimum(), Sets.newHashSet(1, 2, 3), true); subject.update(requestKey, request); LagPortUpdateHandler origin = subject.activeHandlers.get(requestKey); Assert.assertNotNull(origin); UpdateLagPortRequest request2 = new UpdateLagPortRequest( - new SwitchId(2), (int) config.getPoolConfig().getIdMinimum(), Sets.newHashSet(1, 2, 3)); + new SwitchId(2), (int) config.getPoolConfig().getIdMinimum(), Sets.newHashSet(1, 2, 3), true); Assert.assertThrows(InconsistentDataException.class, () -> subject.update(requestKey, request2)); Assert.assertSame(origin, subject.activeHandlers.get(requestKey)); } @@ -82,7 +78,7 @@ public void testHandlerRemoveOnException() { String requestKey = "test-key"; UpdateLagPortRequest request = new UpdateLagPortRequest( - switchId, (int) config.getPoolConfig().getIdMinimum(), Sets.newHashSet(1, 2, 3)); + switchId, (int) config.getPoolConfig().getIdMinimum(), Sets.newHashSet(1, 2, 3), true); subject.update(requestKey, request); Mockito.verify(carrier).errorResponse( Mockito.eq(requestKey), Mockito.eq(ErrorType.NOT_FOUND), Mockito.anyString(), Mockito.anyString()); @@ -99,9 +95,9 @@ public void testInvalidTargetPortsBandwidthException() { int logicalPortNumber = (int) config.getPoolConfig().getIdMinimum(); Set targetPorts = Sets.newHashSet(1, 2); - UpdateLagPortRequest request = new UpdateLagPortRequest(switchId, logicalPortNumber, targetPorts); + UpdateLagPortRequest request = new UpdateLagPortRequest(switchId, logicalPortNumber, targetPorts, true); - Mockito.when(operationService.updateLagPort(switchId, logicalPortNumber, targetPorts)).thenThrow( + Mockito.when(operationService.updateLagPort(switchId, logicalPortNumber, targetPorts, true)).thenThrow( new InvalidDataException(format("Not enough bandwidth for LAG port %s.", logicalPortNumber))); subject.update(requestKey, request); @@ -112,6 +108,6 @@ public void testInvalidTargetPortsBandwidthException() { private LagPortOperationConfig newConfig() { return new LagPortOperationConfig( - repositoryFactory, transactionManager, 1000, 1999, 2000, 2999, 10, 100); + persistenceManager, 1000, 1999, 2000, 2999, 10, 100); } } diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/handler/LagPortUpdateHandlerTest.java b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/handler/LagPortUpdateHandlerTest.java index 9771bfc17dc..6f1f1d9f307 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/handler/LagPortUpdateHandlerTest.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/handler/LagPortUpdateHandlerTest.java @@ -27,6 +27,7 @@ import org.openkilda.model.SwitchId; import org.openkilda.wfm.topology.switchmanager.error.SwitchManagerException; import org.openkilda.wfm.topology.switchmanager.error.SwitchNotFoundException; +import org.openkilda.wfm.topology.switchmanager.model.LagRollbackData; import org.openkilda.wfm.topology.switchmanager.service.LagPortOperationService; import org.openkilda.wfm.topology.switchmanager.service.SwitchManagerCarrier; @@ -41,7 +42,6 @@ import org.mockito.junit.MockitoJUnitRunner; import java.util.HashSet; -import java.util.Set; @RunWith(MockitoJUnitRunner.class) public class LagPortUpdateHandlerTest { @@ -76,7 +76,8 @@ public void testHappyPath() { Mockito.verifyNoMoreInteractions(operationService); Mockito.verify(carrier).response( Mockito.eq(requestKey), - Mockito.eq(new LagPortResponse(request.getLogicalPortNumber(), request.getTargetPorts()))); + Mockito.eq(new LagPortResponse(request.getLogicalPortNumber(), request.getTargetPorts(), + request.isLacpReply()))); Mockito.verifyNoMoreInteractions(carrier); Assert.assertTrue(subject.isCompleted()); @@ -93,10 +94,11 @@ public void testGrpcErrorResponse() { // GRPC error response ErrorData error = new ErrorData(ErrorType.INTERNAL_ERROR, "Dummy error message", "Dummy error description"); - subject.dispatchGrpcResponse(error, grpcRequestCookie); + subject.dispatchErrorResponse(error, grpcRequestCookie); Mockito.verify(operationService).updateLagPort( Mockito.eq(request.getSwitchId()), Mockito.eq(request.getLogicalPortNumber()), - Mockito.eq(subject.rollbackTargets)); + Mockito.eq(subject.rollbackData.getPhysicalPorts()), + Mockito.eq(subject.rollbackData.isLacpReply())); Mockito.verifyNoMoreInteractions(operationService); Mockito.verify(carrier).errorResponse( Mockito.eq(requestKey), Mockito.eq(error.getErrorType()), Mockito.anyString(), Mockito.anyString()); @@ -118,7 +120,8 @@ public void testTimeout() { subject.timeout(); Mockito.verify(operationService).updateLagPort( Mockito.eq(request.getSwitchId()), Mockito.eq(request.getLogicalPortNumber()), - Mockito.eq(subject.rollbackTargets)); + Mockito.eq(subject.rollbackData.getPhysicalPorts()), + Mockito.eq(subject.rollbackData.isLacpReply())); Mockito.verifyNoMoreInteractions(operationService); Mockito.verify(carrier).errorResponse( Mockito.eq(requestKey), Mockito.eq(ErrorType.OPERATION_TIMED_OUT), @@ -142,10 +145,12 @@ public void testExceptionOnErrorFromOperationService() { private MessageCookie verifyStartHandler( LagPortUpdateHandler subject, UpdateLagPortRequest request, String swAddress) { - Set existingTargets = ImmutableSet.of(3, 4, 5); + LagRollbackData existingTargets = LagRollbackData.builder() + .physicalPorts(ImmutableSet.of(3, 4, 5)).lacpReply(true).build(); Mockito.when(operationService.getSwitchIpAddress(request.getSwitchId())).thenReturn(swAddress); Mockito.when(operationService.updateLagPort( - Mockito.eq(request.getSwitchId()), Mockito.eq(request.getLogicalPortNumber()), Mockito.any())) + Mockito.eq(request.getSwitchId()), Mockito.eq(request.getLogicalPortNumber()), Mockito.any(), + Mockito.eq(request.isLacpReply()))) .thenReturn(existingTargets); Mockito.verifyNoInteractions(carrier); @@ -154,12 +159,13 @@ private MessageCookie verifyStartHandler( // DB update and GRPC request subject.start(); Assert.assertFalse(subject.isCompleted()); - Assert.assertEquals(existingTargets, subject.rollbackTargets); + Assert.assertEquals(existingTargets, subject.rollbackData); Mockito.verify(operationService).getSwitchIpAddress(Mockito.eq(request.getSwitchId())); Mockito.verify(operationService).updateLagPort( Mockito.eq(request.getSwitchId()), Mockito.eq(request.getLogicalPortNumber()), - Mockito.eq(new HashSet<>(request.getTargetPorts()))); + Mockito.eq(new HashSet<>(request.getTargetPorts())), + Mockito.eq(request.isLacpReply())); Mockito.verifyNoMoreInteractions(operationService); ArgumentCaptor grpcRequestCookie = ArgumentCaptor.forClass(MessageCookie.class); @@ -173,6 +179,6 @@ private MessageCookie verifyStartHandler( } private UpdateLagPortRequest newRequest() { - return new UpdateLagPortRequest(new SwitchId(1), 2001, Sets.newHashSet(1, 2, 3)); + return new UpdateLagPortRequest(new SwitchId(1), 2001, Sets.newHashSet(1, 2, 3), true); } } diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/impl/ValidationServiceImplTest.java b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/impl/ValidationServiceImplTest.java index 052f09c1533..b4d6c3a56bd 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/impl/ValidationServiceImplTest.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/impl/ValidationServiceImplTest.java @@ -389,11 +389,11 @@ private PersistenceManager build() { when(repositoryFactory.createSwitchRepository()).thenReturn(switchRepository); LagLogicalPort lagLogicalPortA = new LagLogicalPort(SWITCH_ID_A, LOGICAL_PORT_NUMBER_1, - Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2)); + Lists.newArrayList(PHYSICAL_PORT_1, PHYSICAL_PORT_2), true); LagLogicalPort lagLogicalPortB = new LagLogicalPort(SWITCH_ID_A, LOGICAL_PORT_NUMBER_2, - Lists.newArrayList(PHYSICAL_PORT_3, PHYSICAL_PORT_4)); + Lists.newArrayList(PHYSICAL_PORT_3, PHYSICAL_PORT_4), false); LagLogicalPort lagLogicalPortC = new LagLogicalPort(SWITCH_ID_A, LOGICAL_PORT_NUMBER_3, - Lists.newArrayList(PHYSICAL_PORT_5, PHYSICAL_PORT_6)); + Lists.newArrayList(PHYSICAL_PORT_5, PHYSICAL_PORT_6), true); when(lagLogicalPortRepository.findBySwitchId(SWITCH_ID_A)).thenReturn(Lists.newArrayList( lagLogicalPortA, lagLogicalPortB, lagLogicalPortC)); diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/SwitchHelper.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/SwitchHelper.groovy index ca3891218c9..93c245b1ff1 100644 --- a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/SwitchHelper.groovy +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/SwitchHelper.groovy @@ -47,6 +47,8 @@ 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 @@ -164,6 +166,7 @@ class SwitchHelper { def devicesRules = [] def server42Rules = [] def vxlanRules = [] + def lacpRules = [] def toggles = northbound.get().getFeatureToggles() if (swProps.multiTable) { multiTableRules = [MULTITABLE_PRE_INGRESS_PASS_THROUGH_COOKIE, MULTITABLE_INGRESS_DROP_COOKIE, @@ -235,21 +238,30 @@ class SwitchHelper { if (sw.features.contains(NOVIFLOW_PUSH_POP_VXLAN) || sw.features.contains(KILDA_OVS_PUSH_POP_MATCH_VXLAN)) { vxlanRules << VERIFICATION_UNICAST_VXLAN_RULE_COOKIE } + def lacpPorts = northboundV2.get().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 (sw.noviflow && !sw.wb5164) { return ([DROP_RULE_COOKIE, VERIFICATION_BROADCAST_RULE_COOKIE, VERIFICATION_UNICAST_RULE_COOKIE, DROP_VERIFICATION_LOOP_RULE_COOKIE, CATCH_BFD_RULE_COOKIE, ROUND_TRIP_LATENCY_RULE_COOKIE] - + vxlanRules + multiTableRules + devicesRules + server42Rules) + + vxlanRules + multiTableRules + devicesRules + server42Rules + lacpRules) } else if ((sw.noviflow || sw.nbFormat().manufacturer == "E") && sw.wb5164) { return ([DROP_RULE_COOKIE, VERIFICATION_BROADCAST_RULE_COOKIE, VERIFICATION_UNICAST_RULE_COOKIE, DROP_VERIFICATION_LOOP_RULE_COOKIE, CATCH_BFD_RULE_COOKIE] - + vxlanRules + multiTableRules + devicesRules + server42Rules) + + vxlanRules + multiTableRules + devicesRules + server42Rules + lacpRules) } else if (sw.ofVersion == "OF_12") { return [VERIFICATION_BROADCAST_RULE_COOKIE] } else { return ([DROP_RULE_COOKIE, VERIFICATION_BROADCAST_RULE_COOKIE, VERIFICATION_UNICAST_RULE_COOKIE, DROP_VERIFICATION_LOOP_RULE_COOKIE] - + vxlanRules + multiTableRules + devicesRules + server42Rules) + + vxlanRules + multiTableRules + devicesRules + server42Rules + lacpRules) } } @@ -284,6 +296,13 @@ class SwitchHelper { result << MeterId.createMeterIdForDefaultRule(ARP_TRANSIT_COOKIE) //20 result << MeterId.createMeterIdForDefaultRule(ARP_INGRESS_COOKIE) //21 } + def lacpPorts = northboundV2.get().getLagLogicalPort(sw.dpId).findAll { + it.lacpReply + } + if (!lacpPorts.isEmpty()) { + result << MeterId.LACP_REPLY_METER_ID //31 + } + return result*.getValue().sort() } @@ -471,6 +490,18 @@ class SwitchHelper { } } + /** + * Verifies that specified logical port sections in the validation response are empty. + */ + static void verifyLogicalPortsSectionsAreEmpty(SwitchValidationExtendedResult switchValidateInfo, + List sections = ["missing", "excess", "misconfigured"]) { + def assertions = new SoftAssertions() + sections.each { String section -> + assertions.checkSucceeds { assert switchValidateInfo.logicalPorts."$section".empty } + } + assertions.verify() + } + static SwitchProperties getDummyServer42Props() { return new SwitchProperties(true, 33, "00:00:00:00:00:00", 1, null) } 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 bee699a2976..1b17e9a1bbf 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,6 +3,8 @@ package org.openkilda.functionaltests.spec.switches import static groovyx.gpars.GParsPool.withPool import static org.junit.jupiter.api.Assumptions.assumeTrue import static org.openkilda.functionaltests.extension.tags.Tag.HARDWARE +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.testing.Constants.NON_EXISTENT_SWITCH_ID import static org.openkilda.testing.service.floodlight.model.FloodlightConnectMode.RW @@ -14,6 +16,9 @@ import org.openkilda.messaging.error.MessageError import org.openkilda.messaging.model.grpc.LogicalPortType import org.openkilda.model.FlowPathDirection 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.northbound.dto.v1.flows.PingInput import org.openkilda.northbound.dto.v2.flows.FlowEndpointV2 import org.openkilda.northbound.dto.v2.flows.FlowMirrorPointPayload @@ -36,6 +41,9 @@ import javax.inject.Provider @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 GrpcService grpc @@ -48,20 +56,21 @@ class LagPortSpec extends HealthCheckSpecification { Integer lagOffset = 2000 @Tidy - def "Able to CRUD LAG port on #sw.hwSwString"() { + def "Able to CRUD LAG port with lacp_reply=#lacpReply on #sw.hwSwString"() { given: "A switch" - def portsArrayCreate = topology.getAllowedPortsForSwitch(sw)[-2, -1] - def portsArrayUpdate = topology.getAllowedPortsForSwitch(sw)[1, -1] + def portsArrayCreate = topology.getAllowedPortsForSwitch(sw)[-2, -1] as Set + def portsArrayUpdate = topology.getAllowedPortsForSwitch(sw)[1, -1] as Set assert portsArrayCreate.sort() != portsArrayUpdate.sort() when: "Create a LAG" - def payloadCreate = new LagPortRequest(portNumbers: portsArrayCreate) + def payloadCreate = new LagPortRequest(portNumbers: portsArrayCreate, lacpReply: lacpReply) def createResponse = northboundV2.createLagLogicalPort(sw.dpId, payloadCreate) then: "Response reports successful creation of the LAG port" with(createResponse) { logicalPortNumber > 0 portNumbers.sort() == portsArrayCreate.sort() + it.lacpReply == lacpReply } def lagPort = createResponse.logicalPortNumber @@ -117,9 +126,7 @@ class LagPortSpec extends HealthCheckSpecification { with(northbound.validateSwitch(sw.dpId)) { it.verifyRuleSectionsAreEmpty(["missing", "excess", "misconfigured"]) it.verifyMeterSectionsAreEmpty() - it.logicalPorts.misconfigured.empty - it.logicalPorts.missing.empty - it.logicalPorts.excess.empty + it.verifyLogicalPortsSectionsAreEmpty() } when: "Delete the LAG port" @@ -142,7 +149,10 @@ class LagPortSpec extends HealthCheckSpecification { lagPort && !lagPortIsDeleted && northboundV2.deleteLagLogicalPort(sw.dpId, lagPort) where: - sw << getTopology().getActiveSwitches().unique(false) { it.hwSwString } + [sw, lacpReply] << [ + getTopology().getActiveSwitches().unique(false) { it.hwSwString }, // switches + [false, true] // lacp reply + ].combinations() } @Tidy @@ -586,6 +596,392 @@ class LagPortSpec extends HealthCheckSpecification { lagPort && northboundV2.deleteLagLogicalPort(sw.dpId, lagPort) } + @Tidy + 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 + + when: "Create a LAG port" + def createResponse = northboundV2.createLagLogicalPort( + sw.dpId, new LagPortRequest(portsArrayCreate, data.portLacpReply)) + + then: "Response reports successful creation of the LAG port" + with(createResponse) { + 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) + + when: "Delete the LAG port" + def deleteResponse = northboundV2.deleteLagLogicalPort(sw.dpId, portNumber) + + then: "Response reports successful delete of the LAG port" + with(deleteResponse) { + logicalPortNumber == portNumber + portNumbers.sort() == portsArrayCreate.sort() + lacpReply == data.portLacpReply + } + + and: "No LACP rules and meters on the switch" + assertSwitchHasCorrectLacpRulesAndMeters(sw, [], [LACP_COOKIE, getLagCookie(portNumber)], false) + + cleanup: "Remove all LAG ports" + deleteAllLagPorts(sw.dpId) + + where: + data << [ + [ + portLacpReply : false, + mustContainCookies : { int port -> [] }, + mustNotContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustContainLacpMeter : false + ], + [ + portLacpReply : true, + mustContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustNotContainCookies : { int port -> [] }, + mustContainLacpMeter : true + ] + ] + } + + @Tidy + 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 = northboundV2.createLagLogicalPort( + sw.dpId, new LagPortRequest(physicalPortsOfLag1, data.existingPortLacpReply)).logicalPortNumber + + when: "Create a LAG port" + def createResponse = northboundV2.createLagLogicalPort( + sw.dpId, new LagPortRequest(physicalPortsOfLag2, data.newPortLacpReply)) + + then: "Response reports successful creation of the LAG port" + with(createResponse) { + 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) + + when: "Delete created LAG port" + def deleteResponse = northboundV2.deleteLagLogicalPort(sw.dpId, portNumber2) + + then: "Response reports successful delete of the LAG port" + with(deleteResponse) { + logicalPortNumber == portNumber2 + 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) + } else { // Switch must not contain any LACP rules and meter + assertSwitchHasCorrectLacpRulesAndMeters(sw, + [], [LACP_COOKIE, getLagCookie(portNumber1), getLagCookie(portNumber2), ], false) + } + + cleanup: "Remove all LAG ports" + deleteAllLagPorts(sw.dpId) + + where: + data << [ + [ + 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)] }, + 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)] }, + 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)] }, + 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 -> [] }, + mustContainLacpMeter : true + ] + ] + } + + @Tidy + 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 + + and: "A LAG port" + def createResponse = northboundV2.createLagLogicalPort( + sw.dpId, new LagPortRequest(physicalPortsOfCreatedLag, data.oldlacpReply)) + with(createResponse) { + 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)) + + then: "Response reports successful update of the LAG port" + with(updateResponse) { + logicalPortNumber == portNumber + 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) + + cleanup: "Remove all LAG ports" + deleteAllLagPorts(sw.dpId) + + where: + data << [ + [ + description: "physical ports of LAG with disabled LACP", + oldlacpReply : false, + newlacpReply : false, + updatePorts: true, + mustContainCookies : { int port -> [] }, + mustNotContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustContainLacpMeter : false, + ], + [ + description: "physical ports of LAG with enabled LACP", + oldlacpReply : true, + newlacpReply : true, + updatePorts: false, + mustContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustNotContainCookies : { int port -> [] }, + mustContainLacpMeter : true, + ], + [ + description: "lacp_reply from false to true, physical ports are same", + oldlacpReply : false, + newlacpReply : true, + updatePorts: false, + mustContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustNotContainCookies : { int port -> [] }, + mustContainLacpMeter : true, + ], + [ + description: "lacp_reply from true to false, physical ports are same", + oldlacpReply : true, + newlacpReply : false, + updatePorts: false, + mustContainCookies : { int port -> [] }, + mustNotContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustContainLacpMeter : false, + ], + [ + description: "lacp_reply from false to true and update physical ports", + oldlacpReply : false, + newlacpReply : true, + updatePorts: true, + mustContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustNotContainCookies : { int port -> [] }, + mustContainLacpMeter : true, + ], + [ + description: "lacp_reply from true to false and update physical ports", + oldlacpReply : true, + newlacpReply : false, + updatePorts: true, + mustContainCookies : { int port -> [] }, + mustNotContainCookies : { int port -> [LACP_COOKIE, getLagCookie(port)] }, + mustContainLacpMeter : false, + ] + ] + } + + @Tidy + 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 + + and: "LAG port 1" + def portNumber1 = northboundV2.createLagLogicalPort( + sw.dpId, new LagPortRequest(physicalPortsOfLag1, data.existingPortLacpReply)).logicalPortNumber + + and: "LAG port 2" + def createResponse = northboundV2.createLagLogicalPort( + sw.dpId, new LagPortRequest(physicalPortsOfCreatedLag2, data.oldlacpReply)) + with(createResponse) { + 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)) + + then: "Response reports successful update of the LAG port" + with(updateResponse) { + logicalPortNumber == portNumber2 + 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) + + cleanup: "Remove all LAG ports" + deleteAllLagPorts(sw.dpId) + + where: + data << [ + [ + description: "physical ports of LAG with disabled LACP", + existingPortLacpReply : false, + oldlacpReply : false, + newlacpReply : false, + updatePorts: true, + mustContainCookies : { int port1, port2 -> [] }, + mustNotContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1), getLagCookie(port2)] }, + mustContainLacpMeter : false, + ], + [ + description: "physical ports of LAG with enabled LACP", + existingPortLacpReply : false, + oldlacpReply : true, + newlacpReply : true, + updatePorts: true, + mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port2)] }, + mustNotContainCookies : { int port1, port2 -> [getLagCookie(port1)] }, + mustContainLacpMeter : true, + ], + [ + description: "lacp_reply from false to true", + existingPortLacpReply : false, + oldlacpReply : false, + newlacpReply : true, + updatePorts: false, + mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port2)] }, + mustNotContainCookies : { int port1, port2 -> [getLagCookie(port1)] }, + mustContainLacpMeter : true, + ], + [ + description: "lacp_reply from true to false", + existingPortLacpReply : false, + oldlacpReply : true, + newlacpReply : false, + updatePorts: false, + mustContainCookies : { int port1, port2 -> [] }, + mustNotContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1), getLagCookie(port2)] }, + mustContainLacpMeter : false, + ], + [ + description: "physical ports of LAG with disabled LACP", + existingPortLacpReply : true, + oldlacpReply : false, + newlacpReply : false, + updatePorts: true, + mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1)] }, + mustNotContainCookies : { int port1, port2 -> [getLagCookie(port2)] }, + mustContainLacpMeter : true, + ], + [ + description: "physical ports of LAG with enabled LACP", + existingPortLacpReply : true, + oldlacpReply : true, + newlacpReply : true, + updatePorts: true, + mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1), getLagCookie(port2)] }, + mustNotContainCookies : { int port1, port2 -> [] }, + mustContainLacpMeter : true, + ], + [ + description: "lacp_reply from false to true", + existingPortLacpReply : true, + oldlacpReply : false, + newlacpReply : true, + updatePorts: false, + mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1), getLagCookie(port2)] }, + mustNotContainCookies : { int port1, port2 -> [] }, + mustContainLacpMeter : true, + ], + [ + description: "lacp_reply from true to false", + existingPortLacpReply : true, + oldlacpReply : true, + newlacpReply : false, + updatePorts: false, + mustContainCookies : { int port1, port2 -> [LACP_COOKIE, getLagCookie(port1)] }, + mustNotContainCookies : { int port1, port2 -> [getLagCookie(port2)] }, + mustContainLacpMeter : true, + ] + ] + } + + private void assertSwitchHasCorrectLacpRulesAndMeters( + Switch sw, mustContainCookies, mustNotContainsCookies, mustContainLacpMeter) { + // validate switch + with(northbound.validateSwitch(sw.dpId)) { + it.verifyRuleSectionsAreEmpty(["missing", "excess", "misconfigured"]) + it.verifyMeterSectionsAreEmpty() + it.verifyLogicalPortsSectionsAreEmpty() + } + + // 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 + } + } + @Tidy def "Unable decrease bandwidth on LAG port lower than connected flows bandwidth sum"() { given: "Flows on a LAG port with switch ports" @@ -641,8 +1037,10 @@ class LagPortSpec extends HealthCheckSpecification { logicalPortNumber == lagPort portNumbers.sort() == portsArray.sort() } + } - + def getLagCookie(portNumber) { + new PortColourCookie(CookieType.LACP_REPLY_INPUT, portNumber).toString() } void deleteAllLagPorts(SwitchId switchId) { diff --git a/src-java/testing/test-library/src/main/java/org/openkilda/testing/tools/KafkaUtils.java b/src-java/testing/test-library/src/main/java/org/openkilda/testing/tools/KafkaUtils.java index 4e95c27e17f..d35e63828ac 100644 --- a/src-java/testing/test-library/src/main/java/org/openkilda/testing/tools/KafkaUtils.java +++ b/src-java/testing/test-library/src/main/java/org/openkilda/testing/tools/KafkaUtils.java @@ -15,27 +15,18 @@ package org.openkilda.testing.tools; -import static java.lang.String.format; - -import org.openkilda.floodlight.api.request.rulemanager.FlowCommand; -import org.openkilda.floodlight.api.request.rulemanager.GroupCommand; import org.openkilda.floodlight.api.request.rulemanager.InstallSpeakerCommandsRequest; -import org.openkilda.floodlight.api.request.rulemanager.MeterCommand; import org.openkilda.floodlight.api.request.rulemanager.OfCommand; import org.openkilda.messaging.AbstractMessage; import org.openkilda.messaging.MessageContext; import org.openkilda.model.FlowPathDirection; import org.openkilda.model.cookie.Cookie; import org.openkilda.model.cookie.FlowSegmentCookie; -import org.openkilda.rulemanager.FlowSpeakerData; -import org.openkilda.rulemanager.GroupSpeakerData; -import org.openkilda.rulemanager.MeterSpeakerData; import org.openkilda.rulemanager.SpeakerData; import java.util.Collections; import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; /** * Helper class to build kafka messages. @@ -56,27 +47,10 @@ public static AbstractMessage buildMessage(List speakerData) { .messageContext(new MessageContext()) .switchId(speakerData.get(0).getSwitchId()) .commandId(UUID.randomUUID()) - .commands(toCommands(speakerData)) + .commands(OfCommand.toOfCommands(speakerData)) .build(); } - private static List toCommands(List speakerData) { - return speakerData.stream() - .map(KafkaUtils::toCommand) - .collect(Collectors.toList()); - } - - private static OfCommand toCommand(SpeakerData speakerData) { - if (speakerData instanceof FlowSpeakerData) { - return new FlowCommand((FlowSpeakerData) speakerData); - } else if (speakerData instanceof MeterSpeakerData) { - return new MeterCommand((MeterSpeakerData) speakerData); - } else if (speakerData instanceof GroupSpeakerData) { - return new GroupCommand((GroupSpeakerData) speakerData); - } - throw new IllegalStateException(format("Unknown speaker data type %s", speakerData)); - } - /** * Build flow segment cookie. */