From f48f3d4b46c9d69f31d271ab276ba90f10628540 Mon Sep 17 00:00:00 2001 From: Dmitriy Bogun Date: Tue, 20 Sep 2022 21:42:28 +0300 Subject: [PATCH 1/7] Fix stats for y flow ingress endpoint --- .../messaging/info/stats/FlowStatsEntry.java | 34 ++++++------------- .../converter/OfFlowStatsMapper.java | 4 ++- .../rulemanager/RulePriorityAnalyzer.java | 33 ++++++++++++++++++ .../stats-storm-topology/build.gradle | 1 + .../FlowEndpointStatsEntryHandler.java | 4 +++ 5 files changed, 52 insertions(+), 24 deletions(-) create mode 100644 src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RulePriorityAnalyzer.java diff --git a/src-java/floodlight-service/floodlight-api/src/main/java/org/openkilda/messaging/info/stats/FlowStatsEntry.java b/src-java/floodlight-service/floodlight-api/src/main/java/org/openkilda/messaging/info/stats/FlowStatsEntry.java index e8ac3741c49..4c737a98632 100644 --- a/src-java/floodlight-service/floodlight-api/src/main/java/org/openkilda/messaging/info/stats/FlowStatsEntry.java +++ b/src-java/floodlight-service/floodlight-api/src/main/java/org/openkilda/messaging/info/stats/FlowStatsEntry.java @@ -15,18 +15,23 @@ package org.openkilda.messaging.info.stats; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; import java.io.Serializable; /** * TODO: add javadoc. */ +@Getter public class FlowStatsEntry implements Serializable { - @JsonProperty private int tableId; + @JsonProperty + private int priority; + @JsonProperty private long cookie; @@ -42,13 +47,16 @@ public class FlowStatsEntry implements Serializable { @JsonProperty private int outPort; + @JsonCreator public FlowStatsEntry(@JsonProperty("tableId") int tableId, + @JsonProperty("priority") int priority, @JsonProperty("cookie") long cookie, @JsonProperty("packetCount") long packetCount, @JsonProperty("byteCount") long byteCount, @JsonProperty("inPort") int inPort, @JsonProperty("outPort") int outPort) { this.tableId = tableId; + this.priority = priority; this.cookie = cookie; this.packetCount = packetCount; this.byteCount = byteCount; @@ -56,27 +64,7 @@ public FlowStatsEntry(@JsonProperty("tableId") int tableId, this.outPort = outPort; } - public int getTableId() { - return tableId; - } - - public long getCookie() { - return cookie; - } - - public long getPacketCount() { - return packetCount; - } - - public long getByteCount() { - return byteCount; - } - - public int getInPort() { - return inPort; - } - - public int getOutPort() { - return outPort; + public FlowStatsEntry(int tableId, long cookie, long packetCount, long byteCount, int inPort, int outPort) { + this(tableId, -1, cookie, packetCount, byteCount, inPort, outPort); } } diff --git a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/converter/OfFlowStatsMapper.java b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/converter/OfFlowStatsMapper.java index 134d5f7ef21..ea6160a10f4 100644 --- a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/converter/OfFlowStatsMapper.java +++ b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/converter/OfFlowStatsMapper.java @@ -287,7 +287,9 @@ public FlowStatsData toFlowStatsData(List data, SwitchId switc */ public FlowStatsEntry toFlowStatsEntry(OFFlowStatsEntry entry) { try { - return new FlowStatsEntry(entry.getTableId().getValue(), + return new FlowStatsEntry( + entry.getTableId().getValue(), + entry.getPriority(), entry.getCookie().getValue(), entry.getPacketCount().getValue(), entry.getByteCount().getValue(), diff --git a/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RulePriorityAnalyzer.java b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RulePriorityAnalyzer.java new file mode 100644 index 00000000000..e9906357686 --- /dev/null +++ b/src-java/rule-manager/rule-manager-implementation/src/main/java/org/openkilda/rulemanager/RulePriorityAnalyzer.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.rulemanager; + +import org.openkilda.rulemanager.Constants.Priority; + +public final class RulePriorityAnalyzer { + /** + * Check is the priority value match the priority of generic flow endpoint rules. + */ + public static boolean isGenericFlowEndpoint(int priority) { + return priority == Priority.FLOW_PRIORITY + || priority == Priority.DEFAULT_FLOW_PRIORITY + || priority == Priority.DOUBLE_VLAN_FLOW_PRIORITY; + } + + private RulePriorityAnalyzer() { + // deny object creation + } +} diff --git a/src-java/stats-topology/stats-storm-topology/build.gradle b/src-java/stats-topology/stats-storm-topology/build.gradle index 08ac74df2b6..ddb11d26536 100644 --- a/src-java/stats-topology/stats-storm-topology/build.gradle +++ b/src-java/stats-topology/stats-storm-topology/build.gradle @@ -16,6 +16,7 @@ dependencies { implementation project(':floodlight-api') implementation project(':grpc-api') implementation project(':blue-green') + implementation project(':rule-manager-implementation') runtimeOnly project(':kilda-persistence-orientdb') runtimeOnly project(':kilda-persistence-hibernate') aspect project(':kilda-persistence-api') diff --git a/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/FlowEndpointStatsEntryHandler.java b/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/FlowEndpointStatsEntryHandler.java index 30ae0cb8ec8..d59f7602acb 100644 --- a/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/FlowEndpointStatsEntryHandler.java +++ b/src-java/stats-topology/stats-storm-topology/src/main/java/org/openkilda/wfm/topology/stats/service/FlowEndpointStatsEntryHandler.java @@ -20,6 +20,7 @@ import org.openkilda.model.cookie.Cookie; import org.openkilda.model.cookie.CookieBase.CookieType; import org.openkilda.model.cookie.FlowSegmentCookie; +import org.openkilda.rulemanager.RulePriorityAnalyzer; import org.openkilda.wfm.share.utils.MetricFormatter; import org.openkilda.wfm.topology.stats.bolts.metrics.FlowDirectionHelper.Direction; import org.openkilda.wfm.topology.stats.model.CommonFlowDescriptor; @@ -83,6 +84,9 @@ public void handleStatsEntry(YFlowDescriptor descriptor) { @Override public void handleStatsEntry(YFlowSubDescriptor descriptor) { + if (RulePriorityAnalyzer.isGenericFlowEndpoint(statsEntry.getPriority())) { + return; // we must ignore stats from sub flow endpoints rule + } TagsFormatter tags = initTags(true); tags.addFlowIdTag(descriptor.getSubFlowId()); tags.addYFlowIdTag(descriptor.getYFlowId()); From 29ee68a13a2986e63f0dbce38379573964139fe9 Mon Sep 17 00:00:00 2001 From: Dmitriy Bogun Date: Fri, 7 Oct 2022 12:27:46 +0300 Subject: [PATCH 2/7] Implement true Y-flow sync Replace "forced" Y-flow reroute operation with "true" Y-flow sync operation. --- .../crud/sync/y-flow-sync-fsm.png | Bin 0 -> 327351 bytes .../crud/sync/y-flow-sync-fsm.puml | 63 ++++ .../wfm/share/utils/AbstractBaseFsm.java | 3 +- .../command/yflow/YFlowRerouteRequest.java | 2 +- .../wfm/topology/flowhs/FlowHsTopology.java | 72 ++-- .../flowhs/bolts/FlowSyncHubBolt.java | 234 +----------- .../wfm/topology/flowhs/bolts/RouterBolt.java | 27 +- .../flowhs/bolts/SyncHubBoltBase.java | 287 +++++++++++++++ .../flowhs/bolts/YFlowSyncHubBolt.java | 78 ++++ .../fsm/common/FlowPathSwappingFsm.java | 4 +- .../flowhs/fsm/common/FlowProcessingFsm.java | 5 +- .../FlowProcessingWithHistorySupportFsm.java | 4 +- .../common/NbTrackableFlowProcessingFsm.java | 4 +- .../flowhs/fsm/common/YFlowProcessingFsm.java | 4 +- .../YFlowRuleManagerProcessingAction.java | 39 +- .../flowhs/fsm/path/FlowPathFsmBase.java | 6 +- .../CreateSyncHandlersAction.java | 41 +-- .../sync/EmitYRulesSyncRequestsAction.java | 50 +++ .../fsm/sync/FailedCompleteActionBase.java | 103 ++++++ ...ion.java => FlowFailedCompleteAction.java} | 56 +-- .../flowhs/fsm/sync/FlowSyncContext.java | 3 + .../topology/flowhs/fsm/sync/FlowSyncFsm.java | 162 ++------ .../flowhs/fsm/sync/FlowSyncSetupAction.java | 44 +++ ...ava => FlowSyncSuccessCompleteAction.java} | 40 +- .../PathOperationResponseAction.java | 37 +- .../fsm/sync/SuccessCompleteActionBase.java | 64 ++++ .../topology/flowhs/fsm/sync/SyncFsmBase.java | 170 +++++++++ ...upAction.java => SyncSetupActionBase.java} | 52 +-- .../fsm/sync/YFlowFailedCompleteAction.java | 103 ++++++ .../flowhs/fsm/sync/YFlowSyncFsm.java | 345 ++++++++++++++++++ .../flowhs/fsm/sync/YFlowSyncSetupAction.java | 70 ++++ .../sync/YFlowSyncSuccessCompleteAction.java | 88 +++++ .../actions/OnSubFlowAllocatedAction.java | 65 +--- .../flowhs/model/yflow/YFlowPaths.java | 36 ++ .../flowhs/service/FlowSyncService.java | 70 +--- .../flowhs/service/YFlowSyncService.java | 159 ++++++++ .../service/common/SyncServiceBase.java | 102 ++++++ .../flowhs/service/path/FlowPathService.java | 2 +- .../flowhs/utils/HandlerResponseMapping.java | 54 +++ .../flowhs/utils/YFlowRuleManagerAdapter.java | 87 +++++ .../wfm/topology/flowhs/utils/YFlowUtils.java | 108 ++++++ .../flows/yflows/YFlowValidationSpec.groovy | 2 +- 42 files changed, 2261 insertions(+), 684 deletions(-) create mode 100644 docs/design/hub-and-spoke/crud/sync/y-flow-sync-fsm.png create mode 100644 docs/design/hub-and-spoke/crud/sync/y-flow-sync-fsm.puml create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/bolts/SyncHubBoltBase.java create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/bolts/YFlowSyncHubBolt.java rename src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/{actions => }/CreateSyncHandlersAction.java (78%) create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/EmitYRulesSyncRequestsAction.java create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FailedCompleteActionBase.java rename src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/{actions/FailedCompleteAction.java => FlowFailedCompleteAction.java} (56%) create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowSyncSetupAction.java rename src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/{actions/SuccessCompleteAction.java => FlowSyncSuccessCompleteAction.java} (54%) rename src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/{actions => }/PathOperationResponseAction.java (59%) create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/SuccessCompleteActionBase.java create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/SyncFsmBase.java rename src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/{actions/FlowSyncSetupAction.java => SyncSetupActionBase.java} (64%) create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/YFlowFailedCompleteAction.java create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/YFlowSyncFsm.java create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/YFlowSyncSetupAction.java create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/YFlowSyncSuccessCompleteAction.java create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/model/yflow/YFlowPaths.java create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/YFlowSyncService.java create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/common/SyncServiceBase.java create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/utils/HandlerResponseMapping.java create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/utils/YFlowRuleManagerAdapter.java create mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/utils/YFlowUtils.java diff --git a/docs/design/hub-and-spoke/crud/sync/y-flow-sync-fsm.png b/docs/design/hub-and-spoke/crud/sync/y-flow-sync-fsm.png new file mode 100644 index 0000000000000000000000000000000000000000..30c7e4f950e088260a927e897eb1718444947467 GIT binary patch literal 327351 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 z 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

-yB{di)^m494DS!Mi{?5~Bp>&*NDC-~4b_ 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ℜJ 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

*z``$6S)4(apg-J$D{$tGMWs&?4KfAx26d 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 SQ?~79xFacZGxf&97yk#5n}*5& literal 0 HcmV?d00001 diff --git a/docs/design/hub-and-spoke/crud/sync/y-flow-sync-fsm.puml b/docs/design/hub-and-spoke/crud/sync/y-flow-sync-fsm.puml new file mode 100644 index 00000000000..086e4671e9c --- /dev/null +++ b/docs/design/hub-and-spoke/crud/sync/y-flow-sync-fsm.puml @@ -0,0 +1,63 @@ +@startuml +title Y-Flow sync FSM state diagram + +[*] -> SETUP +SETUP: enter / fire "error" if y-flow is IN_PROGRESS status +SETUP: enter / for each subflow fire "error" if subflow is IN_PROGRESS status +SETUP: enter / set y-flow status to IN_PROGRESS +SETUP: enter / for each subflow set flow status to IN_PROGRESS +SETUP: enter / for each subflow apply postponed flow changes +SETUP --> SYNC: next +SETUP --> COMMIT_ERROR: error + +SYNC: enter / for each subflow launch main path install operation +SYNC: enter / [have protected path] for each subflow launch protected path install operation +SYNC --> SYNC_FAIL: sync_fail +SYNC --> SYNC_Y_RULES: guard_passed +SYNC --> CANCEL: error +SYNC --> CANCEL: timeout / report global timeout +SYNC: path_operation_response / handle path operation response +SYNC: path_operation_response / fire "sync_fail" on failed path response +SYNC: path_operation_response / fire "guard_passed" if no more pending path operations + +SYNC_Y_RULES: enter / emit y-flow rules speaker install commands +SYNC_Y_RULES -> COMMIT_SUCCESS: guard_passed +SYNC_Y_RULES -> COMMIT_ERROR: timeout / report global timeout +SYNC_Y_RULES -> SYNC_Y_RULES_FAIL: speaker_error_response +SYNC_Y_RULES: speaker_response / report speaker command success +SYNC_Y_RULES: speaker_response / fire "guard_passed" if no more pending speaker commands + +CANCEL: enter / request cancel path operation for all pending path operations +CANCEL --> COMMIT_ERROR: guard_passed +CANCEL: path_operation_response / handle path operation response +CANCEL: path_operation_response / fire "guard_passed" if no more pending path operations +CANCEL: error / fire "guard_passed" if no pending path operations +CANCEL: timeout / report global timeout + +SYNC_FAIL: enter / fire "guard_passed" if no pending path operations +SYNC_FAIL --> COMMIT_ERROR: guard_passed +SYNC_FAIL --> CANCEL: error +SYNC_FAIL --> CANCEL: timeout / report global timeout +SYNC_FAIL: path_operation_response / handle path operation response +SYNC_FAIL: path_operation_response / fire "guard_passed" if no more pending path operations + +SYNC_Y_RULES_FAIL: enter / report speaker command error +SYNC_Y_RULES_FAIL: enter / fire "guard_passed" if no pending speaker commands +SYNC_Y_RULES_FAIL -> COMMIT_ERROR: guard_passed | timeout +SYNC_Y_RULES_FAIL: speaker_response / report speaker command success +SYNC_Y_RULES_FAIL: speaker_response / fire "guard_passed" if no more pending speaker commands +SYNC_Y_RULES_FAIL: speaker_error_response / report speaker command error +SYNC_Y_RULES_FAIL: speaker_error_response / fire "guard_passed" if no more pending speaker commands + +COMMIT_SUCCESS: enter / update flow status +COMMIT_SUCCESS: enter / send success response +COMMIT_SUCCESS --> FINISHED: next +COMMIT_SUCCESS --> COMMIT_ERROR: error + +COMMIT_ERROR: enter / update flow status +COMMIT_ERROR: enter / send failed response +COMMIT_ERROR --> FINISHED_WITH_ERROR: next + +FINISHED --> [*] +FINISHED_WITH_ERROR --> [*] +@enduml diff --git a/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/utils/AbstractBaseFsm.java b/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/utils/AbstractBaseFsm.java index 33046d4f3b7..6586fe63955 100644 --- a/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/utils/AbstractBaseFsm.java +++ b/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/utils/AbstractBaseFsm.java @@ -17,12 +17,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.squirrelframework.foundation.fsm.StateMachine; import org.squirrelframework.foundation.fsm.StateMachineStatus; import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine; -public abstract class AbstractBaseFsm, S, E, C> +public abstract class AbstractBaseFsm, S, E, C> extends AbstractStateMachine { protected transient Logger log = makeLog(); diff --git a/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowRerouteRequest.java b/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowRerouteRequest.java index ca6b7b98e48..4b208c84fa2 100644 --- a/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowRerouteRequest.java +++ b/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowRerouteRequest.java @@ -40,7 +40,7 @@ public class YFlowRerouteRequest extends CommandData { String yFlowId; Set affectedIsl; - boolean force; + boolean force; // TODO: remove String reason; boolean ignoreBandwidth; diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/FlowHsTopology.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/FlowHsTopology.java index 7518dd5f053..659e65ac2f8 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/FlowHsTopology.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/FlowHsTopology.java @@ -72,6 +72,7 @@ import org.openkilda.wfm.topology.flowhs.bolts.RouterBolt; import org.openkilda.wfm.topology.flowhs.bolts.SpeakerWorkerBolt; import org.openkilda.wfm.topology.flowhs.bolts.SpeakerWorkerForDumpsBolt; +import org.openkilda.wfm.topology.flowhs.bolts.SyncHubBoltBase; import org.openkilda.wfm.topology.flowhs.bolts.YFlowCreateHubBolt; import org.openkilda.wfm.topology.flowhs.bolts.YFlowCreateHubBolt.YFlowCreateConfig; import org.openkilda.wfm.topology.flowhs.bolts.YFlowDeleteHubBolt; @@ -82,6 +83,7 @@ import org.openkilda.wfm.topology.flowhs.bolts.YFlowReadBolt.YFlowReadConfig; import org.openkilda.wfm.topology.flowhs.bolts.YFlowRerouteHubBolt; import org.openkilda.wfm.topology.flowhs.bolts.YFlowRerouteHubBolt.YFlowRerouteConfig; +import org.openkilda.wfm.topology.flowhs.bolts.YFlowSyncHubBolt; import org.openkilda.wfm.topology.flowhs.bolts.YFlowUpdateHubBolt; import org.openkilda.wfm.topology.flowhs.bolts.YFlowUpdateHubBolt.YFlowUpdateConfig; import org.openkilda.wfm.topology.flowhs.bolts.YFlowValidationHubBolt; @@ -127,6 +129,7 @@ public StormTopology createTopology() { yFlowUpdateHub(tb, persistenceManager); yFlowRerouteHub(tb, persistenceManager); yFlowDeleteHub(tb, persistenceManager); + yFlowSyncHub(tb, persistenceManager); yFlowReadBolt(tb, persistenceManager); yFlowValidationHub(tb, persistenceManager); yFlowPathSwapHub(tb, persistenceManager); @@ -176,6 +179,7 @@ private void zkBolt(TopologyBuilder topologyBuilder) { ComponentId.YFLOW_CREATE_HUB.name(), ComponentId.YFLOW_UPDATE_HUB.name(), ComponentId.YFLOW_REROUTE_HUB.name(), + YFlowSyncHubBolt.BOLT_ID, ComponentId.YFLOW_DELETE_HUB.name(), ComponentId.YFLOW_READ_BOLT.name(), ComponentId.YFLOW_VALIDATION_HUB.name(), @@ -184,7 +188,7 @@ private void zkBolt(TopologyBuilder topologyBuilder) { .allGrouping(ComponentId.FLOW_CREATE_HUB.name(), ZkStreams.ZK.toString()) .allGrouping(ComponentId.FLOW_UPDATE_HUB.name(), ZkStreams.ZK.toString()) .allGrouping(ComponentId.FLOW_DELETE_HUB.name(), ZkStreams.ZK.toString()) - .allGrouping(FlowSyncHubBolt.BOLT_ID, ZkStreams.ZK.toString()) + .allGrouping(FlowSyncHubBolt.BOLT_ID, FlowSyncHubBolt.STREAM_ZK) .allGrouping(ComponentId.FLOW_PATH_SWAP_HUB.name(), ZkStreams.ZK.toString()) .allGrouping(ComponentId.FLOW_REROUTE_HUB.name(), ZkStreams.ZK.toString()) .allGrouping(ComponentId.FLOW_SWAP_ENDPOINTS_HUB.name(), ZkStreams.ZK.toString()) @@ -195,6 +199,7 @@ private void zkBolt(TopologyBuilder topologyBuilder) { .allGrouping(ComponentId.YFLOW_CREATE_HUB.name(), ZkStreams.ZK.toString()) .allGrouping(ComponentId.YFLOW_UPDATE_HUB.name(), ZkStreams.ZK.toString()) .allGrouping(ComponentId.YFLOW_REROUTE_HUB.name(), ZkStreams.ZK.toString()) + .allGrouping(YFlowSyncHubBolt.BOLT_ID, YFlowSyncHubBolt.STREAM_ZK) .allGrouping(ComponentId.YFLOW_DELETE_HUB.name(), ZkStreams.ZK.toString()) .allGrouping(ComponentId.YFLOW_READ_BOLT.name(), ZkStreams.ZK.toString()) .allGrouping(ComponentId.YFLOW_VALIDATION_HUB.name(), ZkStreams.ZK.toString()) @@ -341,19 +346,8 @@ private void flowDeleteHub(TopologyBuilder topologyBuilder, PersistenceManager p } private void flowSyncHub(TopologyBuilder topologyBuilder, PersistenceManager persistenceManager) { - int hubTimeout = (int) TimeUnit.SECONDS.toMillis(topologyConfig.getSyncHubTimeoutSeconds()); - - FlowSyncHubBolt.FlowSyncConfig config = FlowSyncHubBolt.FlowSyncConfig.flowSyncBuilder() - .speakerCommandRetriesLimit(topologyConfig.getSyncSpeakerCommandRetries()) - .requestSenderComponent(ComponentId.FLOW_ROUTER_BOLT.name()) - .workerComponent(ComponentId.SPEAKER_WORKER.name()) - .lifeCycleEventComponent(ZooKeeperSpout.SPOUT_ID) - .timeoutMs(hubTimeout) - .autoAck(true) - .build(); - FlowResourcesConfig flowResourcesConfig = configurationProvider.getConfiguration(FlowResourcesConfig.class); - FlowSyncHubBolt hubBolt = new FlowSyncHubBolt(config, persistenceManager, flowResourcesConfig); + FlowSyncHubBolt hubBolt = new FlowSyncHubBolt(newSyncHubConfig(), persistenceManager, flowResourcesConfig); declareBolt(topologyBuilder, hubBolt, FlowSyncHubBolt.BOLT_ID) .fieldsGrouping(ComponentId.FLOW_ROUTER_BOLT.name(), Stream.ROUTER_TO_FLOW_SYNC_HUB.name(), FLOW_FIELD) .directGrouping(ComponentId.SPEAKER_WORKER.name(), Stream.SPEAKER_WORKER_TO_HUB.name()) @@ -591,6 +585,18 @@ private void yFlowDeleteHub(TopologyBuilder topologyBuilder, PersistenceManager .directGrouping(CoordinatorBolt.ID); } + private void yFlowSyncHub(TopologyBuilder topologyBuilder, PersistenceManager persistenceManager) { + FlowResourcesConfig flowResourcesConfig = configurationProvider.getConfiguration(FlowResourcesConfig.class); + RuleManagerConfig ruleManagerConfig = configurationProvider.getConfiguration(RuleManagerConfig.class); + YFlowSyncHubBolt hubBolt = new YFlowSyncHubBolt( + newSyncHubConfig(), persistenceManager, flowResourcesConfig, ruleManagerConfig); + declareBolt(topologyBuilder, hubBolt, YFlowSyncHubBolt.BOLT_ID) + .fieldsGrouping(ComponentId.FLOW_ROUTER_BOLT.name(), Stream.ROUTER_TO_YFLOW_SYNC_HUB.name(), FLOW_FIELD) + .directGrouping(ComponentId.SPEAKER_WORKER.name(), Stream.SPEAKER_WORKER_TO_HUB.name()) + .allGrouping(ZooKeeperSpout.SPOUT_ID) + .directGrouping(CoordinatorBolt.ID); + } + private void yFlowReadBolt(TopologyBuilder topologyBuilder, PersistenceManager persistenceManager) { YFlowReadConfig yFlowReadConfig = YFlowReadConfig.builder() .readOperationRetriesLimit(topologyConfig.getYFlowReadRetriesLimit()) @@ -676,6 +682,7 @@ private void speakerWorkers(TopologyBuilder topologyBuilder) { .hubComponent(ComponentId.YFLOW_UPDATE_HUB.name()) .hubComponent(ComponentId.YFLOW_REROUTE_HUB.name()) .hubComponent(ComponentId.YFLOW_DELETE_HUB.name()) + .hubComponent(YFlowSyncHubBolt.BOLT_ID) .hubComponent(ComponentId.YFLOW_PATH_SWAP_HUB.name()) .streamToHub(SPEAKER_WORKER_TO_HUB.name()) .build()); @@ -686,7 +693,7 @@ private void speakerWorkers(TopologyBuilder topologyBuilder) { .fieldsGrouping(ComponentId.FLOW_PATH_SWAP_HUB.name(), Stream.HUB_TO_SPEAKER_WORKER.name(), FIELDS_KEY) .fieldsGrouping(ComponentId.FLOW_REROUTE_HUB.name(), Stream.HUB_TO_SPEAKER_WORKER.name(), FIELDS_KEY) .fieldsGrouping(ComponentId.FLOW_DELETE_HUB.name(), Stream.HUB_TO_SPEAKER_WORKER.name(), FIELDS_KEY) - .fieldsGrouping(FlowSyncHubBolt.BOLT_ID, Stream.HUB_TO_SPEAKER_WORKER.name(), FIELDS_KEY) + .fieldsGrouping(FlowSyncHubBolt.BOLT_ID, FlowSyncHubBolt.STREAM_WORKER, FIELDS_KEY) .fieldsGrouping(ComponentId.FLOW_CREATE_MIRROR_POINT_HUB.name(), Stream.HUB_TO_SPEAKER_WORKER.name(), FIELDS_KEY) .fieldsGrouping(ComponentId.FLOW_DELETE_MIRROR_POINT_HUB.name(), Stream.HUB_TO_SPEAKER_WORKER.name(), @@ -694,6 +701,7 @@ private void speakerWorkers(TopologyBuilder topologyBuilder) { .fieldsGrouping(ComponentId.YFLOW_CREATE_HUB.name(), Stream.HUB_TO_SPEAKER_WORKER.name(), FIELDS_KEY) .fieldsGrouping(ComponentId.YFLOW_UPDATE_HUB.name(), Stream.HUB_TO_SPEAKER_WORKER.name(), FIELDS_KEY) .fieldsGrouping(ComponentId.YFLOW_REROUTE_HUB.name(), Stream.HUB_TO_SPEAKER_WORKER.name(), FIELDS_KEY) + .fieldsGrouping(YFlowSyncHubBolt.BOLT_ID, YFlowSyncHubBolt.STREAM_WORKER, FIELDS_KEY) .fieldsGrouping(ComponentId.YFLOW_DELETE_HUB.name(), Stream.HUB_TO_SPEAKER_WORKER.name(), FIELDS_KEY) .fieldsGrouping(ComponentId.YFLOW_PATH_SWAP_HUB.name(), Stream.HUB_TO_SPEAKER_WORKER.name(), FIELDS_KEY) .directGrouping(CoordinatorBolt.ID); @@ -765,6 +773,7 @@ private void coordinator(TopologyBuilder topologyBuilder) { .fieldsGrouping(ComponentId.YFLOW_CREATE_HUB.name(), CoordinatorBolt.INCOME_STREAM, FIELDS_KEY) .fieldsGrouping(ComponentId.YFLOW_UPDATE_HUB.name(), CoordinatorBolt.INCOME_STREAM, FIELDS_KEY) .fieldsGrouping(ComponentId.YFLOW_DELETE_HUB.name(), CoordinatorBolt.INCOME_STREAM, FIELDS_KEY) + .fieldsGrouping(YFlowSyncHubBolt.BOLT_ID, CoordinatorBolt.INCOME_STREAM, FIELDS_KEY) .fieldsGrouping(ComponentId.YFLOW_VALIDATION_HUB.name(), CoordinatorBolt.INCOME_STREAM, FIELDS_KEY) .fieldsGrouping(ComponentId.YFLOW_VALIDATION_SPEAKER_WORKER.name(), CoordinatorBolt.INCOME_STREAM, FIELDS_KEY) @@ -779,7 +788,7 @@ private void northboundOutput(TopologyBuilder topologyBuilder) { .shuffleGrouping(ComponentId.FLOW_PATH_SWAP_HUB.name(), Stream.HUB_TO_NB_RESPONSE_SENDER.name()) .shuffleGrouping(ComponentId.FLOW_REROUTE_HUB.name(), Stream.HUB_TO_NB_RESPONSE_SENDER.name()) .shuffleGrouping(ComponentId.FLOW_DELETE_HUB.name(), Stream.HUB_TO_NB_RESPONSE_SENDER.name()) - .shuffleGrouping(ComponentId.FLOW_SYNC_HUB.name(), Stream.HUB_TO_NB_RESPONSE_SENDER.name()) + .shuffleGrouping(FlowSyncHubBolt.BOLT_ID, FlowSyncHubBolt.STREAM_NB_RESPONSE) .shuffleGrouping(ComponentId.FLOW_SWAP_ENDPOINTS_HUB.name(), Stream.HUB_TO_NB_RESPONSE_SENDER.name()) .shuffleGrouping(ComponentId.FLOW_CREATE_MIRROR_POINT_HUB.name(), Stream.HUB_TO_NB_RESPONSE_SENDER.name()) @@ -793,6 +802,7 @@ private void northboundOutput(TopologyBuilder topologyBuilder) { .shuffleGrouping(ComponentId.YFLOW_REROUTE_HUB.name(), Stream.HUB_TO_NB_RESPONSE_SENDER.name()) .shuffleGrouping(ComponentId.YFLOW_DELETE_HUB.name(), Stream.HUB_TO_NB_RESPONSE_SENDER.name()) + .shuffleGrouping(YFlowSyncHubBolt.BOLT_ID, YFlowSyncHubBolt.STREAM_NB_RESPONSE) .shuffleGrouping(ComponentId.YFLOW_READ_BOLT.name()) .shuffleGrouping(ComponentId.YFLOW_VALIDATION_HUB.name(), Stream.HUB_TO_NB_RESPONSE_SENDER.name()) .shuffleGrouping(ComponentId.YFLOW_PATH_SWAP_HUB.name(), Stream.HUB_TO_NB_RESPONSE_SENDER.name()); @@ -812,13 +822,14 @@ private void pingOutput(TopologyBuilder topologyBuilder) { .shuffleGrouping(ComponentId.FLOW_CREATE_HUB.name(), Stream.HUB_TO_PING_SENDER.name()) .shuffleGrouping(ComponentId.FLOW_UPDATE_HUB.name(), Stream.HUB_TO_PING_SENDER.name()) .shuffleGrouping(ComponentId.FLOW_DELETE_HUB.name(), Stream.HUB_TO_PING_SENDER.name()) - .shuffleGrouping(ComponentId.FLOW_SYNC_HUB.name(), Stream.HUB_TO_PING_SENDER.name()) + .shuffleGrouping(FlowSyncHubBolt.BOLT_ID, FlowSyncHubBolt.STREAM_PING_REQUEST) .shuffleGrouping(ComponentId.FLOW_REROUTE_HUB.name(), Stream.HUB_TO_PING_SENDER.name()) .shuffleGrouping(ComponentId.FLOW_PATH_SWAP_HUB.name(), Stream.HUB_TO_PING_SENDER.name()) .shuffleGrouping(ComponentId.YFLOW_CREATE_HUB.name(), Stream.HUB_TO_PING_SENDER.name()) .shuffleGrouping(ComponentId.YFLOW_UPDATE_HUB.name(), Stream.HUB_TO_PING_SENDER.name()) .shuffleGrouping(ComponentId.YFLOW_REROUTE_HUB.name(), Stream.HUB_TO_PING_SENDER.name()) - .shuffleGrouping(ComponentId.YFLOW_DELETE_HUB.name(), Stream.HUB_TO_PING_SENDER.name()); + .shuffleGrouping(ComponentId.YFLOW_DELETE_HUB.name(), Stream.HUB_TO_PING_SENDER.name()) + .shuffleGrouping(YFlowSyncHubBolt.BOLT_ID, YFlowSyncHubBolt.STREAM_PING_REQUEST); } private void server42ControlTopologyOutput(TopologyBuilder topologyBuilder) { @@ -853,7 +864,7 @@ private void flowMonitoringTopologyOutput(TopologyBuilder topologyBuilder) { Stream.HUB_TO_FLOW_MONITORING_TOPOLOGY_SENDER.name()) .shuffleGrouping(ComponentId.FLOW_DELETE_HUB.name(), Stream.HUB_TO_FLOW_MONITORING_TOPOLOGY_SENDER.name()) - .shuffleGrouping(ComponentId.FLOW_SYNC_HUB.name(), Stream.HUB_TO_FLOW_MONITORING_TOPOLOGY_SENDER.name()) + .shuffleGrouping(FlowSyncHubBolt.BOLT_ID, FlowSyncHubBolt.STREAM_FLOW_MONITORING) .shuffleGrouping(ComponentId.YFLOW_CREATE_HUB.name(), Stream.HUB_TO_FLOW_MONITORING_TOPOLOGY_SENDER.name()) .shuffleGrouping(ComponentId.YFLOW_UPDATE_HUB.name(), @@ -861,7 +872,8 @@ private void flowMonitoringTopologyOutput(TopologyBuilder topologyBuilder) { .shuffleGrouping(ComponentId.YFLOW_REROUTE_HUB.name(), Stream.HUB_TO_FLOW_MONITORING_TOPOLOGY_SENDER.name()) .shuffleGrouping(ComponentId.YFLOW_DELETE_HUB.name(), - Stream.HUB_TO_FLOW_MONITORING_TOPOLOGY_SENDER.name()); + Stream.HUB_TO_FLOW_MONITORING_TOPOLOGY_SENDER.name()) + .shuffleGrouping(YFlowSyncHubBolt.BOLT_ID, YFlowSyncHubBolt.STREAM_FLOW_MONITORING); } private void statsTopologyOutput(TopologyBuilder topologyBuilder) { @@ -875,7 +887,7 @@ private void statsTopologyOutput(TopologyBuilder topologyBuilder) { Stream.HUB_TO_STATS_TOPOLOGY_SENDER.name()) .shuffleGrouping(ComponentId.FLOW_DELETE_HUB.name(), Stream.HUB_TO_STATS_TOPOLOGY_SENDER.name()) - .shuffleGrouping(ComponentId.FLOW_SYNC_HUB.name(), Stream.HUB_TO_STATS_TOPOLOGY_SENDER.name()) + .shuffleGrouping(FlowSyncHubBolt.BOLT_ID, FlowSyncHubBolt.STREAM_STATS) .shuffleGrouping(ComponentId.YFLOW_CREATE_HUB.name(), Stream.HUB_TO_STATS_TOPOLOGY_SENDER.name()) .shuffleGrouping(ComponentId.YFLOW_UPDATE_HUB.name(), @@ -884,6 +896,7 @@ private void statsTopologyOutput(TopologyBuilder topologyBuilder) { Stream.HUB_TO_STATS_TOPOLOGY_SENDER.name()) .shuffleGrouping(ComponentId.YFLOW_DELETE_HUB.name(), Stream.HUB_TO_STATS_TOPOLOGY_SENDER.name()) + .shuffleGrouping(YFlowSyncHubBolt.BOLT_ID, YFlowSyncHubBolt.STREAM_STATS) .shuffleGrouping(ComponentId.FLOW_CREATE_MIRROR_POINT_HUB.name(), Stream.HUB_TO_STATS_TOPOLOGY_SENDER.name()) .shuffleGrouping(ComponentId.FLOW_DELETE_MIRROR_POINT_HUB.name(), @@ -903,8 +916,7 @@ private void history(TopologyBuilder topologyBuilder) { grouping) .fieldsGrouping(ComponentId.FLOW_DELETE_HUB.name(), Stream.HUB_TO_HISTORY_TOPOLOGY_SENDER.name(), grouping) - .fieldsGrouping(ComponentId.FLOW_SYNC_HUB.name(), Stream.HUB_TO_HISTORY_TOPOLOGY_SENDER.name(), - grouping) + .fieldsGrouping(FlowSyncHubBolt.BOLT_ID, FlowSyncHubBolt.STREAM_HISTORY, grouping) .fieldsGrouping(ComponentId.FLOW_PATH_SWAP_HUB.name(), Stream.HUB_TO_HISTORY_TOPOLOGY_SENDER.name(), grouping) .fieldsGrouping(ComponentId.FLOW_SWAP_ENDPOINTS_HUB.name(), @@ -923,6 +935,7 @@ private void history(TopologyBuilder topologyBuilder) { ComponentId.YFLOW_REROUTE_HUB.name(), Stream.HUB_TO_HISTORY_TOPOLOGY_SENDER.name(), grouping) .fieldsGrouping( ComponentId.YFLOW_DELETE_HUB.name(), Stream.HUB_TO_HISTORY_TOPOLOGY_SENDER.name(), grouping) + .fieldsGrouping(YFlowSyncHubBolt.BOLT_ID, YFlowSyncHubBolt.STREAM_HISTORY, grouping) .fieldsGrouping( ComponentId.YFLOW_PATH_SWAP_HUB.name(), Stream.HUB_TO_HISTORY_TOPOLOGY_SENDER.name(), grouping); } @@ -950,6 +963,19 @@ private void metrics(TopologyBuilder topologyBuilder) { .shuffleGrouping(ComponentId.YFLOW_PATH_SWAP_HUB.name(), Stream.HUB_TO_METRICS_BOLT.name()); } + private SyncHubBoltBase.SyncHubConfig newSyncHubConfig() { + int hubTimeout = (int) TimeUnit.SECONDS.toMillis(topologyConfig.getSyncHubTimeoutSeconds()); + + return FlowSyncHubBolt.SyncHubConfig.syncHubConfigBuilder() + .speakerCommandRetriesLimit(topologyConfig.getSyncSpeakerCommandRetries()) + .requestSenderComponent(ComponentId.FLOW_ROUTER_BOLT.name()) + .workerComponent(ComponentId.SPEAKER_WORKER.name()) + .lifeCycleEventComponent(ZooKeeperSpout.SPOUT_ID) + .timeoutMs(hubTimeout) + .autoAck(true) + .build(); + } + public enum ComponentId { FLOW_SPOUT("flow.spout"), SPEAKER_WORKER_SPOUT("fl.worker.spout"), @@ -974,6 +1000,7 @@ public enum ComponentId { YFLOW_UPDATE_HUB("y_flow.update.hub.bolt"), YFLOW_REROUTE_HUB("y_flow.reroute.hub.bolt"), YFLOW_DELETE_HUB("y_flow.delete.hub.bolt"), + YFLOW_SYNC_HUB("y_flow.sync.hub.bolt"), YFLOW_READ_BOLT("y_flow.read.bolt"), YFLOW_VALIDATION_HUB("y_flow.validation.hub.bolt"), YFLOW_PATH_SWAP_HUB("y_flow.pathswap.hub.bolt"), @@ -1023,6 +1050,7 @@ public enum Stream { ROUTER_TO_YFLOW_UPDATE_HUB, ROUTER_TO_YFLOW_REROUTE_HUB, ROUTER_TO_YFLOW_DELETE_HUB, + ROUTER_TO_YFLOW_SYNC_HUB, ROUTER_TO_YFLOW_READ, ROUTER_TO_YFLOW_VALIDATION_HUB, ROUTER_TO_YFLOW_PATH_SWAP_HUB, diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/bolts/FlowSyncHubBolt.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/bolts/FlowSyncHubBolt.java index d7c6d50fde6..149d7c86b82 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/bolts/FlowSyncHubBolt.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/bolts/FlowSyncHubBolt.java @@ -15,263 +15,43 @@ package org.openkilda.wfm.topology.flowhs.bolts; -import static org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream.HUB_TO_FLOW_MONITORING_TOPOLOGY_SENDER; -import static org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream.HUB_TO_HISTORY_TOPOLOGY_SENDER; -import static org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream.HUB_TO_NB_RESPONSE_SENDER; -import static org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream.HUB_TO_PING_SENDER; -import static org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream.HUB_TO_SPEAKER_WORKER; -import static org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream.HUB_TO_STATS_TOPOLOGY_SENDER; -import static org.openkilda.wfm.topology.utils.KafkaRecordTranslator.FIELD_ID_PAYLOAD; - -import org.openkilda.bluegreen.LifecycleEvent; -import org.openkilda.floodlight.api.request.SpeakerRequest; -import org.openkilda.floodlight.api.response.SpeakerFlowSegmentResponse; -import org.openkilda.messaging.Message; -import org.openkilda.messaging.command.CommandData; -import org.openkilda.messaging.command.CommandMessage; import org.openkilda.messaging.command.flow.FlowSyncRequest; -import org.openkilda.messaging.command.flow.PeriodicPingCommand; -import org.openkilda.messaging.info.InfoMessage; -import org.openkilda.model.PathId; import org.openkilda.persistence.PersistenceManager; -import org.openkilda.wfm.CommandContext; import org.openkilda.wfm.error.PipelineException; import org.openkilda.wfm.share.flow.resources.FlowResourcesConfig; import org.openkilda.wfm.share.flow.resources.FlowResourcesManager; -import org.openkilda.wfm.share.history.model.FlowHistoryHolder; -import org.openkilda.wfm.share.hubandspoke.HubBolt; -import org.openkilda.wfm.share.utils.CarrierContext; -import org.openkilda.wfm.share.utils.KeyProvider; -import org.openkilda.wfm.share.zk.ZkStreams; -import org.openkilda.wfm.share.zk.ZooKeeperBolt; import org.openkilda.wfm.topology.flowhs.FlowHsTopology.ComponentId; -import org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream; -import org.openkilda.wfm.topology.flowhs.exception.DuplicateKeyException; -import org.openkilda.wfm.topology.flowhs.exception.UnknownKeyException; import org.openkilda.wfm.topology.flowhs.model.path.FlowPathOperationConfig; -import org.openkilda.wfm.topology.flowhs.model.path.FlowPathRequest; -import org.openkilda.wfm.topology.flowhs.model.path.FlowPathResult; -import org.openkilda.wfm.topology.flowhs.service.FlowSyncCarrier; import org.openkilda.wfm.topology.flowhs.service.FlowSyncService; -import org.openkilda.wfm.topology.flowhs.service.path.FlowPathCarrier; -import org.openkilda.wfm.topology.flowhs.service.path.FlowPathService; -import org.openkilda.wfm.topology.utils.MessageKafkaTranslator; +import org.openkilda.wfm.topology.utils.KafkaRecordTranslator; -import lombok.Builder; -import lombok.Getter; import lombok.NonNull; -import org.apache.storm.topology.OutputFieldsDeclarer; -import org.apache.storm.tuple.Fields; import org.apache.storm.tuple.Tuple; -import org.apache.storm.tuple.Values; -public class FlowSyncHubBolt extends HubBolt implements FlowSyncCarrier, FlowPathCarrier { +public class FlowSyncHubBolt extends SyncHubBoltBase { public static final String BOLT_ID = ComponentId.FLOW_SYNC_HUB.name(); - private final FlowSyncConfig config; - private final FlowResourcesConfig flowResourcesConfig; - - private transient FlowSyncService syncService; - private transient FlowPathService pathService; - - private transient CarrierContext carrierContext; - private LifecycleEvent deferredShutdownEvent; - public FlowSyncHubBolt( - @NonNull FlowSyncConfig config, @NonNull PersistenceManager persistenceManager, + @NonNull SyncHubConfig config, @NonNull PersistenceManager persistenceManager, @NonNull FlowResourcesConfig flowResourcesConfig) { - super(persistenceManager, config); - this.config = config; - this.flowResourcesConfig = flowResourcesConfig; + super(config, persistenceManager, flowResourcesConfig); } @Override protected void onRequest(Tuple input) throws PipelineException { - FlowSyncRequest request = pullValue(input, FIELD_ID_PAYLOAD, FlowSyncRequest.class); + FlowSyncRequest request = pullValue(input, KafkaRecordTranslator.FIELD_ID_PAYLOAD, FlowSyncRequest.class); carrierContext.apply(pullKey(input), key -> handleRequest(key, request)); } - @Override - protected void onWorkerResponse(Tuple input) throws PipelineException { - SpeakerFlowSegmentResponse response = pullValue(input, FIELD_ID_PAYLOAD, SpeakerFlowSegmentResponse.class); - carrierContext.apply(pullKey(input), key -> handleWorkerResponse(key, response)); - } - - @Override - protected void onTimeout(String coordinatorKey, Tuple tuple) throws PipelineException { - carrierContext.apply(coordinatorKey, key -> syncService.handleTimeout(key)); - } - - @Override - protected boolean deactivate(LifecycleEvent event) { - // pathService do not process any external requests (only requests from syncService) so it can to not implement - // enable/disable feature and do not need to be called here. - if (syncService.deactivate()) { - return true; - } - deferredShutdownEvent = event; - return false; - } - - @Override - protected void activate() { - syncService.activate(); - } - private void handleRequest(String serviceKey, FlowSyncRequest request) { syncService.handleRequest(serviceKey, request, getCommandContext()); } - private void handleWorkerResponse(String workerKey, SpeakerFlowSegmentResponse response) { - carrierContext.apply(KeyProvider.getParentKey(workerKey), serviceKey -> { - try { - // FlowSyncService do not communicate with speaker, so only FlowPathService can consume speaker - // responses. - pathService.handleSpeakerResponse(serviceKey, response); - } catch (UnknownKeyException e) { - log.warn("Received a speaker response with unknown key {}.", serviceKey); - } - }); - } - - private String newPathServiceKey(PathId pathId) { - return KeyProvider.joinKeys(pathId.toString(), carrierContext.getContext()); - } - - // -- carrier -- - - @Override - public void sendSpeakerRequest(SpeakerRequest request) { - String requestKey = KeyProvider.joinKeys(request.getCommandId().toString(), carrierContext.getContext()); - emit(HUB_TO_SPEAKER_WORKER.name(), getCurrentTuple(), makeOfSpeakerTuple(requestKey, request)); - } - - @Override - public void sendPeriodicPingNotification(String flowId, boolean enabled) { - // TODO(surabujin): ensure usage - PeriodicPingCommand payload = new PeriodicPingCommand(flowId, enabled); - emit(Stream.HUB_TO_PING_SENDER.name(), getCurrentTuple(), makePingTuple(payload)); - } - @Override - public void sendHistoryUpdate(FlowHistoryHolder payload) { - emit(Stream.HUB_TO_HISTORY_TOPOLOGY_SENDER.name(), getCurrentTuple(), makeHistoryTuple(payload)); - } - - @Override - public void cancelTimeoutCallback(String key) { - cancelCallback(key); - } - - @Override - public void sendInactive() { - getOutput().emit(ZkStreams.ZK.toString(), new Values(deferredShutdownEvent, getCommandContext())); - deferredShutdownEvent = null; - } - - @Override - public void sendNorthboundResponse(Message message) { - emit(Stream.HUB_TO_NB_RESPONSE_SENDER.name(), getCurrentTuple(), makeNorthboundTuple(message)); - } - - private Values makeNorthboundTuple(Message message) { - return new Values(carrierContext.getContext(), message, getCommandContext()); - } - - private Values makeOfSpeakerTuple(String requestKey, SpeakerRequest request) { - return new Values(requestKey, request, getCommandContext()); - } - - private Values makePingTuple(CommandData payload) { - CommandContext commandContext = getCommandContext(); - Message message = new CommandMessage( - payload, commandContext.getCreateTime(), commandContext.getCorrelationId()); - return new Values(carrierContext.getContext(), message, commandContext); - } - - private Values makeHistoryTuple(FlowHistoryHolder payload) { - CommandContext commandContext = getCommandContext(); - InfoMessage message = new InfoMessage(payload, commandContext.getCreateTime(), - commandContext.getCorrelationId()); - return new Values(payload.getTaskId(), message, getCommandContext()); - } - - @Override - public void launchFlowPathInstallation( - @NonNull FlowPathRequest request, @NonNull FlowPathOperationConfig config, - @NonNull CommandContext commandContext) throws DuplicateKeyException { - try { - carrierContext.applyUnsafe(newPathServiceKey( - request.getReference().getPathId()), - pathServiceKey -> pathService.installPath(request, pathServiceKey, config, commandContext)); - } catch (DuplicateKeyException e) { - throw e; - } catch (Exception e) { - carrierContext.throwUnexpectedException(e); - } - } - - @Override - public void cancelFlowPathOperation(PathId pathId) throws UnknownKeyException { - try { - carrierContext.applyUnsafe( - newPathServiceKey(pathId), - pathServiceKey -> pathService.cancelOperation(pathServiceKey)); - } catch (UnknownKeyException e) { - throw e; - } catch (Exception e) { - carrierContext.throwUnexpectedException(e); - } - } - - @Override - public void processFlowPathOperationResults(FlowPathResult result) { - syncService.handlePathSyncResponse(result.getReference(), result.getResultCode()); - } - - // -- storm API -- - - @Override - protected void init() { - super.init(); - + protected FlowSyncService newSyncService() { final FlowResourcesManager resourcesManager = new FlowResourcesManager(persistenceManager, flowResourcesConfig); final FlowPathOperationConfig pathOperationConfig = new FlowPathOperationConfig( config.getSpeakerCommandRetriesLimit()); - - pathService = new FlowPathService(this); - syncService = new FlowSyncService(this, persistenceManager, resourcesManager, pathOperationConfig); - - carrierContext = new CarrierContext<>(); - } - - @Override - public void declareOutputFields(OutputFieldsDeclarer declarer) { - super.declareOutputFields(declarer); - - declarer.declareStream(ZkStreams.ZK.toString(), - new Fields(ZooKeeperBolt.FIELD_ID_STATE, ZooKeeperBolt.FIELD_ID_CONTEXT)); - - declarer.declareStream(HUB_TO_SPEAKER_WORKER.name(), MessageKafkaTranslator.STREAM_FIELDS); - declarer.declareStream(HUB_TO_NB_RESPONSE_SENDER.name(), MessageKafkaTranslator.STREAM_FIELDS); - declarer.declareStream(HUB_TO_HISTORY_TOPOLOGY_SENDER.name(), MessageKafkaTranslator.STREAM_FIELDS); - declarer.declareStream(HUB_TO_PING_SENDER.name(), MessageKafkaTranslator.STREAM_FIELDS); - declarer.declareStream(HUB_TO_FLOW_MONITORING_TOPOLOGY_SENDER.name(), MessageKafkaTranslator.STREAM_FIELDS); - declarer.declareStream(HUB_TO_STATS_TOPOLOGY_SENDER.name(), MessageKafkaTranslator.STREAM_FIELDS); - } - - // -- topology API -- - - @Getter - public static class FlowSyncConfig extends Config { - private final int speakerCommandRetriesLimit; - - @Builder(builderMethodName = "flowSyncBuilder", builderClassName = "flowSyncBuild") - public FlowSyncConfig( - String requestSenderComponent, String workerComponent, String lifeCycleEventComponent, int timeoutMs, - boolean autoAck, int speakerCommandRetriesLimit) { - super(requestSenderComponent, workerComponent, lifeCycleEventComponent, timeoutMs, autoAck); - this.speakerCommandRetriesLimit = speakerCommandRetriesLimit; - } + return new FlowSyncService(this, persistenceManager, resourcesManager, pathOperationConfig); } } diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/bolts/RouterBolt.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/bolts/RouterBolt.java index df822d420c2..eadd66124f0 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/bolts/RouterBolt.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/bolts/RouterBolt.java @@ -16,7 +16,6 @@ package org.openkilda.wfm.topology.flowhs.bolts; import static java.lang.String.format; -import static java.util.Collections.emptySet; import static org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream.ROUTER_TO_FLOW_CREATE_HUB; import static org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream.ROUTER_TO_FLOW_CREATE_MIRROR_POINT_HUB; import static org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream.ROUTER_TO_FLOW_DELETE_HUB; @@ -32,6 +31,7 @@ import static org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream.ROUTER_TO_YFLOW_PATH_SWAP_HUB; import static org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream.ROUTER_TO_YFLOW_READ; import static org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream.ROUTER_TO_YFLOW_REROUTE_HUB; +import static org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream.ROUTER_TO_YFLOW_SYNC_HUB; import static org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream.ROUTER_TO_YFLOW_UPDATE_HUB; import static org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream.ROUTER_TO_YFLOW_VALIDATION_HUB; import static org.openkilda.wfm.topology.utils.KafkaRecordTranslator.FIELD_ID_KEY; @@ -127,7 +127,7 @@ protected void handleInput(Tuple input) { Values values = new Values(key, deleteRequest.getFlowId(), data); emitWithContext(ROUTER_TO_FLOW_DELETE_HUB.name(), input, values); } else if (data instanceof FlowSyncRequest) { - routeSyncRequest(input, (FlowSyncRequest) data, key); + routeFlowSyncRequest(input, (FlowSyncRequest) data, key); } else if (data instanceof FlowPathSwapRequest) { FlowPathSwapRequest pathSwapRequest = (FlowPathSwapRequest) data; log.debug("Received a path swap request {} with key {}. MessageId {}", pathSwapRequest.getFlowId(), @@ -209,12 +209,7 @@ protected void handleInput(Tuple input) { emitWithContext(ROUTER_TO_YFLOW_VALIDATION_HUB.name(), input, new Values(key, request.getYFlowId(), data)); } else if (data instanceof YFlowSyncRequest) { - YFlowSyncRequest request = (YFlowSyncRequest) data; - log.debug("Received a y-flow synchronization request {} with key {}", request, key); - YFlowRerouteRequest rerouteRequest = new YFlowRerouteRequest(request.getYFlowId(), emptySet(), - true, "initiated via synchronization request", false); - emitWithContext(ROUTER_TO_YFLOW_REROUTE_HUB.name(), input, - new Values(key, rerouteRequest.getYFlowId(), rerouteRequest)); + routeYflowSyncRequest(input, (YFlowSyncRequest) data, key); } else if (data instanceof YFlowPathSwapRequest) { YFlowPathSwapRequest request = (YFlowPathSwapRequest) data; log.debug("Received a y-flow path swap request {} with key {}", request, key); @@ -226,13 +221,20 @@ protected void handleInput(Tuple input) { } } - private void routeSyncRequest(Tuple input, FlowSyncRequest syncRequest, String key) { - log.debug("Received a sync request {} with key {}. MessageId {}", - syncRequest.getFlowId(), key, input.getMessageId()); - Values values = new Values(key, syncRequest.getFlowId(), syncRequest, getCommandContext()); + private void routeFlowSyncRequest(Tuple input, FlowSyncRequest request, String key) { + log.debug("Received a flow sync request {} with key {} (MessageId={})", + request.getFlowId(), key, input.getMessageId()); + Values values = new Values(key, request.getFlowId(), request, getCommandContext()); emit(ROUTER_TO_FLOW_SYNC_HUB.name(), input, values); } + private void routeYflowSyncRequest(Tuple input, YFlowSyncRequest request, String key) { + log.debug("Received an y-flow sync request {} with key {} (MessageId={})", + request.getYFlowId(), key, input.getMessageId()); + Values values = new Values(key, request.getYFlowId(), request, getCommandContext()); + emit(ROUTER_TO_YFLOW_SYNC_HUB.name(), input, values); + } + @Override public void declareOutputFields(OutputFieldsDeclarer declarer) { declarer.declareStream(ROUTER_TO_FLOW_CREATE_HUB.name(), STREAM_FIELDS); @@ -250,6 +252,7 @@ public void declareOutputFields(OutputFieldsDeclarer declarer) { declarer.declareStream(ROUTER_TO_YFLOW_UPDATE_HUB.name(), STREAM_FIELDS); declarer.declareStream(ROUTER_TO_YFLOW_REROUTE_HUB.name(), STREAM_FIELDS); declarer.declareStream(ROUTER_TO_YFLOW_DELETE_HUB.name(), STREAM_FIELDS); + declarer.declareStream(ROUTER_TO_YFLOW_SYNC_HUB.name(), STREAM_FIELDS); declarer.declareStream(ROUTER_TO_YFLOW_READ.name(), new Fields(FIELD_ID_KEY, FIELD_ID_PAYLOAD, FIELD_ID_CONTEXT)); declarer.declareStream(ROUTER_TO_YFLOW_VALIDATION_HUB.name(), STREAM_FIELDS); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/bolts/SyncHubBoltBase.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/bolts/SyncHubBoltBase.java new file mode 100644 index 00000000000..50d20b90227 --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/bolts/SyncHubBoltBase.java @@ -0,0 +1,287 @@ +/* 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.flowhs.bolts; + +import org.openkilda.bluegreen.LifecycleEvent; +import org.openkilda.floodlight.api.request.SpeakerRequest; +import org.openkilda.floodlight.api.response.SpeakerFlowSegmentResponse; +import org.openkilda.floodlight.api.response.SpeakerResponse; +import org.openkilda.messaging.Message; +import org.openkilda.messaging.command.CommandData; +import org.openkilda.messaging.command.CommandMessage; +import org.openkilda.messaging.command.flow.PeriodicPingCommand; +import org.openkilda.messaging.info.InfoMessage; +import org.openkilda.model.PathId; +import org.openkilda.persistence.PersistenceManager; +import org.openkilda.wfm.CommandContext; +import org.openkilda.wfm.error.PipelineException; +import org.openkilda.wfm.share.flow.resources.FlowResourcesConfig; +import org.openkilda.wfm.share.history.model.FlowHistoryHolder; +import org.openkilda.wfm.share.hubandspoke.HubBolt; +import org.openkilda.wfm.share.utils.CarrierContext; +import org.openkilda.wfm.share.utils.KeyProvider; +import org.openkilda.wfm.share.zk.ZkStreams; +import org.openkilda.wfm.share.zk.ZooKeeperBolt; +import org.openkilda.wfm.topology.flowhs.FlowHsTopology.Stream; +import org.openkilda.wfm.topology.flowhs.exception.DuplicateKeyException; +import org.openkilda.wfm.topology.flowhs.exception.UnknownKeyException; +import org.openkilda.wfm.topology.flowhs.model.path.FlowPathOperationConfig; +import org.openkilda.wfm.topology.flowhs.model.path.FlowPathRequest; +import org.openkilda.wfm.topology.flowhs.model.path.FlowPathResult; +import org.openkilda.wfm.topology.flowhs.service.FlowSyncCarrier; +import org.openkilda.wfm.topology.flowhs.service.common.SyncServiceBase; +import org.openkilda.wfm.topology.flowhs.service.path.FlowPathCarrier; +import org.openkilda.wfm.topology.flowhs.service.path.FlowPathService; +import org.openkilda.wfm.topology.utils.KafkaRecordTranslator; +import org.openkilda.wfm.topology.utils.MessageKafkaTranslator; + +import lombok.Builder; +import lombok.Getter; +import lombok.NonNull; +import org.apache.storm.topology.OutputFieldsDeclarer; +import org.apache.storm.tuple.Fields; +import org.apache.storm.tuple.Tuple; +import org.apache.storm.tuple.Values; + +public abstract class SyncHubBoltBase> + extends HubBolt implements FlowSyncCarrier, FlowPathCarrier { + public static final String STREAM_ZK = ZkStreams.ZK.toString(); + public static final Fields STREAM_ZK_FIELDS = new Fields( + ZooKeeperBolt.FIELD_ID_STATE, ZooKeeperBolt.FIELD_ID_CONTEXT); + + public static final String STREAM_WORKER = Stream.HUB_TO_SPEAKER_WORKER.name(); + public static final Fields STREAM_WORKER_FIELDS = MessageKafkaTranslator.STREAM_FIELDS; + + public static final String STREAM_NB_RESPONSE = Stream.HUB_TO_NB_RESPONSE_SENDER.name(); + public static final Fields STREAM_NB_RESPONSE_FIELDS = MessageKafkaTranslator.STREAM_FIELDS; + + public static final String STREAM_HISTORY = Stream.HUB_TO_HISTORY_TOPOLOGY_SENDER.name(); + public static final Fields STREAM_HISTORY_FIELDS = STREAM_NB_RESPONSE_FIELDS; + + public static final String STREAM_PING_REQUEST = Stream.HUB_TO_PING_SENDER.name(); + public static final Fields STREAM_PING_REQUEST_FIELDS = STREAM_NB_RESPONSE_FIELDS; + + public static final String STREAM_FLOW_MONITORING = Stream.HUB_TO_FLOW_MONITORING_TOPOLOGY_SENDER.name(); + public static final Fields STREAM_FLOW_MONITORING_FIELDS = STREAM_NB_RESPONSE_FIELDS; + + public static final String STREAM_STATS = Stream.HUB_TO_STATS_TOPOLOGY_SENDER.name(); + public static final Fields STREAM_STATS_FIELDS = STREAM_NB_RESPONSE_FIELDS; + + protected final SyncHubConfig config; + protected final FlowResourcesConfig flowResourcesConfig; + + protected transient S syncService; + private transient FlowPathService pathService; + + protected transient CarrierContext carrierContext; + private LifecycleEvent deferredShutdownEvent; + + public SyncHubBoltBase( + @NonNull SyncHubConfig config, @NonNull PersistenceManager persistenceManager, + @NonNull FlowResourcesConfig flowResourcesConfig) { + super(persistenceManager, config); + this.config = config; + this.flowResourcesConfig = flowResourcesConfig; + } + + @Override + protected void onWorkerResponse(Tuple input) throws PipelineException { + SpeakerResponse response = pullValue( + input, KafkaRecordTranslator.FIELD_ID_PAYLOAD, SpeakerResponse.class); + carrierContext.apply(pullKey(input), key -> dispatchWorkerResponse(key, response)); + } + + protected void dispatchWorkerResponse(String workerKey, SpeakerResponse response) { + if (response instanceof SpeakerFlowSegmentResponse) { + onSpeakerCommandResponse(workerKey, (SpeakerFlowSegmentResponse) response); + } else { + log.error("Unexpected worker response - class:{}, toString:{}", response.getClass().getName(), response); + } + } + + protected void onSpeakerCommandResponse(String workerKey, SpeakerFlowSegmentResponse response) { + carrierContext.apply( + KeyProvider.getParentKey(workerKey), serviceKey -> handleWorkerResponse(serviceKey, response)); + } + + @Override + protected void onTimeout(String coordinatorKey, Tuple tuple) throws PipelineException { + carrierContext.apply(coordinatorKey, key -> syncService.handleTimeout(key)); + } + + @Override + protected boolean deactivate(LifecycleEvent event) { + // pathService do not process any external requests (only requests from syncService) so it can to not implement + // enable/disable feature and do not need to be called here. + if (syncService.deactivate()) { + return true; + } + deferredShutdownEvent = event; + return false; + } + + @Override + protected void activate() { + syncService.activate(); + } + + protected void handleWorkerResponse(String serviceKey, SpeakerFlowSegmentResponse response) { + try { + // FlowSyncService do not communicate with speaker, so only FlowPathService can consume speaker + // responses. + pathService.handleSpeakerResponse(serviceKey, response); + } catch (UnknownKeyException e) { + log.warn("Received a speaker response with unknown key {}.", serviceKey); + } + } + + private String newPathServiceKey(PathId pathId) { + return KeyProvider.joinKeys(pathId.toString(), carrierContext.getContext()); + } + + // -- carrier -- + + @Override + public void sendSpeakerRequest(SpeakerRequest request) { + String requestKey = KeyProvider.joinKeys(request.getCommandId().toString(), carrierContext.getContext()); + emit(STREAM_WORKER, getCurrentTuple(), makeOfSpeakerTuple(requestKey, request)); + } + + @Override + public void sendPeriodicPingNotification(String flowId, boolean enabled) { + // TODO(surabujin): ensure usage + PeriodicPingCommand payload = new PeriodicPingCommand(flowId, enabled); + emit(Stream.HUB_TO_PING_SENDER.name(), getCurrentTuple(), makePingTuple(payload)); + } + + @Override + public void sendHistoryUpdate(FlowHistoryHolder payload) { + emit(Stream.HUB_TO_HISTORY_TOPOLOGY_SENDER.name(), getCurrentTuple(), makeHistoryTuple(payload)); + } + + @Override + public void cancelTimeoutCallback(String key) { + cancelCallback(key); + } + + @Override + public void sendInactive() { + getOutput().emit(STREAM_ZK, new Values(deferredShutdownEvent, getCommandContext())); + deferredShutdownEvent = null; + } + + @Override + public void sendNorthboundResponse(Message message) { + emit(Stream.HUB_TO_NB_RESPONSE_SENDER.name(), getCurrentTuple(), makeNorthboundTuple(message)); + } + + private Values makeNorthboundTuple(Message message) { + return new Values(carrierContext.getContext(), message, getCommandContext()); + } + + private Values makeOfSpeakerTuple(String requestKey, SpeakerRequest request) { + return new Values(requestKey, request, getCommandContext()); + } + + private Values makePingTuple(CommandData payload) { + CommandContext commandContext = getCommandContext(); + Message message = new CommandMessage( + payload, commandContext.getCreateTime(), commandContext.getCorrelationId()); + return new Values(carrierContext.getContext(), message, commandContext); + } + + private Values makeHistoryTuple(FlowHistoryHolder payload) { + CommandContext commandContext = getCommandContext(); + InfoMessage message = new InfoMessage(payload, commandContext.getCreateTime(), + commandContext.getCorrelationId()); + return new Values(payload.getTaskId(), message, getCommandContext()); + } + + @Override + public void launchFlowPathInstallation( + @NonNull FlowPathRequest request, @NonNull FlowPathOperationConfig config, + @NonNull CommandContext commandContext) throws DuplicateKeyException { + try { + carrierContext.applyUnsafe(newPathServiceKey( + request.getReference().getPathId()), + pathServiceKey -> pathService.installPath(request, pathServiceKey, config, commandContext)); + } catch (DuplicateKeyException e) { + throw e; + } catch (Exception e) { + carrierContext.throwUnexpectedException(e); + } + } + + @Override + public void cancelFlowPathOperation(PathId pathId) throws UnknownKeyException { + try { + carrierContext.applyUnsafe( + newPathServiceKey(pathId), + pathServiceKey -> pathService.cancelOperation(pathServiceKey)); + } catch (UnknownKeyException e) { + throw e; + } catch (Exception e) { + carrierContext.throwUnexpectedException(e); + } + } + + @Override + public void processFlowPathOperationResults(FlowPathResult result) { + syncService.handlePathSyncResponse(result.getReference(), result.getResultCode()); + } + + // -- storm API -- + + @Override + protected void init() { + super.init(); + + syncService = newSyncService(); + pathService = new FlowPathService(this); + carrierContext = new CarrierContext<>(); + } + + protected abstract S newSyncService(); + + @Override + public void declareOutputFields(OutputFieldsDeclarer declarer) { + super.declareOutputFields(declarer); + + declarer.declareStream(STREAM_ZK, STREAM_ZK_FIELDS); + + declarer.declareStream(STREAM_WORKER, STREAM_WORKER_FIELDS); + declarer.declareStream(STREAM_NB_RESPONSE, STREAM_NB_RESPONSE_FIELDS); + declarer.declareStream(STREAM_HISTORY, STREAM_HISTORY_FIELDS); + declarer.declareStream(STREAM_PING_REQUEST, STREAM_PING_REQUEST_FIELDS); + declarer.declareStream(STREAM_FLOW_MONITORING, STREAM_FLOW_MONITORING_FIELDS); + declarer.declareStream(STREAM_STATS, STREAM_STATS_FIELDS); + } + + // -- topology API -- + + @Getter + public static class SyncHubConfig extends Config { + private final int speakerCommandRetriesLimit; + + @Builder(builderMethodName = "syncHubConfigBuilder") + public SyncHubConfig( + String requestSenderComponent, String workerComponent, String lifeCycleEventComponent, int timeoutMs, + boolean autoAck, int speakerCommandRetriesLimit) { + super(requestSenderComponent, workerComponent, lifeCycleEventComponent, timeoutMs, autoAck); + this.speakerCommandRetriesLimit = speakerCommandRetriesLimit; + } + } +} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/bolts/YFlowSyncHubBolt.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/bolts/YFlowSyncHubBolt.java new file mode 100644 index 00000000000..07be3932235 --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/bolts/YFlowSyncHubBolt.java @@ -0,0 +1,78 @@ +/* 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.flowhs.bolts; + +import org.openkilda.floodlight.api.response.SpeakerResponse; +import org.openkilda.floodlight.api.response.rulemanager.SpeakerCommandResponse; +import org.openkilda.messaging.command.yflow.YFlowSyncRequest; +import org.openkilda.persistence.PersistenceManager; +import org.openkilda.rulemanager.RuleManager; +import org.openkilda.rulemanager.RuleManagerConfig; +import org.openkilda.rulemanager.RuleManagerImpl; +import org.openkilda.wfm.share.flow.resources.FlowResourcesConfig; +import org.openkilda.wfm.share.flow.resources.FlowResourcesManager; +import org.openkilda.wfm.topology.flowhs.FlowHsTopology.ComponentId; +import org.openkilda.wfm.topology.flowhs.model.path.FlowPathOperationConfig; +import org.openkilda.wfm.topology.flowhs.service.YFlowSyncService; +import org.openkilda.wfm.topology.utils.KafkaRecordTranslator; + +import lombok.NonNull; +import org.apache.storm.tuple.Tuple; + +public class YFlowSyncHubBolt extends SyncHubBoltBase { + public static final String BOLT_ID = ComponentId.YFLOW_SYNC_HUB.name(); + + private final RuleManagerConfig ruleManagerConfig; + + public YFlowSyncHubBolt( + @NonNull SyncHubConfig config, @NonNull PersistenceManager persistenceManager, + @NonNull FlowResourcesConfig flowResourcesConfig, @NonNull RuleManagerConfig ruleManagerConfig) { + super(config, persistenceManager, flowResourcesConfig); + this.ruleManagerConfig = ruleManagerConfig; + } + + @Override + protected void onRequest(Tuple input) throws Exception { + YFlowSyncRequest request = pullValue(input, KafkaRecordTranslator.FIELD_ID_PAYLOAD, YFlowSyncRequest.class); + carrierContext.apply(pullKey(input), key -> handleRequest(key, request)); + } + + private void handleRequest(String serviceKey, YFlowSyncRequest request) { + syncService.handleRequest(serviceKey, request, getCommandContext()); + } + + @Override + protected void dispatchWorkerResponse(String workerKey, SpeakerResponse response) { + boolean handled = false; + if (response instanceof SpeakerCommandResponse) { + handled = syncService.handleSpeakerResponse((SpeakerCommandResponse) response); + } + if (! handled) { + super.dispatchWorkerResponse(workerKey, response); + } + } + + // -- storm API -- + + @Override + protected YFlowSyncService newSyncService() { + final FlowResourcesManager resourcesManager = new FlowResourcesManager(persistenceManager, flowResourcesConfig); + final FlowPathOperationConfig pathOperationConfig = new FlowPathOperationConfig( + config.getSpeakerCommandRetriesLimit()); + RuleManager ruleManager = new RuleManagerImpl(ruleManagerConfig); + return new YFlowSyncService(this, persistenceManager, resourcesManager, ruleManager, pathOperationConfig); + } +} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/FlowPathSwappingFsm.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/FlowPathSwappingFsm.java index 7b28d3688cb..319aacc341f 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/FlowPathSwappingFsm.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/FlowPathSwappingFsm.java @@ -36,7 +36,7 @@ import lombok.NonNull; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.squirrelframework.foundation.fsm.StateMachine; +import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine; import java.util.ArrayList; import java.util.Collection; @@ -48,7 +48,7 @@ @Getter @Setter @Slf4j -public abstract class FlowPathSwappingFsm, S, E, C, +public abstract class FlowPathSwappingFsm, S, E, C, R extends NorthboundResponseCarrier & HistoryUpdateCarrier, L extends FlowProcessingEventListener> extends FlowProcessingWithHistorySupportFsm { protected final String flowId; diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/FlowProcessingFsm.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/FlowProcessingFsm.java index a60e4cb2004..5849076ced7 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/FlowProcessingFsm.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/FlowProcessingFsm.java @@ -20,12 +20,13 @@ import lombok.Getter; import lombok.NonNull; -import org.squirrelframework.foundation.fsm.StateMachine; +import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine; import java.util.Collection; import java.util.function.Consumer; -public abstract class FlowProcessingFsm, S, E, C, L extends ProcessingEventListener> +public abstract class FlowProcessingFsm< + T extends AbstractStateMachine, S, E, C, L extends ProcessingEventListener> extends AbstractBaseFsm { private final E nextEvent; private final E errorEvent; diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/FlowProcessingWithHistorySupportFsm.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/FlowProcessingWithHistorySupportFsm.java index 28dcf27c923..ccad2ec143c 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/FlowProcessingWithHistorySupportFsm.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/FlowProcessingWithHistorySupportFsm.java @@ -29,12 +29,12 @@ import org.openkilda.wfm.topology.flowhs.service.common.ProcessingEventListener; import lombok.NonNull; -import org.squirrelframework.foundation.fsm.StateMachine; +import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine; import java.time.Instant; import java.util.Collection; -public abstract class FlowProcessingWithHistorySupportFsm, S, E, C, +public abstract class FlowProcessingWithHistorySupportFsm, S, E, C, R extends NorthboundResponseCarrier & HistoryUpdateCarrier, L extends ProcessingEventListener> extends NbTrackableFlowProcessingFsm implements FlowHistoryCarrier { diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/NbTrackableFlowProcessingFsm.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/NbTrackableFlowProcessingFsm.java index a33160f20b9..6101442b523 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/NbTrackableFlowProcessingFsm.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/NbTrackableFlowProcessingFsm.java @@ -26,11 +26,11 @@ import lombok.Getter; import lombok.NonNull; import lombok.Setter; -import org.squirrelframework.foundation.fsm.StateMachine; +import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine; import java.util.Collection; -public abstract class NbTrackableFlowProcessingFsm, S, E, C, +public abstract class NbTrackableFlowProcessingFsm, S, E, C, R extends NorthboundResponseCarrier, L extends ProcessingEventListener> extends FlowProcessingFsm { @Getter diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/YFlowProcessingFsm.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/YFlowProcessingFsm.java index ad1a888cd46..c8b9e0bb967 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/YFlowProcessingFsm.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/YFlowProcessingFsm.java @@ -32,7 +32,7 @@ import lombok.NonNull; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.squirrelframework.foundation.fsm.StateMachine; +import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine; import java.util.Collection; import java.util.HashMap; @@ -42,7 +42,7 @@ @Slf4j @Getter -public abstract class YFlowProcessingFsm, S, E, C, +public abstract class YFlowProcessingFsm, S, E, C, R extends FlowGenericCarrier, L extends ProcessingEventListener> extends FlowProcessingWithHistorySupportFsm { private final String yFlowId; diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/actions/YFlowRuleManagerProcessingAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/actions/YFlowRuleManagerProcessingAction.java index 3a588b07682..f47f1d68f3d 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/actions/YFlowRuleManagerProcessingAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/actions/YFlowRuleManagerProcessingAction.java @@ -26,7 +26,6 @@ import org.openkilda.model.PathId; import org.openkilda.model.SwitchId; import org.openkilda.model.YFlow; -import org.openkilda.model.YSubFlow; import org.openkilda.persistence.PersistenceManager; import org.openkilda.rulemanager.DataAdapter; import org.openkilda.rulemanager.FlowSpeakerData; @@ -35,62 +34,36 @@ import org.openkilda.rulemanager.adapter.PersistenceDataAdapter; import org.openkilda.wfm.CommandContext; import org.openkilda.wfm.topology.flowhs.fsm.common.YFlowProcessingFsm; -import org.openkilda.wfm.topology.flowhs.fsm.common.converters.FlowRulesConverter; import org.openkilda.wfm.topology.flowhs.fsm.common.converters.OfCommandConverter; +import org.openkilda.wfm.topology.flowhs.utils.YFlowRuleManagerAdapter; -import com.google.common.collect.Sets; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Map; -import java.util.Objects; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; @Slf4j public abstract class YFlowRuleManagerProcessingAction, S, E, C> extends YFlowProcessingWithHistorySupportAction { protected final RuleManager ruleManager; + private final YFlowRuleManagerAdapter ruleManagerAdapter; protected YFlowRuleManagerProcessingAction(PersistenceManager persistenceManager, RuleManager ruleManager) { super(persistenceManager); this.ruleManager = ruleManager; + + ruleManagerAdapter = new YFlowRuleManagerAdapter(persistenceManager, ruleManager); } protected Collection buildYFlowInstallRequests(YFlow yFlow, CommandContext context) { - Map> speakerData = buildYFlowSpeakerData(yFlow); - return FlowRulesConverter.INSTANCE.buildFlowInstallCommands(speakerData, context); + return ruleManagerAdapter.buildInstallRequests(yFlow, context); } protected Collection buildYFlowDeleteRequests(YFlow yFlow, CommandContext context) { - Map> speakerData = buildYFlowSpeakerData(yFlow); - return FlowRulesConverter.INSTANCE.buildFlowDeleteCommands(speakerData, context); - } - - private Map> buildYFlowSpeakerData(YFlow yFlow) { - Set pathIds = yFlow.getSubFlows().stream() - .map(YSubFlow::getFlow) - .flatMap(flow -> Stream.of(flow.getForwardPathId(), flow.getReversePathId(), - flow.getProtectedForwardPathId(), flow.getProtectedReversePathId())) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - - Set switchIds = Sets.newHashSet(yFlow.getSharedEndpoint().getSwitchId(), yFlow.getYPoint(), - yFlow.getProtectedPathYPoint()); - DataAdapter dataAdapter = PersistenceDataAdapter.builder() - .persistenceManager(persistenceManager) - .switchIds(switchIds) - .pathIds(pathIds) - .build(); - List flowPaths = new ArrayList<>(dataAdapter.getFlowPaths().values()); - - return ruleManager.buildRulesForYFlow(flowPaths, dataAdapter).stream() - .collect(Collectors.groupingBy(SpeakerData::getSwitchId, - Collectors.mapping(Function.identity(), toList()))); + return ruleManagerAdapter.buildDeleteRequests(yFlow, context); } protected List buildYFlowSpeakerData(SwitchId switchId, Set pathIds) { diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/path/FlowPathFsmBase.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/path/FlowPathFsmBase.java index 7b6a6c09d54..769903422c3 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/path/FlowPathFsmBase.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/path/FlowPathFsmBase.java @@ -22,6 +22,8 @@ import org.openkilda.wfm.CommandContext; import org.openkilda.wfm.topology.flowhs.fsm.FsmUtil; import org.openkilda.wfm.topology.flowhs.fsm.common.FlowProcessingWithHistorySupportFsm; +import org.openkilda.wfm.topology.flowhs.fsm.path.FlowPathFsmBase.Event; +import org.openkilda.wfm.topology.flowhs.fsm.path.FlowPathFsmBase.State; import org.openkilda.wfm.topology.flowhs.model.path.FlowPathChunk; import org.openkilda.wfm.topology.flowhs.model.path.FlowPathOperationConfig; import org.openkilda.wfm.topology.flowhs.model.path.FlowPathReference; @@ -36,7 +38,7 @@ import com.fasterxml.uuid.NoArgGenerator; import lombok.Getter; import lombok.NonNull; -import org.squirrelframework.foundation.fsm.StateMachine; +import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine; import java.util.ArrayList; import java.util.HashMap; @@ -52,7 +54,7 @@ // TODO(surabujin): reconsider inheritance from FlowProcessingWithHistorySupportFsm // TODO(surabujin): request round trip timer public abstract class FlowPathFsmBase< - T extends StateMachine> + T extends AbstractStateMachine> extends FlowProcessingWithHistorySupportFsm< T, FlowPathFsmBase.State, FlowPathFsmBase.Event, FlowPathContext, FlowGenericCarrier, ProcessingEventListener> implements FlowPathOperation { diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/actions/CreateSyncHandlersAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/CreateSyncHandlersAction.java similarity index 78% rename from src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/actions/CreateSyncHandlersAction.java rename to src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/CreateSyncHandlersAction.java index eb83db9382f..197491d5932 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/actions/CreateSyncHandlersAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/CreateSyncHandlersAction.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package org.openkilda.wfm.topology.flowhs.fsm.sync.actions; +package org.openkilda.wfm.topology.flowhs.fsm.sync; import org.openkilda.model.Flow; import org.openkilda.model.FlowPath; @@ -24,10 +24,6 @@ import org.openkilda.wfm.share.model.SpeakerRequestBuildContext; import org.openkilda.wfm.topology.flowhs.exception.DuplicateKeyException; import org.openkilda.wfm.topology.flowhs.fsm.common.actions.FlowProcessingWithHistorySupportAction; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncContext; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm.Event; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm.State; import org.openkilda.wfm.topology.flowhs.model.path.FlowPathChunk; import org.openkilda.wfm.topology.flowhs.model.path.FlowPathOperationConfig; import org.openkilda.wfm.topology.flowhs.model.path.FlowPathOperationDescriptor; @@ -45,26 +41,28 @@ import java.util.Collections; import java.util.List; -public class CreateSyncHandlersAction - extends FlowProcessingWithHistorySupportAction { - private final FlowSyncCarrier carrier; +public class CreateSyncHandlersAction, S, E> + extends FlowProcessingWithHistorySupportAction { private final FlowCommandBuilderFactory commandBuilderFactory; private final FlowPathOperationConfig flowPathOperationConfig; public CreateSyncHandlersAction( - @NonNull FlowSyncCarrier carrier, @NonNull PersistenceManager persistenceManager, - @NonNull FlowResourcesManager resourcesManager, + @NonNull PersistenceManager persistenceManager, @NonNull FlowResourcesManager resourcesManager, @NonNull FlowPathOperationConfig flowPathOperationConfig) { super(persistenceManager); - this.carrier = carrier; this.commandBuilderFactory = new FlowCommandBuilderFactory(resourcesManager); this.flowPathOperationConfig = flowPathOperationConfig; } @Override - protected void perform(State from, State to, Event event, FlowSyncContext context, FlowSyncFsm stateMachine) { - Flow flow = getFlow(stateMachine.getFlowId()); + protected void perform(S from, S to, E event, FlowSyncContext context, F stateMachine) { + for (String flowId : stateMachine.getTargets()) { + proceedFlow(stateMachine, getFlow(flowId)); + } + } + + private void proceedFlow(F stateMachine, Flow flow) { FlowCommandBuilder commandBuilder = commandBuilderFactory.getBuilder(flow.getEncapsulationType()); proceedPathPair(stateMachine, commandBuilder, flow, flow.getForwardPath(), flow.getReversePath()); @@ -75,8 +73,7 @@ protected void perform(State from, State to, Event event, FlowSyncContext contex } private void proceedPathPair( - FlowSyncFsm stateMachine, FlowCommandBuilder commandBuilder, Flow flow, FlowPath path, - FlowPath oppositePath) { + F stateMachine, FlowCommandBuilder commandBuilder, Flow flow, FlowPath path, FlowPath oppositePath) { SpeakerRequestBuildContext speakerContext = buildBaseSpeakerContextForInstall( path.getSrcSwitchId(), path.getDestSwitchId()); @@ -91,7 +88,7 @@ private void proceedPathPair( } private void proceedPath( - FlowSyncFsm stateMachine, String flowId, FlowPath path, + F stateMachine, String flowId, FlowPath path, FlowSegmentRequestFactoriesSequence notIngressSequence, FlowSegmentRequestFactoriesSequence ingressSequence) { List chunks = Arrays.asList( @@ -102,7 +99,7 @@ private void proceedPath( } private void proceedPathPairSkippingIngressChunk( - FlowSyncFsm stateMachine, FlowCommandBuilder commandBuilder, Flow flow, FlowPath path, + F stateMachine, FlowCommandBuilder commandBuilder, Flow flow, FlowPath path, FlowPath oppositePath) { Pair sequences = commandBuilder.buildAllExceptIngressSeverally( @@ -112,18 +109,18 @@ private void proceedPathPairSkippingIngressChunk( } private void proceedPathSkippingIngressChunk( - FlowSyncFsm stateMachine, String flowId, FlowPath path, FlowSegmentRequestFactoriesSequence sequence) { + F stateMachine, String flowId, FlowPath path, FlowSegmentRequestFactoriesSequence sequence) { List chunks = Collections.singletonList( new FlowPathChunk(PathChunkType.NOT_INGRESS, sequence)); FlowPathRequest request = new FlowPathRequest(flowId, path.getPathId(), chunks, false); launchPathInstall(stateMachine, path, request); } - private void launchPathInstall( - FlowSyncFsm stateMachine, FlowPath path, FlowPathRequest request) { - FlowPathOperationDescriptor descriptor = new FlowPathOperationDescriptor(request, path.getStatus()); + private void launchPathInstall(F stateMachine, FlowPath path, FlowPathRequest request) { + final FlowPathStatus initialStatus = path.getStatus(); path.setStatus(FlowPathStatus.IN_PROGRESS); + final FlowSyncCarrier carrier = stateMachine.getCarrier(); try { carrier.launchFlowPathInstallation(request, flowPathOperationConfig, stateMachine.getCommandContext()); } catch (DuplicateKeyException e) { @@ -133,6 +130,6 @@ private void launchPathInstall( e.getMessage()), e); } - stateMachine.addPendingPathOperation(descriptor); + stateMachine.addPendingPathOperation(new FlowPathOperationDescriptor(request, initialStatus)); } } diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/EmitYRulesSyncRequestsAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/EmitYRulesSyncRequestsAction.java new file mode 100644 index 00000000000..1f40fb84852 --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/EmitYRulesSyncRequestsAction.java @@ -0,0 +1,50 @@ +/* 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.flowhs.fsm.sync; + +import org.openkilda.floodlight.api.request.rulemanager.InstallSpeakerCommandsRequest; +import org.openkilda.model.YFlow; +import org.openkilda.persistence.PersistenceManager; +import org.openkilda.rulemanager.RuleManager; +import org.openkilda.wfm.CommandContext; +import org.openkilda.wfm.topology.flowhs.fsm.common.actions.YFlowProcessingAction; +import org.openkilda.wfm.topology.flowhs.fsm.sync.YFlowSyncFsm.Event; +import org.openkilda.wfm.topology.flowhs.fsm.sync.YFlowSyncFsm.State; +import org.openkilda.wfm.topology.flowhs.service.FlowSyncCarrier; +import org.openkilda.wfm.topology.flowhs.utils.YFlowRuleManagerAdapter; + +public class EmitYRulesSyncRequestsAction + extends YFlowProcessingAction { + private final YFlowRuleManagerAdapter ruleManagerAdapter; + + public EmitYRulesSyncRequestsAction( + PersistenceManager persistenceManager, RuleManager ruleManager) { + super(persistenceManager); + + ruleManagerAdapter = new YFlowRuleManagerAdapter(persistenceManager, ruleManager); + } + + @Override + protected void perform(State from, State to, Event event, FlowSyncContext context, YFlowSyncFsm stateMachine) { + YFlow yFlow = getYFlow(stateMachine.getYFlowId()); + CommandContext commandContext = stateMachine.getCommandContext(); + FlowSyncCarrier carrier = stateMachine.getCarrier(); + for (InstallSpeakerCommandsRequest origin : ruleManagerAdapter.buildInstallRequests(yFlow, commandContext)) { + InstallSpeakerCommandsRequest request = origin.toBuilder().failIfExists(false).build(); + carrier.sendSpeakerRequest(request); + } + } +} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FailedCompleteActionBase.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FailedCompleteActionBase.java new file mode 100644 index 00000000000..b4751f0c48b --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FailedCompleteActionBase.java @@ -0,0 +1,103 @@ +/* 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.flowhs.fsm.sync; + +import org.openkilda.messaging.error.ErrorData; +import org.openkilda.messaging.error.ErrorMessage; +import org.openkilda.messaging.error.ErrorType; +import org.openkilda.model.Flow; +import org.openkilda.model.FlowPath; +import org.openkilda.model.FlowPathStatus; +import org.openkilda.model.FlowStatus; +import org.openkilda.persistence.PersistenceManager; +import org.openkilda.wfm.CommandContext; +import org.openkilda.wfm.share.logger.FlowOperationsDashboardLogger; +import org.openkilda.wfm.topology.flowhs.fsm.common.actions.FlowProcessingWithHistorySupportAction; +import org.openkilda.wfm.topology.flowhs.service.FlowSyncCarrier; + +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j +public abstract class FailedCompleteActionBase, S, E> + extends FlowProcessingWithHistorySupportAction { + + protected final FlowOperationsDashboardLogger dashboardLogger; + + public FailedCompleteActionBase( + PersistenceManager persistenceManager, FlowOperationsDashboardLogger dashboardLogger) { + super(persistenceManager); + this.dashboardLogger = dashboardLogger; + } + + @Override + protected void perform(S from, S to, E event, FlowSyncContext context, F stateMachine) { + if (context.getErrorType() != ErrorType.NOT_FOUND) { + updateStatus(stateMachine); + } + + ErrorData error; + if (context.getErrorType() == null) { + error = reportGenericFailure(stateMachine); + } else { + error = reportSpecificFailure( + context.getErrorType(), context.getErrorDetails(), stateMachine.getCommandContext()); + } + + sendResponse(error, stateMachine.getCarrier(), stateMachine.getCommandContext()); + stateMachine.fireNext(context); + } + + protected abstract void updateStatus(F stateMachine); + + protected abstract ErrorData reportGenericFailure(F stateMachine); + + protected abstract ErrorData reportSpecificFailure( + ErrorType errorType, String errorDetails, CommandContext commandContext); + + protected boolean reportIncompletePathOperations(Flow flow) { + List affectedPaths = new ArrayList<>(); + for (FlowPath path : flow.getPaths()) { + if (path.getStatus() != FlowPathStatus.IN_PROGRESS) { + continue; + } + path.setStatus(FlowPathStatus.INACTIVE); + affectedPaths.add(path.getPathId().toString()); + } + + if (affectedPaths.isEmpty()) { + return false; + } + + log.error( + "Flow path(s) \"{}\" stays in {} state after end of sync operation, it should never happens, " + + "forcing flow status to {}", + String.join("\", \"", affectedPaths), FlowStatus.IN_PROGRESS, FlowStatus.DOWN); + return true; + } + + protected void forceFlowDown(Flow flow) { + flow.setStatus(FlowStatus.DOWN); + flow.setStatusInfo("Internal error during SYNC operation"); + } + + private void sendResponse(ErrorData payload, FlowSyncCarrier carrier, CommandContext commandContext) { + carrier.sendNorthboundResponse( + new ErrorMessage(payload, System.currentTimeMillis(), commandContext.getCorrelationId())); + } +} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/actions/FailedCompleteAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowFailedCompleteAction.java similarity index 56% rename from src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/actions/FailedCompleteAction.java rename to src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowFailedCompleteAction.java index b1c592e4975..a1b19eb6ac4 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/actions/FailedCompleteAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowFailedCompleteAction.java @@ -13,56 +13,34 @@ * limitations under the License. */ -package org.openkilda.wfm.topology.flowhs.fsm.sync.actions; +package org.openkilda.wfm.topology.flowhs.fsm.sync; import static java.lang.String.format; import org.openkilda.messaging.error.ErrorData; -import org.openkilda.messaging.error.ErrorMessage; import org.openkilda.messaging.error.ErrorType; import org.openkilda.model.Flow; import org.openkilda.model.FlowStatus; import org.openkilda.persistence.PersistenceManager; import org.openkilda.wfm.CommandContext; import org.openkilda.wfm.share.logger.FlowOperationsDashboardLogger; -import org.openkilda.wfm.topology.flowhs.fsm.common.actions.FlowProcessingWithHistorySupportAction; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncContext; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm; import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm.Event; import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm.State; -import org.openkilda.wfm.topology.flowhs.service.FlowSyncCarrier; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; @Slf4j -public class FailedCompleteAction - extends FlowProcessingWithHistorySupportAction { +public class FlowFailedCompleteAction extends FailedCompleteActionBase { private static final String OPERATION_LEVEL_ERROR_MESSAGE = "Could not sync flow"; - private final FlowSyncCarrier carrier; - private final FlowOperationsDashboardLogger dashboardLogger; - - public FailedCompleteAction( - @NonNull FlowSyncCarrier carrier, @NonNull PersistenceManager persistenceManager, - FlowOperationsDashboardLogger dashboardLogger) { - super(persistenceManager); - - this.carrier = carrier; - this.dashboardLogger = dashboardLogger; + public FlowFailedCompleteAction( + @NonNull PersistenceManager persistenceManager, FlowOperationsDashboardLogger dashboardLogger) { + super(persistenceManager, dashboardLogger); } @Override - protected void perform(State from, State to, Event event, FlowSyncContext context, FlowSyncFsm stateMachine) { - if (context.getErrorType() == null) { - reportGenericFailure(stateMachine); - } else { - reportSpecificFailure(context.getErrorType(), context.getErrorDetails(), stateMachine.getCommandContext()); - } - stateMachine.fireNext(context); - } - - private void reportGenericFailure(FlowSyncFsm stateMachine) { + protected void updateStatus(FlowSyncFsm stateMachine) { Flow flow = getFlow(stateMachine.getFlowId()); final int failCount = stateMachine.getPathOperationFail().size(); @@ -74,6 +52,8 @@ private void reportGenericFailure(FlowSyncFsm stateMachine) { flow.setStatusInfo(String.format( "%d of %d path operations have failed during DANGEROUS sync attempt", failCount, failCount + successCount)); + } else if (reportIncompletePathOperations(flow)) { + forceFlowDown(flow); } else if (FlowStatus.UP != status) { flow.setStatus(status); flow.setStatusInfo(String.format( @@ -84,22 +64,18 @@ private void reportGenericFailure(FlowSyncFsm stateMachine) { } stateMachine.saveActionToHistory(format("The flow status was set to %s", flow.getStatus())); log.error("{} - setting flow \"{}\" status to {}", flow.getStatusInfo(), flow.getFlowId(), flow.getStatus()); - - ErrorData error = new ErrorData( - ErrorType.INTERNAL_ERROR, OPERATION_LEVEL_ERROR_MESSAGE, - format("Failed to sync flow %s", flow.getFlowId())); - sendResponse(error, stateMachine.getCommandContext()); - dashboardLogger.onFailedFlowSync(flow.getFlowId(), failCount, failCount + successCount); } - private void reportSpecificFailure(ErrorType errorType, String errorDetails, CommandContext commandContext) { - ErrorData error = new ErrorData(errorType, OPERATION_LEVEL_ERROR_MESSAGE, errorDetails); - sendResponse(error, commandContext); + @Override + protected ErrorData reportGenericFailure(FlowSyncFsm stateMachine) { + return new ErrorData( + ErrorType.INTERNAL_ERROR, OPERATION_LEVEL_ERROR_MESSAGE, + format("Failed to sync flow %s", stateMachine.getFlowId())); } - private void sendResponse(ErrorData payload, CommandContext commandContext) { - carrier.sendNorthboundResponse( - new ErrorMessage(payload, System.currentTimeMillis(), commandContext.getCorrelationId())); + @Override + protected ErrorData reportSpecificFailure(ErrorType errorType, String errorDetails, CommandContext commandContext) { + return new ErrorData(errorType, OPERATION_LEVEL_ERROR_MESSAGE, errorDetails); } } diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowSyncContext.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowSyncContext.java index 228e5e9d6e7..27bba7b69df 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowSyncContext.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowSyncContext.java @@ -15,6 +15,7 @@ package org.openkilda.wfm.topology.flowhs.fsm.sync; +import org.openkilda.floodlight.api.response.rulemanager.SpeakerCommandResponse; import org.openkilda.messaging.error.ErrorType; import org.openkilda.model.PathId; import org.openkilda.wfm.topology.flowhs.model.path.FlowPathResultCode; @@ -30,4 +31,6 @@ public class FlowSyncContext { PathId pathId; FlowPathResultCode pathResultCode; + + SpeakerCommandResponse speakerRuleResponse; } diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowSyncFsm.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowSyncFsm.java index 73201dbfe60..c72d62cc143 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowSyncFsm.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowSyncFsm.java @@ -15,127 +15,45 @@ package org.openkilda.wfm.topology.flowhs.fsm.sync; -import org.openkilda.model.PathId; import org.openkilda.persistence.PersistenceManager; import org.openkilda.wfm.CommandContext; import org.openkilda.wfm.share.flow.resources.FlowResourcesManager; import org.openkilda.wfm.share.logger.FlowOperationsDashboardLogger; import org.openkilda.wfm.share.utils.FsmExecutor; -import org.openkilda.wfm.topology.flowhs.exception.UnknownKeyException; -import org.openkilda.wfm.topology.flowhs.fsm.FsmUtil; -import org.openkilda.wfm.topology.flowhs.fsm.common.FlowProcessingWithHistorySupportFsm; import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm.Event; import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm.State; -import org.openkilda.wfm.topology.flowhs.fsm.sync.actions.CreateSyncHandlersAction; -import org.openkilda.wfm.topology.flowhs.fsm.sync.actions.FailedCompleteAction; -import org.openkilda.wfm.topology.flowhs.fsm.sync.actions.FlowSyncSetupAction; -import org.openkilda.wfm.topology.flowhs.fsm.sync.actions.PathOperationResponseAction; -import org.openkilda.wfm.topology.flowhs.fsm.sync.actions.SuccessCompleteAction; import org.openkilda.wfm.topology.flowhs.model.path.FlowPathOperationConfig; -import org.openkilda.wfm.topology.flowhs.model.path.FlowPathOperationDescriptor; -import org.openkilda.wfm.topology.flowhs.model.path.FlowPathResultCode; import org.openkilda.wfm.topology.flowhs.service.FlowGenericCarrier; -import org.openkilda.wfm.topology.flowhs.service.FlowProcessingEventListener; import org.openkilda.wfm.topology.flowhs.service.FlowSyncCarrier; import lombok.Getter; import lombok.NonNull; -import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.squirrelframework.foundation.fsm.StateMachineBuilder; import org.squirrelframework.foundation.fsm.StateMachineBuilderFactory; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - @Slf4j -public class FlowSyncFsm - extends FlowProcessingWithHistorySupportFsm< - FlowSyncFsm, State, Event, FlowSyncContext, FlowSyncCarrier, FlowProcessingEventListener> { +public class FlowSyncFsm extends SyncFsmBase { public static final FsmExecutor EXECUTOR = new FsmExecutor<>( Event.NEXT); - @Getter - private final CompletableFuture resultFuture = new CompletableFuture<>(); - - private final Map pendingPathOperations = new HashMap<>(); - - @Getter - private final List pathOperationSuccess = new ArrayList<>(); - @Getter - private final List pathOperationFail = new ArrayList<>(); - @Getter private final String flowId; - /** - * Indicator of dangerous sync operation. - * - *

If sync operation is dangerous customer traffic can be interrupted during installation of flow segments. - * Also, if path operation fails to install all path segments, target flow most probably will become corrupted - - * can't carry customer traffic. And one more side effect of such dangerous operation - in some cases extra rules on - * the switches used by target flow can appear after dangerous sync.

- */ - @Getter @Setter - private boolean dangerousSync = false; - public FlowSyncFsm( @NonNull CommandContext commandContext, @NonNull FlowSyncCarrier carrier, @NonNull String flowId) { - super(Event.NEXT, Event.ERROR, commandContext, carrier); + super(commandContext, carrier, Event.NEXT, Event.ERROR); this.flowId = flowId; } - // -- public API -- - - public void handlePathOperationResult(PathId pathId, FlowPathResultCode resultCode) { - handleEvent(Event.PATH_OPERATION_RESPONSE, FlowSyncContext.builder() - .pathId(pathId) - .pathResultCode(resultCode) - .build()); - } - - public void handleTimeout() { - handleEvent(Event.TIMEOUT, FlowSyncContext.builder().build()); - } - - public boolean isPendingPathOperationsExists() { - return ! pendingPathOperations.isEmpty(); - } - - public void addPendingPathOperation(FlowPathOperationDescriptor descriptor) { - pendingPathOperations.put(descriptor.getPathId(), descriptor); - } - - public Optional addSuccessPathOperation(PathId pathId) { - return commitFlowPathOperation(pathOperationSuccess, pathId); - } - - public Optional addFailedPathOperation(PathId pathId) { - return commitFlowPathOperation(pathOperationFail, pathId); - } - // -- FSM actions -- public void enterCancelAction(State from, State to, Event event, FlowSyncContext context) { - for (PathId entry : pendingPathOperations.keySet()) { - log.info("Cancel path sync(install) operation for {} (flowId={})", entry, flowId); - try { - getCarrier().cancelFlowPathOperation(entry); - } catch (UnknownKeyException e) { - log.error("Path {} sync operation is missing (flowId={})", entry, flowId); - addFailedPathOperation(entry); - } - } - - checkPendingOperations(context); + cancelPendingPathOperations(context); } public void checkPendingOperationsAction(State from, State to, Event event, FlowSyncContext context) { - checkPendingOperations(context); + continueIfNoPendingPathOperations(context); } public void reportGlobalTimeoutAction(State from, State to, Event event, FlowSyncContext context) { @@ -144,29 +62,29 @@ public void reportGlobalTimeoutAction(State from, State to, Event event, FlowSyn // -- private/service methods -- - private void checkPendingOperations(FlowSyncContext context) { - if (! isPendingPathOperationsExists()) { - handleEvent(Event.GUARD_PASSED, context); - } + @Override + protected void injectPathOperationResultEvent(FlowSyncContext context) { + injectEvent(Event.PATH_OPERATION_RESPONSE, context); } - protected void handleEvent(Event event, FlowSyncContext context) { - EXECUTOR.fire(this, event, context); + @Override + void handlePathSyncFailure(FlowSyncContext context) { + fire(Event.SYNC_FAIL, context); } - private void onComplete() { - resultFuture.complete(null); + @Override + public void injectTimeoutEvent(FlowSyncContext context) { + injectEvent(Event.TIMEOUT, context); } - private Optional commitFlowPathOperation( - List target, PathId pathId) { - FlowPathOperationDescriptor descriptor = pendingPathOperations.remove(pathId); - if (descriptor == null) { - return Optional.empty(); - } + @Override + protected void allPathOperationsIsOverProceedToTheNextStep(FlowSyncContext context) { + fire(Event.GUARD_PASSED, context); + } - target.add(descriptor); - return Optional.of(descriptor); + @Override + protected void injectEvent(Event event, FlowSyncContext context) { + EXECUTOR.fire(this, event, context); } @Override @@ -174,6 +92,11 @@ protected String getCrudActionName() { return "sync"; } + @Override + protected State getFinishedState() { + return State.FINISHED; + } + // -- FSM definition -- public static class Factory { @@ -192,8 +115,8 @@ public Factory( final FlowOperationsDashboardLogger dashboardLogger = new FlowOperationsDashboardLogger(log); - final PathOperationResponseAction pathOperationResponseAction = new PathOperationResponseAction( - persistenceManager); + final PathOperationResponseAction pathOperationResponseAction = + new PathOperationResponseAction<>(persistenceManager); final String reportGlobalTimeoutActionMethod = "reportGlobalTimeoutAction"; final String checkPendingOperationsActionMethod = "checkPendingOperationsAction"; @@ -205,8 +128,8 @@ public Factory( // SYNC builder.onEntry(State.SYNC) - .perform(new CreateSyncHandlersAction( - carrier, persistenceManager, resourcesManager, flowPathOperationConfig)); + .perform(new CreateSyncHandlersAction<>( + persistenceManager, resourcesManager, flowPathOperationConfig)); builder.transition().from(State.SYNC).to(State.SYNC_FAIL).on(Event.SYNC_FAIL); builder.transition().from(State.SYNC).to(State.COMMIT_SUCCESS).on(Event.GUARD_PASSED); builder.transition().from(State.SYNC).to(State.CANCEL).on(Event.ERROR); @@ -238,13 +161,13 @@ public Factory( // COMMIT_SUCCESS builder.onEntry(State.COMMIT_SUCCESS) - .perform(new SuccessCompleteAction(carrier, persistenceManager, dashboardLogger)); + .perform(new FlowSyncSuccessCompleteAction(persistenceManager, dashboardLogger)); builder.transition().from(State.COMMIT_SUCCESS).to(State.FINISHED).on(Event.NEXT); builder.transition().from(State.COMMIT_SUCCESS).to(State.COMMIT_ERROR).on(Event.ERROR); // COMMIT_ERROR builder.onEntry(State.COMMIT_ERROR) - .perform(new FailedCompleteAction(carrier, persistenceManager, dashboardLogger)); + .perform(new FlowFailedCompleteAction(persistenceManager, dashboardLogger)); builder.transition().from(State.COMMIT_ERROR).to(State.FINISHED_WITH_ERROR).on(Event.NEXT); // final @@ -254,12 +177,7 @@ public Factory( public FlowSyncFsm newInstance(@NonNull String flowId, @NonNull CommandContext commandContext) { FlowSyncFsm instance = builder.newStateMachine(State.SETUP, commandContext, carrier, flowId); - - addExecutionTimeMeter(instance); - addCompleteNotification(instance); - instance.start(FlowSyncContext.builder().build()); - return instance; } } @@ -268,27 +186,11 @@ public enum State { SETUP, SYNC, SYNC_FAIL, CANCEL, COMMIT_SUCCESS, COMMIT_ERROR, - FINISHED, FINISHED_WITH_ERROR, + FINISHED, FINISHED_WITH_ERROR } public enum Event { NEXT, GUARD_PASSED, ERROR, TIMEOUT, SYNC_FAIL, PATH_OPERATION_RESPONSE } - - private static void addExecutionTimeMeter(FlowSyncFsm fsm) { - FsmUtil.addExecutionTimeMeter(fsm, () -> successResultAdapter(fsm)); - } - - private static void addCompleteNotification(FlowSyncFsm fsm) { - fsm.addTerminateListener(dummy -> fsm.onComplete()); - } - - private static Boolean successResultAdapter(FlowSyncFsm fsm) { - if (fsm.isTerminated()) { - return fsm.getCurrentState() == State.FINISHED; - } - - return null; - } } diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowSyncSetupAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowSyncSetupAction.java new file mode 100644 index 00000000000..a2dd9f416c0 --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowSyncSetupAction.java @@ -0,0 +1,44 @@ +/* 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.flowhs.fsm.sync; + +import org.openkilda.model.Flow; +import org.openkilda.persistence.PersistenceManager; +import org.openkilda.wfm.share.logger.FlowOperationsDashboardLogger; +import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm.Event; +import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm.State; + +import lombok.extern.slf4j.Slf4j; + +import java.util.Collections; +import java.util.List; + +@Slf4j +public class FlowSyncSetupAction extends SyncSetupActionBase { + public FlowSyncSetupAction(PersistenceManager persistenceManager, FlowOperationsDashboardLogger dashboardLogger) { + super(persistenceManager, dashboardLogger); + } + + @Override + protected Flow loadContainer(FlowSyncFsm stateMachine) { + return getFlow(stateMachine.getFlowId()); + } + + @Override + protected List collectAffectedFlows(Flow container) { + return Collections.singletonList(container); + } +} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/actions/SuccessCompleteAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowSyncSuccessCompleteAction.java similarity index 54% rename from src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/actions/SuccessCompleteAction.java rename to src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowSyncSuccessCompleteAction.java index 1681a9b089f..f57b6e7b171 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/actions/SuccessCompleteAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/FlowSyncSuccessCompleteAction.java @@ -13,20 +13,16 @@ * limitations under the License. */ -package org.openkilda.wfm.topology.flowhs.fsm.sync.actions; +package org.openkilda.wfm.topology.flowhs.fsm.sync; import org.openkilda.messaging.info.InfoMessage; import org.openkilda.messaging.info.flow.FlowRerouteResponse; import org.openkilda.model.Flow; import org.openkilda.model.FlowPath; -import org.openkilda.model.FlowStatus; import org.openkilda.persistence.PersistenceManager; import org.openkilda.wfm.CommandContext; import org.openkilda.wfm.share.logger.FlowOperationsDashboardLogger; import org.openkilda.wfm.share.mappers.FlowPathMapper; -import org.openkilda.wfm.topology.flowhs.fsm.common.actions.FlowProcessingWithHistorySupportAction; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncContext; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm; import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm.Event; import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm.State; import org.openkilda.wfm.topology.flowhs.service.FlowSyncCarrier; @@ -35,42 +31,24 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -public class SuccessCompleteAction - extends FlowProcessingWithHistorySupportAction { - private final FlowSyncCarrier carrier; - private final FlowOperationsDashboardLogger dashboardLogger; - - public SuccessCompleteAction( - FlowSyncCarrier carrier, @NonNull PersistenceManager persistenceManager, - @NonNull FlowOperationsDashboardLogger dashboardLogger) { - super(persistenceManager); - - this.carrier = carrier; - this.dashboardLogger = dashboardLogger; +public class FlowSyncSuccessCompleteAction extends SuccessCompleteActionBase { + public FlowSyncSuccessCompleteAction( + @NonNull PersistenceManager persistenceManager, @NonNull FlowOperationsDashboardLogger dashboardLogger) { + super(persistenceManager, dashboardLogger); } @Override - protected void perform(State from, State to, Event event, FlowSyncContext context, FlowSyncFsm stateMachine) { + protected void updateStatus(FlowSyncFsm stateMachine) { Flow flow = getFlow(stateMachine.getFlowId()); - FlowStatus status = flow.computeFlowStatus(); - flow.setStatus(status); - if (status == FlowStatus.UP) { - flow.setStatusInfo(null); - } else if (status == FlowStatus.DEGRADED) { - log.debug("Keep flow {} into {} status", flow.getFlowId(), status); - } else { + if (! updateFlowStatus(flow)) { stateMachine.fireError(); - return; } - sendResponse(flow.getForwardPath(), stateMachine.getCommandContext()); - + sendResponse(flow.getForwardPath(), stateMachine.getCarrier(), stateMachine.getCommandContext()); dashboardLogger.onSuccessfulFlowSync(flow.getFlowId()); - - stateMachine.fireNext(context); } - private void sendResponse(FlowPath path, CommandContext commandContext) { + private void sendResponse(FlowPath path, FlowSyncCarrier carrier, CommandContext commandContext) { // Setting "rerouted" payload field into false, because paths are always kept unchanged now FlowRerouteResponse payload = new FlowRerouteResponse(FlowPathMapper.INSTANCE.map(path), false); carrier.sendNorthboundResponse( diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/actions/PathOperationResponseAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/PathOperationResponseAction.java similarity index 59% rename from src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/actions/PathOperationResponseAction.java rename to src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/PathOperationResponseAction.java index 0cf51be1e60..dcfb617bf49 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/actions/PathOperationResponseAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/PathOperationResponseAction.java @@ -13,59 +13,56 @@ * limitations under the License. */ -package org.openkilda.wfm.topology.flowhs.fsm.sync.actions; +package org.openkilda.wfm.topology.flowhs.fsm.sync; import org.openkilda.model.FlowPathStatus; import org.openkilda.model.PathId; import org.openkilda.persistence.PersistenceManager; import org.openkilda.wfm.topology.flowhs.fsm.common.actions.FlowProcessingWithHistorySupportAction; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncContext; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm.Event; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm.State; import org.openkilda.wfm.topology.flowhs.model.path.FlowPathOperationDescriptor; +import org.openkilda.wfm.topology.flowhs.model.path.FlowPathReference; import org.openkilda.wfm.topology.flowhs.model.path.FlowPathResultCode; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; @Slf4j -public class PathOperationResponseAction - extends FlowProcessingWithHistorySupportAction { +public class PathOperationResponseAction, S, E> + extends FlowProcessingWithHistorySupportAction { public PathOperationResponseAction(@NonNull PersistenceManager persistenceManager) { super(persistenceManager); } @Override - protected void perform(State from, State to, Event event, FlowSyncContext context, FlowSyncFsm stateMachine) { + protected void perform(S from, S to, E event, FlowSyncContext context, F stateMachine) { FlowPathResultCode resultCode = context.getPathResultCode(); PathId pathId = context.getPathId(); if (resultCode == FlowPathResultCode.SUCCESS) { handleSuccess( - stateMachine, stateMachine.addSuccessPathOperation(pathId) + stateMachine.addSuccessPathOperation(pathId) .orElseThrow(() -> newMissingPendingOperationError(pathId, resultCode))); } else { FlowPathOperationDescriptor descriptor = stateMachine.addFailedPathOperation(pathId) .orElseThrow(() -> newMissingPendingOperationError(pathId, resultCode)); - handleFailure(stateMachine, descriptor); - stateMachine.fire(Event.SYNC_FAIL, context); + handleFailure(descriptor); + stateMachine.handlePathSyncFailure(context); } - if (! stateMachine.isPendingPathOperationsExists()) { - stateMachine.fire(Event.GUARD_PASSED, context); - } + stateMachine.continueIfNoPendingPathOperations(context); } - private void handleSuccess(FlowSyncFsm stateMachine, FlowPathOperationDescriptor descriptor) { - log.info("Flow path {} have been synced (flowId={})", descriptor.getPathId(), stateMachine.getFlowId()); - flowPathRepository.updateStatus(descriptor.getPathId(), FlowPathStatus.ACTIVE); + private void handleSuccess(FlowPathOperationDescriptor descriptor) { + FlowPathReference reference = descriptor.getRequest().getReference(); + log.info("Flow path {} have been synced (flowId={})", reference.getPathId(), reference.getFlowId()); + flowPathRepository.updateStatus(reference.getPathId(), FlowPathStatus.ACTIVE); } - private void handleFailure(FlowSyncFsm stateMachine, FlowPathOperationDescriptor descriptor) { + private void handleFailure(FlowPathOperationDescriptor descriptor) { + FlowPathReference reference = descriptor.getRequest().getReference(); log.error( "Failed to sync flow path {}, restore it's status to initial value {} (flowId={})", - descriptor.getPathId(), descriptor.getInitialStatus(), stateMachine.getFlowId()); - flowPathRepository.updateStatus(descriptor.getPathId(), descriptor.getInitialStatus()); + reference.getPathId(), descriptor.getInitialStatus(), reference.getFlowId()); + flowPathRepository.updateStatus(reference.getPathId(), descriptor.getInitialStatus()); } private IllegalStateException newMissingPendingOperationError(PathId pathId, FlowPathResultCode resultCode) { diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/SuccessCompleteActionBase.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/SuccessCompleteActionBase.java new file mode 100644 index 00000000000..9e57a259836 --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/SuccessCompleteActionBase.java @@ -0,0 +1,64 @@ +/* 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.flowhs.fsm.sync; + +import org.openkilda.model.Flow; +import org.openkilda.model.FlowStatus; +import org.openkilda.persistence.PersistenceManager; +import org.openkilda.wfm.share.logger.FlowOperationsDashboardLogger; +import org.openkilda.wfm.topology.flowhs.fsm.common.actions.FlowProcessingWithHistorySupportAction; + +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public abstract class SuccessCompleteActionBase, S, E> + extends FlowProcessingWithHistorySupportAction { + + protected final FlowOperationsDashboardLogger dashboardLogger; + + public SuccessCompleteActionBase( + @NonNull PersistenceManager persistenceManager, @NonNull FlowOperationsDashboardLogger dashboardLogger) { + super(persistenceManager); + + this.dashboardLogger = dashboardLogger; + } + + @Override + protected void perform(S from, S to, E event, FlowSyncContext context, F stateMachine) { + updateStatus(stateMachine); + stateMachine.fireNext(context); + } + + protected abstract void updateStatus(F stateMachine); + + protected boolean updateFlowStatus(Flow flow) { + FlowStatus status = flow.computeFlowStatus(); + flow.setStatus(status); + if (status == FlowStatus.UP) { + flow.setStatusInfo(null); + } else if (status == FlowStatus.DEGRADED) { + flow.setStatusInfo(String.format( + "Final flow %s status after SYNC operation is %s", flow.getFlowId(), status)); + } else { + // error reporting action will fill all required info + return false; + } + + // final flow status is within expected boundaries + return true; + } +} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/SyncFsmBase.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/SyncFsmBase.java new file mode 100644 index 00000000000..108365928a8 --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/SyncFsmBase.java @@ -0,0 +1,170 @@ +/* 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.flowhs.fsm.sync; + +import org.openkilda.model.PathId; +import org.openkilda.wfm.CommandContext; +import org.openkilda.wfm.topology.flowhs.exception.UnknownKeyException; +import org.openkilda.wfm.topology.flowhs.fsm.FsmUtil; +import org.openkilda.wfm.topology.flowhs.fsm.common.FlowProcessingWithHistorySupportFsm; +import org.openkilda.wfm.topology.flowhs.model.path.FlowPathOperationDescriptor; +import org.openkilda.wfm.topology.flowhs.model.path.FlowPathReference; +import org.openkilda.wfm.topology.flowhs.model.path.FlowPathResultCode; +import org.openkilda.wfm.topology.flowhs.service.FlowProcessingEventListener; +import org.openkilda.wfm.topology.flowhs.service.FlowSyncCarrier; + +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +public abstract class SyncFsmBase, S, E> + extends FlowProcessingWithHistorySupportFsm { + + @Getter + private final Set targets = new HashSet<>(); + + @Getter + private final CompletableFuture resultFuture = new CompletableFuture<>(); + + /** + * Indicator of dangerous sync operation. + * + *

If sync operation is dangerous customer traffic can be interrupted during installation of flow segments. + * Also, if path operation fails to install all path segments, target flow most probably will become corrupted - + * can't carry customer traffic. And one more side effect of such dangerous operation - in some cases extra rules on + * the switches used by target flow can appear after dangerous sync.

+ */ + @Getter @Setter + private boolean dangerousSync = false; + + @Getter + private final List pathOperationSuccess = new ArrayList<>(); + @Getter + private final List pathOperationFail = new ArrayList<>(); + + private final Map pendingPathOperations = new HashMap<>(); + + public SyncFsmBase( + @NonNull CommandContext commandContext, @NonNull FlowSyncCarrier carrier, E nextEvent, E errorEvent) { + super(nextEvent, errorEvent, commandContext, carrier); + + FsmUtil.addExecutionTimeMeter(this, this::isSuccessfullyCompleted); + addTerminateListener(dummy -> onComplete()); + } + + /** + * Handle path operation result. + */ + public void handlePathOperationResult(PathId pathId, FlowPathResultCode resultCode) { + injectPathOperationResultEvent(FlowSyncContext.builder() + .pathId(pathId) + .pathResultCode(resultCode) + .build()); + } + + public void handleTimeout() { + injectTimeoutEvent(FlowSyncContext.builder().build()); + } + + void continueIfNoPendingPathOperations(FlowSyncContext context) { + if (! isPendingPathOperationsExists()) { + allPathOperationsIsOverProceedToTheNextStep(context); + } + } + + protected abstract void injectPathOperationResultEvent(FlowSyncContext context); + + protected abstract void injectTimeoutEvent(FlowSyncContext context); + + abstract void handlePathSyncFailure(FlowSyncContext context); + + protected void cancelPendingPathOperations(FlowSyncContext context) { + // must create independent list, because pendingPathOperations can be modified inside loop body + Collection pendingOperations = new ArrayList<>(pendingPathOperations.values()); + for (FlowPathOperationDescriptor entry : pendingOperations) { + FlowPathReference reference = entry.getRequest().getReference(); + PathId pathId = reference.getPathId(); + String flowId = reference.getFlowId(); + + log.info("Cancel path sync(install) operation for {} (flowId={})", pathId, flowId); + try { + getCarrier().cancelFlowPathOperation(pathId); + } catch (UnknownKeyException e) { + log.error("Path {} sync operation is missing (flowId={})", pathId, flowId); + addFailedPathOperation(pathId); + } + } + + continueIfNoPendingPathOperations(context); + } + + public boolean isPendingPathOperationsExists() { + return !pendingPathOperations.isEmpty(); + } + + public void addPendingPathOperation(FlowPathOperationDescriptor descriptor) { + pendingPathOperations.put(descriptor.getPathId(), descriptor); + } + + public Optional addSuccessPathOperation(PathId pathId) { + return commitFlowPathOperation(pathOperationSuccess, pathId); + } + + public Optional addFailedPathOperation(PathId pathId) { + return commitFlowPathOperation(pathOperationFail, pathId); + } + + protected void onComplete() { + resultFuture.complete(null); + } + + protected abstract void injectEvent(E event, FlowSyncContext context); + + private Optional commitFlowPathOperation( + List target, PathId pathId) { + FlowPathOperationDescriptor descriptor = pendingPathOperations.remove(pathId); + if (descriptor == null) { + return Optional.empty(); + } + + target.add(descriptor); + return Optional.of(descriptor); + } + + protected Boolean isSuccessfullyCompleted() { + if (isTerminated()) { + return getCurrentState() == getFinishedState(); + } + + return null; + } + + protected abstract void allPathOperationsIsOverProceedToTheNextStep(FlowSyncContext context); + + protected abstract S getFinishedState(); +} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/actions/FlowSyncSetupAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/SyncSetupActionBase.java similarity index 64% rename from src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/actions/FlowSyncSetupAction.java rename to src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/SyncSetupActionBase.java index e076529ac72..268af94e971 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/actions/FlowSyncSetupAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/SyncSetupActionBase.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package org.openkilda.wfm.topology.flowhs.fsm.sync.actions; +package org.openkilda.wfm.topology.flowhs.fsm.sync; import static java.lang.String.format; @@ -25,27 +25,28 @@ import org.openkilda.wfm.share.logger.FlowOperationsDashboardLogger; import org.openkilda.wfm.topology.flowhs.exception.FlowProcessingException; import org.openkilda.wfm.topology.flowhs.fsm.common.actions.FlowProcessingWithHistorySupportAction; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncContext; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm.Event; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm.State; import lombok.extern.slf4j.Slf4j; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + @Slf4j -public class FlowSyncSetupAction - extends FlowProcessingWithHistorySupportAction { - private final FlowOperationsDashboardLogger dashboardLogger; +public abstract class SyncSetupActionBase, S, E, T> + extends FlowProcessingWithHistorySupportAction { + + protected final FlowOperationsDashboardLogger dashboardLogger; - public FlowSyncSetupAction(PersistenceManager persistenceManager, FlowOperationsDashboardLogger dashboardLogger) { + public SyncSetupActionBase(PersistenceManager persistenceManager, FlowOperationsDashboardLogger dashboardLogger) { super(persistenceManager); this.dashboardLogger = dashboardLogger; } @Override - protected void perform(State from, State to, Event event, FlowSyncContext context, FlowSyncFsm stateMachine) { + protected void perform(S from, S to, E event, FlowSyncContext context, F stateMachine) { try { - transactionManager.doInTransaction(() -> transaction(stateMachine)); + transactionManager.doInTransaction(() -> transaction(stateMachine, loadContainer(stateMachine))); stateMachine.fireNext(context); } catch (FlowProcessingException e) { FlowSyncContext errorContext = FlowSyncContext.builder() @@ -56,21 +57,30 @@ protected void perform(State from, State to, Event event, FlowSyncContext contex } } + protected void transaction(F stateMachine, T container) { + boolean isPotentiallyDestructiveSync = false; + Set targets = new HashSet<>(); + for (Flow flow : collectAffectedFlows(container)) { + dashboardLogger.onFlowSync(flow.getFlowId()); + stateMachine.saveNewEventToHistory( + "Started flow paths sync", FlowEventData.Event.SYNC, FlowEventData.Initiator.NB, + "Performing flow paths sync operation on NB request"); - private void transaction(FlowSyncFsm stateMachine) { - Flow flow = getFlow(stateMachine.getFlowId()); - - dashboardLogger.onFlowSync(stateMachine.getFlowId()); - stateMachine.saveNewEventToHistory( - "Started flow paths sync", FlowEventData.Event.SYNC, FlowEventData.Initiator.NB, - "Performing flow paths sync operation on NB request"); + ensureNoCollision(flow); - ensureNoCollision(flow); + targets.add(flow.getFlowId()); + flow.setStatus(FlowStatus.IN_PROGRESS); + isPotentiallyDestructiveSync = isPotentiallyDestructiveSync || applyFlowPostponedChanges(flow); + } - flow.setStatus(FlowStatus.IN_PROGRESS); - stateMachine.setDangerousSync(applyFlowPostponedChanges(flow)); + stateMachine.getTargets().addAll(targets); + stateMachine.setDangerousSync(isPotentiallyDestructiveSync); } + protected abstract T loadContainer(F stateMachine); + + protected abstract List collectAffectedFlows(T container); + private void ensureNoCollision(Flow flow) { if (flow.getStatus() == FlowStatus.IN_PROGRESS) { String message = format("Flow %s is in progress now", flow.getFlowId()); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/YFlowFailedCompleteAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/YFlowFailedCompleteAction.java new file mode 100644 index 00000000000..16936f9b5aa --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/YFlowFailedCompleteAction.java @@ -0,0 +1,103 @@ +/* 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.flowhs.fsm.sync; + +import static java.lang.String.format; + +import org.openkilda.messaging.error.ErrorData; +import org.openkilda.messaging.error.ErrorType; +import org.openkilda.model.Flow; +import org.openkilda.model.FlowStatus; +import org.openkilda.model.YFlow; +import org.openkilda.persistence.PersistenceManager; +import org.openkilda.persistence.repositories.YFlowRepository; +import org.openkilda.wfm.CommandContext; +import org.openkilda.wfm.share.logger.FlowOperationsDashboardLogger; +import org.openkilda.wfm.topology.flowhs.exception.FlowProcessingException; +import org.openkilda.wfm.topology.flowhs.fsm.sync.YFlowSyncFsm.Event; +import org.openkilda.wfm.topology.flowhs.fsm.sync.YFlowSyncFsm.State; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class YFlowFailedCompleteAction extends FailedCompleteActionBase { + private static final String OPERATION_LEVEL_ERROR_MESSAGE = "Could not sync y-flow"; + + private final YFlowRepository yFlowRepository; + + public YFlowFailedCompleteAction( + PersistenceManager persistenceManager, FlowOperationsDashboardLogger dashboardLogger) { + super(persistenceManager, dashboardLogger); + yFlowRepository = persistenceManager.getRepositoryFactory().createYFlowRepository(); + } + + @Override + protected void updateStatus(YFlowSyncFsm stateMachine) { + String yFlowId = stateMachine.getYFlowId(); + for (String flowId : stateMachine.getTargets()) { + Flow flow = getFlow(flowId); + if (reportIncompletePathOperations(flow)) { + forceFlowDown(flow); + continue; + } + + FlowStatus status = flow.computeFlowStatus(); + flow.setStatus(status); + if (status == FlowStatus.UP) { + flow.setStatusInfo(null); + } else { + flow.setStatusInfo(String.format( + "Set status to %s as a result of Y-flow \"%s\" sync operation", status, yFlowId)); + } + } + + YFlow yFlow = yFlowRepository.findById(yFlowId) + .orElseThrow(() -> new FlowProcessingException(ErrorType.NOT_FOUND, + format("Y-flow %s not found", yFlowId))); + if (stateMachine.isDangerousSync()) { + log.error( + "Force Y-flow \"{}\" status to {} due to errors during destructive sync operations", + yFlow.getYFlowId(), FlowStatus.DOWN); + yFlow.setStatus(FlowStatus.DOWN); + } else { + yFlow.recalculateStatus(); + if (yFlow.getStatus() == FlowStatus.UP) { + log.error( + "Calculated Y-flow \"{}\" status is {}, but due to errors during sync operation forcing " + + "it to {} (actual status of flow\'s rules is unknown)", + yFlowId, FlowStatus.UP, FlowStatus.DEGRADED); + yFlow.setStatus(FlowStatus.DEGRADED); + } else { + log.error( + "Set Y-flow \"{}\" status to {} as result of failed sync operation", + yFlowId, yFlow.getStatus()); + + } + } + } + + @Override + protected ErrorData reportGenericFailure(YFlowSyncFsm stateMachine) { + return new ErrorData( + ErrorType.INTERNAL_ERROR, OPERATION_LEVEL_ERROR_MESSAGE, + format("Failed to sync Y-flow %s", stateMachine.getYFlowId())); + } + + @Override + protected ErrorData reportSpecificFailure(ErrorType errorType, String errorDetails, CommandContext commandContext) { + return new ErrorData(errorType, OPERATION_LEVEL_ERROR_MESSAGE, errorDetails); + } +} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/YFlowSyncFsm.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/YFlowSyncFsm.java new file mode 100644 index 00000000000..1e492096573 --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/YFlowSyncFsm.java @@ -0,0 +1,345 @@ +/* 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.flowhs.fsm.sync; + +import org.openkilda.floodlight.api.request.SpeakerRequest; +import org.openkilda.floodlight.api.response.rulemanager.SpeakerCommandResponse; +import org.openkilda.messaging.Message; +import org.openkilda.model.PathId; +import org.openkilda.persistence.PersistenceManager; +import org.openkilda.rulemanager.RuleManager; +import org.openkilda.wfm.CommandContext; +import org.openkilda.wfm.share.flow.resources.FlowResourcesManager; +import org.openkilda.wfm.share.history.model.FlowHistoryHolder; +import org.openkilda.wfm.share.logger.FlowOperationsDashboardLogger; +import org.openkilda.wfm.share.utils.FsmExecutor; +import org.openkilda.wfm.topology.flowhs.exception.DuplicateKeyException; +import org.openkilda.wfm.topology.flowhs.exception.UnknownKeyException; +import org.openkilda.wfm.topology.flowhs.fsm.sync.YFlowSyncFsm.Event; +import org.openkilda.wfm.topology.flowhs.fsm.sync.YFlowSyncFsm.State; +import org.openkilda.wfm.topology.flowhs.model.path.FlowPathOperationConfig; +import org.openkilda.wfm.topology.flowhs.model.path.FlowPathRequest; +import org.openkilda.wfm.topology.flowhs.service.FlowGenericCarrier; +import org.openkilda.wfm.topology.flowhs.service.FlowSyncCarrier; + +import lombok.Getter; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.squirrelframework.foundation.fsm.StateMachineBuilder; +import org.squirrelframework.foundation.fsm.StateMachineBuilderFactory; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +@Slf4j +public class YFlowSyncFsm extends SyncFsmBase { + public static final FsmExecutor EXECUTOR = new FsmExecutor<>( + Event.NEXT); + + @Getter + private final String yFlowId; + + private final Set pendingSpeakerCommands = new HashSet<>(); + + private final FlowSyncCarrier decoratedCarrier; + + public YFlowSyncFsm( + @NonNull CommandContext commandContext, @NonNull Supplier carrierSupplier, + @NonNull String yFlowId) { + super(commandContext, carrierSupplier.get(), Event.NEXT, Event.ERROR); + this.yFlowId = yFlowId; + decoratedCarrier = new CarrierDecorator(carrierSupplier.get()); + } + + public void handleSpeakerResponse(SpeakerCommandResponse response) { + if (! pendingSpeakerCommands.remove(response.getCommandId())) { + log.error( + "Got speaker response, but it's missing in pending requests list" + + " (commandId={}, pending-commands-set={})", + response.getCommandId(), + pendingSpeakerCommands.stream().sorted() + .map(UUID::toString) + .collect(Collectors.joining(", ", "{", "}"))); + return; + } + + FlowSyncContext context = FlowSyncContext.builder() + .speakerRuleResponse(response) + .build(); + Event event = response.isSuccess() ? Event.SPEAKER_RESPONSE : Event.SPEAKER_ERROR_RESPONSE; + log.debug( + "Y-flow \"{}\" sync got {} speaker command with id={}", + yFlowId, event == Event.SPEAKER_RESPONSE ? "success" : "failed", response.getCommandId()); + injectEvent(event, context); + } + + @Override + protected void injectPathOperationResultEvent(FlowSyncContext context) { + injectEvent(Event.PATH_OPERATION_RESPONSE, context); + } + + @Override + public void injectTimeoutEvent(FlowSyncContext context) { + injectEvent(Event.TIMEOUT, context); + } + + @Override + public FlowSyncCarrier getCarrier() { + return decoratedCarrier; + } + + // -- FSM actions -- + + public void enterCancelAction(State from, State to, Event event, FlowSyncContext context) { + cancelPendingPathOperations(context); + } + + public void checkPendingPathOperationsAction(State from, State to, Event event, FlowSyncContext context) { + continueIfNoPendingPathOperations(context); + } + + public void processSuccessSpeakerResponseAction(State from, State to, Event event, FlowSyncContext context) { + log.info( + "Y-flow \"{}\" sync got success response on speaker command (id={})", + yFlowId, context.getSpeakerRuleResponse().getCommandId()); + continueIfNoPendingSpeakerCommands(context); + } + + public void processFailedSpeakerResponseAction(State from, State to, Event event, FlowSyncContext context) { + SpeakerCommandResponse response = context.getSpeakerRuleResponse(); + String errors = response.getFailedCommandIds().entrySet().stream() + .map(entry -> String.format("%s: %s", entry.getKey(), entry.getValue())) + .collect(Collectors.joining("),(", "(", ")")); + log.error( + "Y-flow \"{}\" sync got failed response on speaker command: {} {}", + yFlowId, response.getCommandId(), errors); + continueIfNoPendingSpeakerCommands(context); + } + + public void reportGlobalTimeoutAction(State from, State to, Event event, FlowSyncContext context) { + saveGlobalTimeoutToHistory(); + } + + // -- private/service methods -- + + @Override + void handlePathSyncFailure(FlowSyncContext context) { + fire(Event.PATH_OPERATION_FAIL, context); + } + + @Override + protected void allPathOperationsIsOverProceedToTheNextStep(FlowSyncContext context) { + fire(Event.GUARD_PASSED, context); + } + + @Override + protected void injectEvent(Event event, FlowSyncContext context) { + EXECUTOR.fire(this, event, context); + } + + private void continueIfNoPendingSpeakerCommands(FlowSyncContext context) { + if (pendingSpeakerCommands.isEmpty()) { + fire(Event.GUARD_PASSED, context); + } + } + + // only to satisfy parents requirements + @Override + public String getFlowId() { + return yFlowId; + } + + @Override + protected String getCrudActionName() { + return "y-sync"; + } + + @Override + protected State getFinishedState() { + return State.FINISHED; + } + + private class CarrierDecorator implements FlowSyncCarrier { + private final FlowSyncCarrier target; + + public CarrierDecorator(FlowSyncCarrier target) { + this.target = target; + } + + @Override + public void sendSpeakerRequest(SpeakerRequest command) { + pendingSpeakerCommands.add(command.getCommandId()); + target.sendSpeakerRequest(command); + } + + @Override + public void sendPeriodicPingNotification(String flowId, boolean enabled) { + target.sendPeriodicPingNotification(flowId, enabled); + } + + @Override + public void sendHistoryUpdate(FlowHistoryHolder historyHolder) { + target.sendHistoryUpdate(historyHolder); + } + + @Override + public void cancelTimeoutCallback(String key) { + target.cancelTimeoutCallback(key); + } + + @Override + public void sendInactive() { + target.sendInactive(); + } + + @Override + public void sendNorthboundResponse(Message message) { + target.sendNorthboundResponse(message); + } + + @Override + public void launchFlowPathInstallation( + @NonNull FlowPathRequest request, @NonNull FlowPathOperationConfig config, + @NonNull CommandContext commandContext) throws DuplicateKeyException { + target.launchFlowPathInstallation(request, config, commandContext); + } + + @Override + public void cancelFlowPathOperation(PathId pathId) throws UnknownKeyException { + target.cancelFlowPathOperation(pathId); + } + } + + // -- FSM definition -- + + public static class Factory { + private final StateMachineBuilder builder; + + public Factory( + @NonNull PersistenceManager persistenceManager, + @NonNull FlowResourcesManager resourcesManager, + @NonNull RuleManager ruleManager, @NonNull FlowPathOperationConfig flowPathOperationConfig) { + builder = StateMachineBuilderFactory.create( + YFlowSyncFsm.class, State.class, Event.class, FlowSyncContext.class, + CommandContext.class, Supplier.class, String.class); + + final FlowOperationsDashboardLogger dashboardLogger = new FlowOperationsDashboardLogger(log); + + final PathOperationResponseAction pathOperationResponseAction = + new PathOperationResponseAction<>(persistenceManager); + final String reportGlobalTimeoutActionMethod = "reportGlobalTimeoutAction"; + final String checkPendingPathOperationsActionMethod = "checkPendingPathOperationsAction"; + final String processSuccessSpeakerResponseActionMethod = "processSuccessSpeakerResponseAction"; + final String processFailedSpeakerResponseActionMethod = "processFailedSpeakerResponseAction"; + + // SETUP + builder.onEntry(State.SETUP) + .perform(new YFlowSyncSetupAction(persistenceManager, dashboardLogger)); + builder.transition().from(State.SETUP).to(State.SYNC).on(Event.NEXT); + builder.transition().from(State.SETUP).to(State.COMMIT_ERROR).on(Event.ERROR); + + // SYNC + builder.onEntry(State.SYNC) + .perform(new CreateSyncHandlersAction<>( + persistenceManager, resourcesManager, flowPathOperationConfig)); + builder.transition().from(State.SYNC).to(State.SYNC_FAIL).on(Event.PATH_OPERATION_FAIL); + builder.transition().from(State.SYNC).to(State.SYNC_Y_RULES).on(Event.GUARD_PASSED); + builder.transition().from(State.SYNC).to(State.CANCEL).on(Event.ERROR); + builder.transition().from(State.SYNC).to(State.CANCEL).on(Event.TIMEOUT) + .callMethod(reportGlobalTimeoutActionMethod); + builder.internalTransition().within(State.SYNC).on(Event.PATH_OPERATION_RESPONSE) + .perform(pathOperationResponseAction); + + // SYNC_Y_RULES + builder.onEntry(State.SYNC_Y_RULES) + .perform(new EmitYRulesSyncRequestsAction(persistenceManager, ruleManager)); + builder.transition().from(State.SYNC_Y_RULES).to(State.COMMIT_SUCCESS).on(Event.GUARD_PASSED); + builder.transition().from(State.SYNC_Y_RULES).to(State.COMMIT_ERROR).on(Event.TIMEOUT) + .callMethod(reportGlobalTimeoutActionMethod); + builder.transition().from(State.SYNC_Y_RULES).to(State.SYNC_Y_RULES_FAIL).on(Event.SPEAKER_ERROR_RESPONSE); + builder.internalTransition().within(State.SYNC_Y_RULES).on(Event.SPEAKER_RESPONSE) + .callMethod(processSuccessSpeakerResponseActionMethod); + + // CANCEL + builder.onEntry(State.CANCEL) + .callMethod("enterCancelAction"); + builder.transition().from(State.CANCEL).to(State.COMMIT_ERROR).on(Event.GUARD_PASSED); + builder.internalTransition().within(State.CANCEL).on(Event.PATH_OPERATION_RESPONSE) + .perform(pathOperationResponseAction); + builder.internalTransition().within(State.CANCEL).on(Event.ERROR) + .callMethod(checkPendingPathOperationsActionMethod); + builder.internalTransition().within(State.CANCEL).on(Event.TIMEOUT) + .callMethod(reportGlobalTimeoutActionMethod); + + // SYNC_FAIL + builder.onEntry(State.SYNC_FAIL) + .callMethod(checkPendingPathOperationsActionMethod); + builder.transition().from(State.SYNC_FAIL).to(State.COMMIT_ERROR).on(Event.GUARD_PASSED); + builder.transition().from(State.SYNC_FAIL).to(State.CANCEL).on(Event.ERROR); + builder.transition().from(State.SYNC_FAIL).to(State.CANCEL).on(Event.TIMEOUT) + .callMethod(reportGlobalTimeoutActionMethod); + builder.internalTransition().within(State.SYNC_FAIL).on(Event.PATH_OPERATION_RESPONSE) + .perform(pathOperationResponseAction); + + // SYNC_Y_RULES_FAIL + builder.onEntry(State.SYNC_Y_RULES_FAIL) + .callMethod(processFailedSpeakerResponseActionMethod); + builder.transition().from(State.SYNC_Y_RULES_FAIL).to(State.COMMIT_ERROR).on(Event.GUARD_PASSED); + builder.transition().from(State.SYNC_Y_RULES_FAIL).to(State.COMMIT_ERROR).on(Event.TIMEOUT) + .callMethod(reportGlobalTimeoutActionMethod); + builder.internalTransition().within(State.SYNC_Y_RULES_FAIL).on(Event.SPEAKER_RESPONSE) + .callMethod(processSuccessSpeakerResponseActionMethod); + builder.internalTransition().within(State.SYNC_Y_RULES_FAIL).on(Event.SPEAKER_ERROR_RESPONSE) + .callMethod(processFailedSpeakerResponseActionMethod); + + // COMMIT_SUCCESS + builder.onEntry(State.COMMIT_SUCCESS) + .perform(new YFlowSyncSuccessCompleteAction(persistenceManager, dashboardLogger)); + builder.transition().from(State.COMMIT_SUCCESS).to(State.FINISHED).on(Event.NEXT); + builder.transition().from(State.COMMIT_SUCCESS).to(State.COMMIT_ERROR).on(Event.ERROR); + + // COMMIT_ERROR + builder.onEntry(State.COMMIT_ERROR) + .perform(new YFlowFailedCompleteAction(persistenceManager, dashboardLogger)); + builder.transition().from(State.COMMIT_ERROR).to(State.FINISHED_WITH_ERROR).on(Event.NEXT); + + // final + builder.defineFinalState(State.FINISHED); + builder.defineFinalState(State.FINISHED_WITH_ERROR); + } + + public YFlowSyncFsm newInstance( + Supplier carrierSupplier, String flowId, CommandContext commandContext) { + return builder.newStateMachine(State.SETUP, commandContext, carrierSupplier, flowId); + } + } + + public enum State { + SETUP, + SYNC, SYNC_FAIL, SYNC_Y_RULES, SYNC_Y_RULES_FAIL, CANCEL, + COMMIT_SUCCESS, COMMIT_ERROR, + FINISHED, FINISHED_WITH_ERROR + } + + public enum Event { + NEXT, ERROR, TIMEOUT, + PATH_OPERATION_RESPONSE, SPEAKER_RESPONSE, SPEAKER_ERROR_RESPONSE, GUARD_PASSED, + PATH_OPERATION_FAIL + } +} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/YFlowSyncSetupAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/YFlowSyncSetupAction.java new file mode 100644 index 00000000000..457116d8ad3 --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/YFlowSyncSetupAction.java @@ -0,0 +1,70 @@ +/* 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.flowhs.fsm.sync; + +import static java.lang.String.format; + +import org.openkilda.messaging.error.ErrorType; +import org.openkilda.model.Flow; +import org.openkilda.model.FlowStatus; +import org.openkilda.model.YFlow; +import org.openkilda.model.YSubFlow; +import org.openkilda.persistence.PersistenceManager; +import org.openkilda.persistence.repositories.YFlowRepository; +import org.openkilda.wfm.share.logger.FlowOperationsDashboardLogger; +import org.openkilda.wfm.topology.flowhs.exception.FlowProcessingException; +import org.openkilda.wfm.topology.flowhs.fsm.sync.YFlowSyncFsm.Event; +import org.openkilda.wfm.topology.flowhs.fsm.sync.YFlowSyncFsm.State; + +import java.util.List; +import java.util.stream.Collectors; + +public class YFlowSyncSetupAction extends SyncSetupActionBase { + private final YFlowRepository yFlowRepository; + + public YFlowSyncSetupAction(PersistenceManager persistenceManager, FlowOperationsDashboardLogger dashboardLogger) { + super(persistenceManager, dashboardLogger); + this.yFlowRepository = persistenceManager.getRepositoryFactory().createYFlowRepository(); + } + + @Override + protected void transaction(YFlowSyncFsm stateMachine, YFlow container) { + ensureNoCollision(container); + super.transaction(stateMachine, container); + } + + @Override + protected YFlow loadContainer(YFlowSyncFsm stateMachine) { + String yFlowId = stateMachine.getYFlowId(); + return yFlowRepository.findById(yFlowId) + .orElseThrow(() -> new FlowProcessingException(ErrorType.NOT_FOUND, + format("Y-flow %s not found", yFlowId))); + } + + @Override + protected List collectAffectedFlows(YFlow container) { + return container.getSubFlows().stream() + .map(YSubFlow::getFlow) + .collect(Collectors.toList()); + } + + private void ensureNoCollision(YFlow yFlow) { + if (yFlow.getStatus() == FlowStatus.IN_PROGRESS) { + String message = format("Y-Flow %s is in progress now", yFlow.getYFlowId()); + throw new FlowProcessingException(ErrorType.REQUEST_INVALID, message); + } + } +} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/YFlowSyncSuccessCompleteAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/YFlowSyncSuccessCompleteAction.java new file mode 100644 index 00000000000..a08e9b86530 --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/sync/YFlowSyncSuccessCompleteAction.java @@ -0,0 +1,88 @@ +/* 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.flowhs.fsm.sync; + +import static java.lang.String.format; + +import org.openkilda.messaging.command.yflow.YFlowRerouteResponse; +import org.openkilda.messaging.error.ErrorType; +import org.openkilda.messaging.info.InfoMessage; +import org.openkilda.model.Flow; +import org.openkilda.model.FlowStatus; +import org.openkilda.model.YFlow; +import org.openkilda.persistence.PersistenceManager; +import org.openkilda.persistence.repositories.YFlowRepository; +import org.openkilda.wfm.CommandContext; +import org.openkilda.wfm.share.logger.FlowOperationsDashboardLogger; +import org.openkilda.wfm.topology.flowhs.exception.FlowProcessingException; +import org.openkilda.wfm.topology.flowhs.fsm.sync.YFlowSyncFsm.Event; +import org.openkilda.wfm.topology.flowhs.fsm.sync.YFlowSyncFsm.State; +import org.openkilda.wfm.topology.flowhs.model.yflow.YFlowPaths; +import org.openkilda.wfm.topology.flowhs.service.FlowSyncCarrier; +import org.openkilda.wfm.topology.flowhs.utils.YFlowUtils; + +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class YFlowSyncSuccessCompleteAction extends SuccessCompleteActionBase { + private final YFlowRepository yFlowRepository; + private final YFlowUtils utils; + + public YFlowSyncSuccessCompleteAction( + @NonNull PersistenceManager persistenceManager, @NonNull FlowOperationsDashboardLogger dashboardLogger) { + super(persistenceManager, dashboardLogger); + yFlowRepository = persistenceManager.getRepositoryFactory().createYFlowRepository(); + utils = new YFlowUtils(persistenceManager); + } + + @Override + protected void updateStatus(YFlowSyncFsm stateMachine) { + boolean isError = false; + for (String flowId : stateMachine.getTargets()) { + Flow flow = getFlow(flowId); + isError = ! updateFlowStatus(flow) || isError; + } + + if (isError) { + stateMachine.fireError(); + return; + } + + String yFlowId = stateMachine.getYFlowId(); + YFlow yFlow = yFlowRepository.findById(yFlowId) + .orElseThrow(() -> new FlowProcessingException(ErrorType.NOT_FOUND, + format("Y-flow %s not found", yFlowId))); + yFlow.recalculateStatus(); + if (yFlow.getStatus() == FlowStatus.UP) { + log.info("Y-Flow \"{}\" have been successfully synchronized", yFlowId); + } else if (yFlow.getStatus() == FlowStatus.DEGRADED) { + log.warn("Y-Flow \"{}\" synchronization is incomplete, final status is {}", yFlowId, FlowStatus.DEGRADED); + } else { + stateMachine.fireError(); + } + + sendResponse(yFlow, stateMachine.getCarrier(), stateMachine.getCommandContext()); + } + + private void sendResponse(YFlow yFlow, FlowSyncCarrier carrier, CommandContext commandContext) { + YFlowPaths paths = utils.definePaths(yFlow); + YFlowRerouteResponse response = new YFlowRerouteResponse( + paths.getSharedPath(), paths.getSubFlowPaths(), false); + carrier.sendNorthboundResponse(new InfoMessage(response, commandContext.getCreateTime(), + commandContext.getCorrelationId())); + } +} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/OnSubFlowAllocatedAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/OnSubFlowAllocatedAction.java index c4be5daa15d..56288a966b3 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/OnSubFlowAllocatedAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/OnSubFlowAllocatedAction.java @@ -16,49 +16,37 @@ package org.openkilda.wfm.topology.flowhs.fsm.yflow.reroute.actions; import static java.lang.String.format; -import static java.util.Collections.emptyList; import org.openkilda.messaging.Message; -import org.openkilda.messaging.command.yflow.SubFlowPathDto; import org.openkilda.messaging.command.yflow.YFlowRerouteResponse; -import org.openkilda.messaging.error.ErrorType; import org.openkilda.messaging.info.InfoMessage; -import org.openkilda.messaging.info.event.PathInfoData; -import org.openkilda.model.Flow; -import org.openkilda.model.FlowPath; -import org.openkilda.model.SwitchId; -import org.openkilda.model.YFlow; -import org.openkilda.model.YSubFlow; import org.openkilda.persistence.PersistenceManager; import org.openkilda.persistence.repositories.RepositoryFactory; import org.openkilda.persistence.repositories.YFlowRepository; import org.openkilda.wfm.CommandContext; -import org.openkilda.wfm.share.mappers.FlowPathMapper; -import org.openkilda.wfm.share.service.IntersectionComputer; -import org.openkilda.wfm.topology.flowhs.exception.FlowProcessingException; import org.openkilda.wfm.topology.flowhs.fsm.common.actions.NbTrackableWithHistorySupportAction; import org.openkilda.wfm.topology.flowhs.fsm.yflow.reroute.YFlowRerouteContext; import org.openkilda.wfm.topology.flowhs.fsm.yflow.reroute.YFlowRerouteFsm; import org.openkilda.wfm.topology.flowhs.fsm.yflow.reroute.YFlowRerouteFsm.Event; import org.openkilda.wfm.topology.flowhs.fsm.yflow.reroute.YFlowRerouteFsm.State; +import org.openkilda.wfm.topology.flowhs.model.yflow.YFlowPaths; +import org.openkilda.wfm.topology.flowhs.utils.YFlowUtils; import lombok.extern.slf4j.Slf4j; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; @Slf4j public class OnSubFlowAllocatedAction extends NbTrackableWithHistorySupportAction { private final YFlowRepository yFlowRepository; + private final YFlowUtils utils; public OnSubFlowAllocatedAction(PersistenceManager persistenceManager) { super(persistenceManager); RepositoryFactory repositoryFactory = persistenceManager.getRepositoryFactory(); - this.yFlowRepository = repositoryFactory.createYFlowRepository(); + yFlowRepository = repositoryFactory.createYFlowRepository(); + utils = new YFlowUtils(persistenceManager); } @Override @@ -84,43 +72,12 @@ protected Optional performWithResponse(State from, State to, Event even } private Message buildRerouteResponseMessage(YFlowRerouteFsm stateMachine) { - String yFlowId = stateMachine.getYFlowId(); - List oldFlowPathCookies = stateMachine.getOldYFlowPathCookies(); - List flowPaths = transactionManager.doInTransaction(() -> { - YFlow yflow = yFlowRepository.findById(yFlowId) - .orElseThrow(() -> new FlowProcessingException(ErrorType.NOT_FOUND, - format("Y-flow %s not found", yFlowId))); - SwitchId sharedSwitchId = yflow.getSharedEndpoint().getSwitchId(); - List paths = new ArrayList<>(); - for (YSubFlow subFlow : yflow.getSubFlows()) { - Flow flow = subFlow.getFlow(); - FlowPath flowPath = flow.getPaths().stream() - .filter(path -> sharedSwitchId.equals(path.getSrcSwitchId()) - && !path.isProtected() - && !oldFlowPathCookies.contains(path.getCookie().getValue())) - .findFirst() - .orElse(sharedSwitchId.equals(flow.getForwardPath().getSrcSwitchId()) ? flow.getForwardPath() - : flow.getReversePath()); - paths.add(flowPath); - } - return paths; - }); - - List nonEmptyPaths = flowPaths.stream() - .filter(fp -> !fp.getSegments().isEmpty()).collect(Collectors.toList()); - PathInfoData sharedPath = FlowPathMapper.INSTANCE.map(nonEmptyPaths.size() >= 2 - ? IntersectionComputer.calculatePathIntersectionFromSource(nonEmptyPaths) : emptyList()); - - List subFlowPathDtos = flowPaths.stream() - .map(flowPath -> new SubFlowPathDto(flowPath.getFlowId(), FlowPathMapper.INSTANCE.map(flowPath))) - .sorted(Comparator.comparing(SubFlowPathDto::getFlowId)) - .collect(Collectors.toList()); - - PathInfoData oldSharedPath = stateMachine.getOldSharedPath(); - List oldSubFlowPathDtos = stateMachine.getOldSubFlowPathDtos(); - - YFlowRerouteResponse response = new YFlowRerouteResponse(sharedPath, subFlowPathDtos, - !(sharedPath.equals(oldSharedPath) && subFlowPathDtos.equals(oldSubFlowPathDtos))); + YFlowPaths paths = utils.definePaths(stateMachine.getYFlowId(), stateMachine.getOldYFlowPathCookies()); + boolean rerouted = !( + paths.getSharedPath().equals(stateMachine.getOldSharedPath()) + && paths.getSubFlowPaths().equals(stateMachine.getOldSubFlowPathDtos())); + YFlowRerouteResponse response = new YFlowRerouteResponse( + paths.getSharedPath(), paths.getSubFlowPaths(), rerouted); CommandContext commandContext = stateMachine.getCommandContext(); return new InfoMessage(response, commandContext.getCreateTime(), commandContext.getCorrelationId()); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/model/yflow/YFlowPaths.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/model/yflow/YFlowPaths.java new file mode 100644 index 00000000000..61bbaf34def --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/model/yflow/YFlowPaths.java @@ -0,0 +1,36 @@ +/* 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.flowhs.model.yflow; + +import org.openkilda.messaging.command.yflow.SubFlowPathDto; +import org.openkilda.messaging.info.event.PathInfoData; + +import lombok.Builder; +import lombok.NonNull; +import lombok.Singular; +import lombok.Value; + +import java.util.List; + +@Value +@Builder +public class YFlowPaths { + @NonNull + PathInfoData sharedPath; + + @NonNull @Singular + List subFlowPaths; +} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/FlowSyncService.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/FlowSyncService.java index e6baee0deec..fc2c71309fc 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/FlowSyncService.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/FlowSyncService.java @@ -16,20 +16,13 @@ package org.openkilda.wfm.topology.flowhs.service; import org.openkilda.messaging.command.flow.FlowSyncRequest; -import org.openkilda.messaging.error.ErrorData; -import org.openkilda.messaging.error.ErrorMessage; -import org.openkilda.messaging.error.ErrorType; import org.openkilda.persistence.PersistenceManager; import org.openkilda.wfm.CommandContext; import org.openkilda.wfm.share.flow.resources.FlowResourcesManager; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncContext; import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm; -import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncFsm.Event; import org.openkilda.wfm.topology.flowhs.model.path.FlowPathOperationConfig; import org.openkilda.wfm.topology.flowhs.model.path.FlowPathReference; -import org.openkilda.wfm.topology.flowhs.model.path.FlowPathResultCode; -import org.openkilda.wfm.topology.flowhs.service.common.FlowProcessingFsmRegister; -import org.openkilda.wfm.topology.flowhs.service.common.FlowProcessingService; +import org.openkilda.wfm.topology.flowhs.service.common.SyncServiceBase; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; @@ -37,68 +30,35 @@ import java.util.Optional; @Slf4j -public class FlowSyncService extends FlowProcessingService, FlowProcessingEventListener> { +public class FlowSyncService extends SyncServiceBase { private final FlowSyncFsm.Factory handlerFactory; public FlowSyncService( @NonNull FlowSyncCarrier carrier, @NonNull PersistenceManager persistenceManager, @NonNull FlowResourcesManager flowResourcesManager, @NonNull FlowPathOperationConfig flowPathOperationConfig) { - super(new FlowProcessingFsmRegister<>(), FlowSyncFsm.EXECUTOR, carrier, persistenceManager); + super(FlowSyncFsm.EXECUTOR, carrier, persistenceManager); handlerFactory = new FlowSyncFsm.Factory( carrier, persistenceManager, flowResourcesManager, flowPathOperationConfig); } - /** - * Handle flow sync request. - */ - public void handleRequest(String requestKey, FlowSyncRequest request, CommandContext commandContext) { - String flowId = request.getFlowId(); - log.debug("Handling flow reroute request with key {} and flow ID: {}", requestKey, flowId); - - // Because of field grouping specific flowId goes into specific bolt instance, so we can do such checks - if (fsmRegister.hasRegisteredFsmWithFlowId(flowId)) { - ErrorData payload = new ErrorData( - ErrorType.BUSY, "Overlapping flow sync requests", - String.format("Flow %s are doing \"sync\" already", flowId)); - carrier.sendNorthboundResponse(new ErrorMessage( - payload, commandContext.getCreateTime(), commandContext.getCorrelationId())); - return; - } - - FlowSyncFsm handler = handlerFactory.newInstance(request.getFlowId(), commandContext); - fsmRegister.registerFsm(requestKey, handler); - handler.getResultFuture() - .thenAccept(dummy -> onComplete(requestKey)); + @Override + protected void handleRequest(String requestKey, String targetId, CommandContext commandContext) { + log.debug("Handling flow sync request with key {} and flow ID: {}", requestKey, targetId); + super.handleRequest(requestKey, targetId, commandContext); } - /** - * Handle global operation timeout. - */ - public void handleTimeout(String requestKey) { - fsmRegister.getFsmByKey(requestKey) - .ifPresent(FlowSyncFsm::handleTimeout); + public void handleRequest(String requestKey, FlowSyncRequest request, CommandContext commandContext) { + handleRequest(requestKey, request.getFlowId(), commandContext); } - /** - * Handle flow path sync(install) operation results. - */ - public void handlePathSyncResponse(FlowPathReference reference, FlowPathResultCode result) { - Optional handler = fsmRegister.getFsmByFlowId(reference.getFlowId()); - if (handler.isPresent()) { - handler.get().handlePathOperationResult(reference.getPathId(), result); - } else { - log.error("Got path sync result for {} but there is no relative sync handler", reference); - } + @Override + protected Optional lookupHandler(FlowPathReference reference) { + return fsmRegister.getFsmByFlowId(reference.getFlowId()); } - private void onComplete(String requestKey) { - fsmRegister.unregisterFsm(requestKey); - carrier.cancelTimeoutCallback(requestKey); - - if (!isActive() && !fsmRegister.hasAnyRegisteredFsm()) { - carrier.sendInactive(); - } + @Override + protected FlowSyncFsm newHandler(String flowId, CommandContext commandContext) { + return handlerFactory.newInstance(flowId, commandContext); } } diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/YFlowSyncService.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/YFlowSyncService.java new file mode 100644 index 00000000000..5e1dc670b5d --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/YFlowSyncService.java @@ -0,0 +1,159 @@ +/* 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.flowhs.service; + +import org.openkilda.floodlight.api.request.SpeakerRequest; +import org.openkilda.floodlight.api.response.rulemanager.SpeakerCommandResponse; +import org.openkilda.messaging.Message; +import org.openkilda.messaging.command.yflow.YFlowSyncRequest; +import org.openkilda.model.PathId; +import org.openkilda.persistence.PersistenceManager; +import org.openkilda.rulemanager.RuleManager; +import org.openkilda.wfm.CommandContext; +import org.openkilda.wfm.share.flow.resources.FlowResourcesManager; +import org.openkilda.wfm.share.history.model.FlowHistoryHolder; +import org.openkilda.wfm.topology.flowhs.exception.DuplicateKeyException; +import org.openkilda.wfm.topology.flowhs.exception.UnknownKeyException; +import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncContext; +import org.openkilda.wfm.topology.flowhs.fsm.sync.YFlowSyncFsm; +import org.openkilda.wfm.topology.flowhs.model.path.FlowPathOperationConfig; +import org.openkilda.wfm.topology.flowhs.model.path.FlowPathReference; +import org.openkilda.wfm.topology.flowhs.model.path.FlowPathRequest; +import org.openkilda.wfm.topology.flowhs.service.common.SyncServiceBase; +import org.openkilda.wfm.topology.flowhs.utils.HandlerResponseMapping; + +import lombok.Getter; +import lombok.NonNull; + +import java.util.Optional; +import java.util.UUID; +import java.util.function.Function; + +public class YFlowSyncService extends SyncServiceBase { + private final YFlowSyncFsm.Factory handlerFactory; + + private final HandlerResponseMapping + pathResponseMapping = new HandlerResponseMapping<>(); + private final HandlerResponseMapping speakerResponseMapping = new HandlerResponseMapping<>(); + + public YFlowSyncService( + @NonNull FlowSyncCarrier carrier, @NonNull PersistenceManager persistenceManager, + @NonNull FlowResourcesManager flowResourcesManager, + RuleManager ruleManager, @NonNull FlowPathOperationConfig flowPathOperationConfig) { + super(YFlowSyncFsm.EXECUTOR, carrier, persistenceManager); + handlerFactory = new YFlowSyncFsm.Factory( + persistenceManager, flowResourcesManager, ruleManager, flowPathOperationConfig); + } + + @Override + protected void handleRequest(String requestKey, String targetId, CommandContext commandContext) { + log.debug("Handling Y-flow sync request with key {} and flow ID: {}", requestKey, targetId); + super.handleRequest(requestKey, targetId, commandContext); + } + + public void handleRequest(String requestKey, YFlowSyncRequest request, CommandContext commandContext) { + handleRequest(requestKey, request.getYFlowId(), commandContext); + } + + /** + * Handle speaker response. + */ + public boolean handleSpeakerResponse(SpeakerCommandResponse response) { + Optional mapping = speakerResponseMapping.lookupAndRemove(response.getCommandId()); + if (mapping.isPresent()) { + mapping.get().handleSpeakerResponse(response); + return true; + } + return false; + } + + @Override + protected Optional lookupHandler(FlowPathReference reference) { + return pathResponseMapping.lookupAndRemove(reference); + } + + @Override + protected YFlowSyncFsm newHandler(String flowId, CommandContext commandContext) { + CarrierDecorator decoratedCarrier = new CarrierDecorator( + carrier, substitute -> handlerFactory.newInstance(() -> substitute, flowId, commandContext)); + YFlowSyncFsm handler = decoratedCarrier.getHandler(); + handler.start(FlowSyncContext.builder().build()); + return handler; + } + + @Override + protected void onComplete(YFlowSyncFsm handler, String requestKey) { + pathResponseMapping.remove(handler); + speakerResponseMapping.remove(handler); + super.onComplete(handler, requestKey); + } + + private class CarrierDecorator implements FlowSyncCarrier { + private final FlowSyncCarrier target; + + @Getter + private final YFlowSyncFsm handler; + + public CarrierDecorator(FlowSyncCarrier target, Function handlerBuilder) { + this.target = target; + this.handler = handlerBuilder.apply(this); + } + + @Override + public void sendSpeakerRequest(SpeakerRequest command) { + speakerResponseMapping.add(command.getCommandId(), getHandler()); + target.sendSpeakerRequest(command); + } + + @Override + public void sendPeriodicPingNotification(String flowId, boolean enabled) { + target.sendPeriodicPingNotification(flowId, enabled); + } + + @Override + public void launchFlowPathInstallation( + @NonNull FlowPathRequest request, @NonNull FlowPathOperationConfig config, + @NonNull CommandContext commandContext) throws DuplicateKeyException { + pathResponseMapping.add(request.getReference(), getHandler()); + target.launchFlowPathInstallation(request, config, commandContext); + } + + @Override + public void cancelFlowPathOperation(PathId pathId) throws UnknownKeyException { + target.cancelFlowPathOperation(pathId); + } + + @Override + public void sendHistoryUpdate(FlowHistoryHolder historyHolder) { + target.sendHistoryUpdate(historyHolder); + } + + @Override + public void cancelTimeoutCallback(String key) { + target.cancelTimeoutCallback(key); + } + + @Override + public void sendInactive() { + target.sendInactive(); + } + + @Override + public void sendNorthboundResponse(Message message) { + target.sendNorthboundResponse(message); + } + } +} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/common/SyncServiceBase.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/common/SyncServiceBase.java new file mode 100644 index 00000000000..b7608adbdcc --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/common/SyncServiceBase.java @@ -0,0 +1,102 @@ +/* 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.flowhs.service.common; + +import org.openkilda.messaging.error.ErrorData; +import org.openkilda.messaging.error.ErrorMessage; +import org.openkilda.messaging.error.ErrorType; +import org.openkilda.persistence.PersistenceManager; +import org.openkilda.wfm.CommandContext; +import org.openkilda.wfm.share.utils.FsmExecutor; +import org.openkilda.wfm.topology.flowhs.fsm.sync.FlowSyncContext; +import org.openkilda.wfm.topology.flowhs.fsm.sync.SyncFsmBase; +import org.openkilda.wfm.topology.flowhs.model.path.FlowPathReference; +import org.openkilda.wfm.topology.flowhs.model.path.FlowPathResultCode; +import org.openkilda.wfm.topology.flowhs.service.FlowProcessingEventListener; +import org.openkilda.wfm.topology.flowhs.service.FlowSyncCarrier; + +import lombok.NonNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; + +public abstract class SyncServiceBase, E> + extends FlowProcessingService, FlowProcessingEventListener> { + protected Logger log; + + public SyncServiceBase( + @NonNull FsmExecutor fsmExecutor, + @NonNull FlowSyncCarrier carrier, + @NonNull PersistenceManager persistenceManager) { + super(new FlowProcessingFsmRegister<>(), fsmExecutor, carrier, persistenceManager); + log = LoggerFactory.getLogger(getClass()); + } + + /** + * Handle sync request. + */ + protected void handleRequest(String requestKey, String targetId, CommandContext commandContext) { + // Because of field grouping specific flowId goes into specific bolt instance, so we can do such checks + if (fsmRegister.hasRegisteredFsmWithFlowId(targetId)) { + ErrorData payload = new ErrorData( + ErrorType.BUSY, "Overlapping flow sync requests", + String.format("Flow %s are doing \"sync\" already", targetId)); + carrier.sendNorthboundResponse(new ErrorMessage( + payload, commandContext.getCreateTime(), commandContext.getCorrelationId())); + return; + } + + F handler = newHandler(targetId, commandContext); + fsmRegister.registerFsm(requestKey, handler); + handler.getResultFuture() + .thenAccept(dummy -> onComplete(handler, requestKey)); + } + + /** + * Handle flow path sync(install) operation results. + */ + public void handlePathSyncResponse(FlowPathReference reference, FlowPathResultCode result) { + Optional handler = lookupHandler(reference); + if (handler.isPresent()) { + handler.get().handlePathOperationResult(reference.getPathId(), result); + } else { + log.error("Got path sync result for {} but there is no relative sync handler", reference); + } + } + + /** + * Handle global operation timeout. + */ + public void handleTimeout(String requestKey) { + fsmRegister.getFsmByKey(requestKey) + .ifPresent(handler -> handler.handleTimeout()); + } + + protected void onComplete(F handler, String requestKey) { + fsmRegister.unregisterFsm(requestKey); + carrier.cancelTimeoutCallback(requestKey); + + if (!isActive() && !fsmRegister.hasAnyRegisteredFsm()) { + carrier.sendInactive(); + } + } + + protected abstract Optional lookupHandler(FlowPathReference reference); + + protected abstract F newHandler(String flowId, CommandContext commandContext); +} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/path/FlowPathService.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/path/FlowPathService.java index b56315ae3d7..ba906ece537 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/path/FlowPathService.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/path/FlowPathService.java @@ -110,7 +110,7 @@ private void onComplete( String operationName, String requestKey, FlowPathOperation operation, FlowPathResult result) { log.debug( "Flow path {} operation with reference {} completed with result {}", - operation.getPathReference(), operationName, operation.getResultCode()); + operationName, operation.getPathReference(), operation.getResultCode()); registry.remove(requestKey); carrier.processFlowPathOperationResults(result); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/utils/HandlerResponseMapping.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/utils/HandlerResponseMapping.java new file mode 100644 index 00000000000..212e55765d1 --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/utils/HandlerResponseMapping.java @@ -0,0 +1,54 @@ +/* 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.flowhs.utils; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class HandlerResponseMapping { + private final Map mapping = new HashMap<>(); + private final Map> reverse = new HashMap<>(); + + public void add(R reference, H handler) { + mapping.put(reference, handler); + reverse.computeIfAbsent(handler, key -> new HashSet<>()).add(reference); + } + + /** + * Lookup handler by reference, remove existing mapping after lookup. + */ + public Optional lookupAndRemove(R reference) { + H handler = mapping.remove(reference); + if (handler != null) { + reverse.getOrDefault(handler, Collections.emptySet()).remove(reference); + return Optional.of(handler); + } + return Optional.empty(); + } + + /** + * Remove mappings for specified handler. + */ + public void remove(H handler) { + for (R entry : reverse.getOrDefault(handler, Collections.emptySet())) { + mapping.remove(entry); + } + } +} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/utils/YFlowRuleManagerAdapter.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/utils/YFlowRuleManagerAdapter.java new file mode 100644 index 00000000000..f0d21b3898e --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/utils/YFlowRuleManagerAdapter.java @@ -0,0 +1,87 @@ +/* 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.flowhs.utils; + +import static java.util.stream.Collectors.toList; + +import org.openkilda.floodlight.api.request.rulemanager.DeleteSpeakerCommandsRequest; +import org.openkilda.floodlight.api.request.rulemanager.InstallSpeakerCommandsRequest; +import org.openkilda.model.FlowPath; +import org.openkilda.model.PathId; +import org.openkilda.model.SwitchId; +import org.openkilda.model.YFlow; +import org.openkilda.model.YSubFlow; +import org.openkilda.persistence.PersistenceManager; +import org.openkilda.rulemanager.DataAdapter; +import org.openkilda.rulemanager.RuleManager; +import org.openkilda.rulemanager.SpeakerData; +import org.openkilda.rulemanager.adapter.PersistenceDataAdapter; +import org.openkilda.wfm.CommandContext; +import org.openkilda.wfm.topology.flowhs.fsm.common.converters.FlowRulesConverter; + +import com.google.common.collect.Sets; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class YFlowRuleManagerAdapter { + private final PersistenceManager persistenceManager; + private final RuleManager ruleManager; + + public YFlowRuleManagerAdapter(PersistenceManager persistenceManager, RuleManager ruleManager) { + this.persistenceManager = persistenceManager; + this.ruleManager = ruleManager; + } + + public Collection buildInstallRequests(YFlow yFlow, CommandContext context) { + Map> speakerData = buildSpeakerData(yFlow); + return FlowRulesConverter.INSTANCE.buildFlowInstallCommands(speakerData, context); + } + + public Collection buildDeleteRequests(YFlow yFlow, CommandContext context) { + Map> speakerData = buildSpeakerData(yFlow); + return FlowRulesConverter.INSTANCE.buildFlowDeleteCommands(speakerData, context); + } + + private Map> buildSpeakerData(YFlow yFlow) { + Set pathIds = yFlow.getSubFlows().stream() + .map(YSubFlow::getFlow) + .flatMap(flow -> Stream.of(flow.getForwardPathId(), flow.getReversePathId(), + flow.getProtectedForwardPathId(), flow.getProtectedReversePathId())) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + Set switchIds = Sets.newHashSet(yFlow.getSharedEndpoint().getSwitchId(), yFlow.getYPoint(), + yFlow.getProtectedPathYPoint()); + DataAdapter dataAdapter = PersistenceDataAdapter.builder() + .persistenceManager(persistenceManager) + .switchIds(switchIds) + .pathIds(pathIds) + .build(); + List flowPaths = new ArrayList<>(dataAdapter.getFlowPaths().values()); + + return ruleManager.buildRulesForYFlow(flowPaths, dataAdapter).stream() + .collect(Collectors.groupingBy(SpeakerData::getSwitchId, + Collectors.mapping(Function.identity(), toList()))); + } +} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/utils/YFlowUtils.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/utils/YFlowUtils.java new file mode 100644 index 00000000000..06dcb49ad8d --- /dev/null +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/utils/YFlowUtils.java @@ -0,0 +1,108 @@ +/* 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.flowhs.utils; + +import static java.lang.String.format; +import static java.util.Collections.emptyList; + +import org.openkilda.messaging.command.yflow.SubFlowPathDto; +import org.openkilda.messaging.error.ErrorType; +import org.openkilda.messaging.info.event.PathInfoData; +import org.openkilda.model.Flow; +import org.openkilda.model.FlowPath; +import org.openkilda.model.SwitchId; +import org.openkilda.model.YFlow; +import org.openkilda.model.YSubFlow; +import org.openkilda.persistence.PersistenceManager; +import org.openkilda.persistence.repositories.RepositoryFactory; +import org.openkilda.persistence.repositories.YFlowRepository; +import org.openkilda.persistence.tx.TransactionManager; +import org.openkilda.wfm.share.mappers.FlowPathMapper; +import org.openkilda.wfm.share.service.IntersectionComputer; +import org.openkilda.wfm.topology.flowhs.exception.FlowProcessingException; +import org.openkilda.wfm.topology.flowhs.model.yflow.YFlowPaths; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public class YFlowUtils { + private final TransactionManager transactionManager; + private final YFlowRepository yFlowRepository; + + public YFlowUtils(PersistenceManager persistenceManager) { + transactionManager = persistenceManager.getTransactionManager(); + RepositoryFactory repositoryFactory = persistenceManager.getRepositoryFactory(); + this.yFlowRepository = repositoryFactory.createYFlowRepository(); + } + + public YFlowPaths definePaths(String yFlowId, List ignoredPathsCookies) { + return definePaths(collectPaths(yFlowId, ignoredPathsCookies)); + } + + public YFlowPaths definePaths(YFlow yFlow) { + return definePaths(collectPaths(yFlow)); + } + + private YFlowPaths definePaths(List flowPaths) { + List nonEmptyPaths = flowPaths.stream() + .filter(fp -> !fp.getSegments().isEmpty()).collect(Collectors.toList()); + PathInfoData sharedPath = FlowPathMapper.INSTANCE.map(nonEmptyPaths.size() >= 2 + ? IntersectionComputer.calculatePathIntersectionFromSource(nonEmptyPaths) : emptyList()); + + List subFlowPaths = flowPaths.stream() + .map(flowPath -> new SubFlowPathDto(flowPath.getFlowId(), FlowPathMapper.INSTANCE.map(flowPath))) + .sorted(Comparator.comparing(SubFlowPathDto::getFlowId)) + .collect(Collectors.toList()); + + return YFlowPaths.builder() + .sharedPath(sharedPath) + .subFlowPaths(subFlowPaths) + .build(); + } + + private List collectPaths(String yFlowId, List ignoredPathsCookies) { + return transactionManager.doInTransaction(() -> { + YFlow yFlow = yFlowRepository.findById(yFlowId) + .orElseThrow(() -> new FlowProcessingException(ErrorType.NOT_FOUND, + format("Y-flow %s not found", yFlowId))); + return collectPaths(yFlow, ignoredPathsCookies); + }); + } + + private List collectPaths(YFlow yFlow) { + return collectPaths(yFlow, Collections.emptyList()); + } + + private List collectPaths(YFlow yFlow, List ignoredPathsCookies) { + SwitchId sharedSwitchId = yFlow.getSharedEndpoint().getSwitchId(); + List paths = new ArrayList<>(); + for (YSubFlow subFlow : yFlow.getSubFlows()) { + Flow flow = subFlow.getFlow(); + FlowPath flowPath = flow.getPaths().stream() + .filter(path -> sharedSwitchId.equals(path.getSrcSwitchId()) + && !path.isProtected() + && !ignoredPathsCookies.contains(path.getCookie().getValue())) + .findFirst() + .orElse(sharedSwitchId.equals(flow.getForwardPath().getSrcSwitchId()) ? flow.getForwardPath() + : flow.getReversePath()); + paths.add(flowPath); + } + return paths; + } +} diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowValidationSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowValidationSpec.groovy index 3b2e8b93e5b..a48cef5a44b 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowValidationSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowValidationSpec.groovy @@ -177,7 +177,7 @@ class YFlowValidationSpec extends HealthCheckSpecification { ], [ action : "synchronize", - actionInMsg: "reroute", + actionInMsg: "sync", method : { northboundV2.synchronizeYFlow(NON_EXISTENT_FLOW_ID) } ] ] From 97a261c045094daae320347ead80e05171905690 Mon Sep 17 00:00:00 2001 From: Sergey Nikitin Date: Mon, 31 Oct 2022 14:02:47 +0400 Subject: [PATCH 3/7] Added balanced grouping into connected devices topology --- .../connecteddevices-topology.tmpl | 4 +- confd/vars/main.yaml | 4 + .../ConnectedDevicesTopology.java | 22 ++++-- .../connecteddevices/bolts/PacketBolt.java | 42 +++-------- .../connecteddevices/bolts/RouterBolt.java | 75 +++++++++++++++++++ .../service/PacketService.java | 28 +++++-- 6 files changed, 132 insertions(+), 43 deletions(-) create mode 100644 src-java/connecteddevices-topology/connecteddevices-storm-topology/src/main/java/org/openkilda/wfm/topology/connecteddevices/bolts/RouterBolt.java diff --git a/confd/templates/connecteddevices-topology/connecteddevices-topology.tmpl b/confd/templates/connecteddevices-topology/connecteddevices-topology.tmpl index 8112b7eb02d..2aea0b5285d 100644 --- a/confd/templates/connecteddevices-topology/connecteddevices-topology.tmpl +++ b/confd/templates/connecteddevices-topology/connecteddevices-topology.tmpl @@ -3,7 +3,7 @@ # topology configuration config: - topology.parallelism: {{ getv "/kilda_storm_parallelism_level_new" }} + topology.parallelism: {{ getv "/kilda_storm_connected_devices_parallelism" }} topology.workers: {{ getv "/kilda_storm_parallelism_workers_count" }} topology.spouts.parallelism: 1 @@ -16,3 +16,5 @@ spouts: bolts: - id: "zookeeper.bolt" parallelism: 1 + - id: "packet-bolt" + parallelism: {{ getv "/kilda_storm_connected_devices_packet_bolt_parallelism" }} diff --git a/confd/vars/main.yaml b/confd/vars/main.yaml index ba186d0432d..6b28dc77f0b 100644 --- a/confd/vars/main.yaml +++ b/confd/vars/main.yaml @@ -175,6 +175,10 @@ kilda_storm_parallelism_workers_count: 1 kilda_storm_history_parallelism: 2 kilda_storm_stats_parallelism: 2 +# Connected devices +kilda_storm_connected_devices_parallelism: 2 +kilda_storm_connected_devices_packet_bolt_parallelism: 2 + # Flow HS kilda_storm_flow_hs_parallelism: 2 kilda_storm_flow_hs_reroute_hub_count_multiplier: 2 diff --git a/src-java/connecteddevices-topology/connecteddevices-storm-topology/src/main/java/org/openkilda/wfm/topology/connecteddevices/ConnectedDevicesTopology.java b/src-java/connecteddevices-topology/connecteddevices-storm-topology/src/main/java/org/openkilda/wfm/topology/connecteddevices/ConnectedDevicesTopology.java index df6e8c62f07..414d09d8786 100644 --- a/src-java/connecteddevices-topology/connecteddevices-storm-topology/src/main/java/org/openkilda/wfm/topology/connecteddevices/ConnectedDevicesTopology.java +++ b/src-java/connecteddevices-topology/connecteddevices-storm-topology/src/main/java/org/openkilda/wfm/topology/connecteddevices/ConnectedDevicesTopology.java @@ -22,6 +22,7 @@ import org.openkilda.wfm.share.zk.ZooKeeperSpout; import org.openkilda.wfm.topology.AbstractTopology; import org.openkilda.wfm.topology.connecteddevices.bolts.PacketBolt; +import org.openkilda.wfm.topology.connecteddevices.bolts.RouterBolt; import org.openkilda.wfm.topology.utils.KafkaRecordTranslator; import org.apache.storm.generated.StormTopology; @@ -30,6 +31,7 @@ public class ConnectedDevicesTopology extends AbstractTopology { public static final String CONNECTED_DEVICES_SPOUT_ID = "connected-devices-spout"; + public static final String ROUTER_BOLT_ID = "router-bolt"; public static final String PACKET_BOLT_ID = "packet-bolt"; public ConnectedDevicesTopology(LaunchEnvironment env) { @@ -46,6 +48,7 @@ public StormTopology createTopology() { createZkSpout(builder); createSpout(builder); + createRouterBolt(builder, persistenceManager); createPacketBolt(builder, persistenceManager); createZkBolt(builder); @@ -59,22 +62,29 @@ private void createZkSpout(TopologyBuilder builder) { declareSpout(builder, zooKeeperSpout, ZooKeeperSpout.SPOUT_ID); } - private void createPacketBolt(TopologyBuilder builder, PersistenceManager persistenceManager) { - PacketBolt routerBolt = new PacketBolt(persistenceManager, ZooKeeperSpout.SPOUT_ID); - declareBolt(builder, routerBolt, PACKET_BOLT_ID) - .fieldsGrouping(CONNECTED_DEVICES_SPOUT_ID, new Fields(KafkaRecordTranslator.FIELD_ID_KEY)) + private void createRouterBolt(TopologyBuilder builder, PersistenceManager persistenceManager) { + RouterBolt routerBolt = new RouterBolt(persistenceManager, ZooKeeperSpout.SPOUT_ID); + declareBolt(builder, routerBolt, ROUTER_BOLT_ID) + .shuffleGrouping(CONNECTED_DEVICES_SPOUT_ID) .allGrouping(ZooKeeperSpout.SPOUT_ID); } + private void createPacketBolt(TopologyBuilder builder, PersistenceManager persistenceManager) { + PacketBolt packetBolt = new PacketBolt(persistenceManager); + declareBolt(builder, packetBolt, PACKET_BOLT_ID) + .fieldsGrouping(ROUTER_BOLT_ID, RouterBolt.PACKET_STREAM_ID, + new Fields(KafkaRecordTranslator.FIELD_ID_KEY)); + } + private void createSpout(TopologyBuilder builder) { declareKafkaSpout(builder, topologyConfig.getKafkaTopoConnectedDevicesTopic(), CONNECTED_DEVICES_SPOUT_ID); } private void createZkBolt(TopologyBuilder builder) { ZooKeeperBolt zooKeeperBolt = new ZooKeeperBolt(getConfig().getBlueGreenMode(), getZkTopoName(), - getZookeeperConfig(), getBoltInstancesCount(PACKET_BOLT_ID)); + getZookeeperConfig(), getBoltInstancesCount(ROUTER_BOLT_ID)); declareBolt(builder, zooKeeperBolt, ZooKeeperBolt.BOLT_ID) - .allGrouping(PACKET_BOLT_ID, ZkStreams.ZK.toString()); + .allGrouping(ROUTER_BOLT_ID, ZkStreams.ZK.toString()); } @Override diff --git a/src-java/connecteddevices-topology/connecteddevices-storm-topology/src/main/java/org/openkilda/wfm/topology/connecteddevices/bolts/PacketBolt.java b/src-java/connecteddevices-topology/connecteddevices-storm-topology/src/main/java/org/openkilda/wfm/topology/connecteddevices/bolts/PacketBolt.java index dffe7ae4cd6..6c804c92d7e 100644 --- a/src-java/connecteddevices-topology/connecteddevices-storm-topology/src/main/java/org/openkilda/wfm/topology/connecteddevices/bolts/PacketBolt.java +++ b/src-java/connecteddevices-topology/connecteddevices-storm-topology/src/main/java/org/openkilda/wfm/topology/connecteddevices/bolts/PacketBolt.java @@ -17,29 +17,23 @@ import static org.openkilda.wfm.topology.utils.KafkaRecordTranslator.FIELD_ID_PAYLOAD; -import org.openkilda.messaging.Message; -import org.openkilda.messaging.info.InfoData; -import org.openkilda.messaging.info.InfoMessage; import org.openkilda.messaging.info.event.ArpInfoData; +import org.openkilda.messaging.info.event.ConnectedDevicePacketBase; import org.openkilda.messaging.info.event.LldpInfoData; import org.openkilda.persistence.PersistenceManager; import org.openkilda.wfm.AbstractBolt; import org.openkilda.wfm.error.PipelineException; -import org.openkilda.wfm.share.zk.ZkStreams; -import org.openkilda.wfm.share.zk.ZooKeeperBolt; import org.openkilda.wfm.topology.connecteddevices.service.PacketService; import lombok.extern.slf4j.Slf4j; -import org.apache.storm.topology.OutputFieldsDeclarer; -import org.apache.storm.tuple.Fields; import org.apache.storm.tuple.Tuple; @Slf4j public class PacketBolt extends AbstractBolt { private transient PacketService packetService; - public PacketBolt(PersistenceManager persistenceManager, String lifeCycleEventSourceComponent) { - super(persistenceManager, lifeCycleEventSourceComponent); + public PacketBolt(PersistenceManager persistenceManager) { + super(persistenceManager); } @Override @@ -49,28 +43,14 @@ protected void init() { @Override protected void handleInput(Tuple input) throws PipelineException { - if (active) { - Message message = pullValue(input, FIELD_ID_PAYLOAD, Message.class); - - if (message instanceof InfoMessage) { - log.debug("Received info message {}", message); - InfoData data = ((InfoMessage) message).getData(); - if (data instanceof LldpInfoData) { - packetService.handleLldpData((LldpInfoData) data); - } else if (data instanceof ArpInfoData) { - packetService.handleArpData((ArpInfoData) data); - } else { - unhandledInput(input); - } - } else { - unhandledInput(input); - } + ConnectedDevicePacketBase data = pullValue(input, FIELD_ID_PAYLOAD, ConnectedDevicePacketBase.class); + + if (data instanceof LldpInfoData) { + packetService.handleLldpData((LldpInfoData) data); + } else if (data instanceof ArpInfoData) { + packetService.handleArpData((ArpInfoData) data); + } else { + unhandledInput(input); } } - - @Override - public void declareOutputFields(OutputFieldsDeclarer declarer) { - declarer.declareStream(ZkStreams.ZK.toString(), new Fields(ZooKeeperBolt.FIELD_ID_STATE, - ZooKeeperBolt.FIELD_ID_CONTEXT)); - } } diff --git a/src-java/connecteddevices-topology/connecteddevices-storm-topology/src/main/java/org/openkilda/wfm/topology/connecteddevices/bolts/RouterBolt.java b/src-java/connecteddevices-topology/connecteddevices-storm-topology/src/main/java/org/openkilda/wfm/topology/connecteddevices/bolts/RouterBolt.java new file mode 100644 index 00000000000..03a3b7cfc9b --- /dev/null +++ b/src-java/connecteddevices-topology/connecteddevices-storm-topology/src/main/java/org/openkilda/wfm/topology/connecteddevices/bolts/RouterBolt.java @@ -0,0 +1,75 @@ +/* Copyright 2019 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.connecteddevices.bolts; + +import static org.openkilda.wfm.topology.connecteddevices.service.PacketService.createMessageKey; +import static org.openkilda.wfm.topology.utils.KafkaRecordTranslator.FIELD_ID_PAYLOAD; + +import org.openkilda.messaging.Message; +import org.openkilda.messaging.info.InfoData; +import org.openkilda.messaging.info.InfoMessage; +import org.openkilda.messaging.info.event.ArpInfoData; +import org.openkilda.messaging.info.event.LldpInfoData; +import org.openkilda.persistence.PersistenceManager; +import org.openkilda.wfm.AbstractBolt; +import org.openkilda.wfm.error.PipelineException; +import org.openkilda.wfm.share.zk.ZkStreams; +import org.openkilda.wfm.share.zk.ZooKeeperBolt; +import org.openkilda.wfm.topology.utils.MessageKafkaTranslator; + +import lombok.extern.slf4j.Slf4j; +import org.apache.storm.topology.OutputFieldsDeclarer; +import org.apache.storm.tuple.Fields; +import org.apache.storm.tuple.Tuple; +import org.apache.storm.tuple.Values; + +@Slf4j +public class RouterBolt extends AbstractBolt { + + public static final String PACKET_STREAM_ID = "packet-stream"; + + public RouterBolt(PersistenceManager persistenceManager, String lifeCycleEventSourceComponent) { + super(persistenceManager, lifeCycleEventSourceComponent); + } + + @Override + protected void handleInput(Tuple input) throws PipelineException { + if (active) { + Message message = pullValue(input, FIELD_ID_PAYLOAD, Message.class); + + if (message instanceof InfoMessage) { + log.debug("Received info message {}", message); + InfoData data = ((InfoMessage) message).getData(); + if (data instanceof LldpInfoData) { + emitWithContext(PACKET_STREAM_ID, input, new Values(createMessageKey((LldpInfoData) data), data)); + } else if (data instanceof ArpInfoData) { + emitWithContext(PACKET_STREAM_ID, input, new Values(createMessageKey((ArpInfoData) data), data)); + } else { + unhandledInput(input); + } + } else { + unhandledInput(input); + } + } + } + + @Override + public void declareOutputFields(OutputFieldsDeclarer declarer) { + declarer.declareStream(ZkStreams.ZK.toString(), new Fields(ZooKeeperBolt.FIELD_ID_STATE, + ZooKeeperBolt.FIELD_ID_CONTEXT)); + declarer.declareStream(PACKET_STREAM_ID, MessageKafkaTranslator.STREAM_FIELDS); + } +} diff --git a/src-java/connecteddevices-topology/connecteddevices-storm-topology/src/main/java/org/openkilda/wfm/topology/connecteddevices/service/PacketService.java b/src-java/connecteddevices-topology/connecteddevices-storm-topology/src/main/java/org/openkilda/wfm/topology/connecteddevices/service/PacketService.java index 3a2d672030b..b0ddf4ca831 100644 --- a/src-java/connecteddevices-topology/connecteddevices-storm-topology/src/main/java/org/openkilda/wfm/topology/connecteddevices/service/PacketService.java +++ b/src-java/connecteddevices-topology/connecteddevices-storm-topology/src/main/java/org/openkilda/wfm/topology/connecteddevices/service/PacketService.java @@ -56,11 +56,11 @@ public class PacketService { public static final int FULL_PORT_VLAN = 0; - private TransactionManager transactionManager; - private SwitchRepository switchRepository; - private SwitchConnectedDeviceRepository switchConnectedDeviceRepository; - private TransitVlanRepository transitVlanRepository; - private FlowRepository flowRepository; + private final TransactionManager transactionManager; + private final SwitchRepository switchRepository; + private final SwitchConnectedDeviceRepository switchConnectedDeviceRepository; + private final TransitVlanRepository transitVlanRepository; + private final FlowRepository flowRepository; public PacketService(PersistenceManager persistenceManager) { transactionManager = persistenceManager.getTransactionManager(); @@ -123,6 +123,24 @@ public void handleArpData(ArpInfoData data) { }); } + /** + * This key is needed to balance load on Packet Bolt. If you see that some packet bolts have high load, and + * some have low load, try to extend this key. Maximum extension is equal to + * SwitchConnectedDeviceFrame.UNIQUE_INDEX_PROPERTY + */ + public static String createMessageKey(LldpInfoData data) { + return String.format("%s_%s_lldp", data.getSwitchId(), data.getPortNumber()); + } + + /** + * This key is needed to balance load on Packet Bolt. If you see that some packet bolts have high load, and + * some have low load, try to extend this key. Maximum extension is equal to + * SwitchConnectedDeviceFrame.UNIQUE_INDEX_PROPERTY + */ + public static String createMessageKey(ArpInfoData data) { + return String.format("%s_%s_arp", data.getSwitchId(), data.getPortNumber()); + } + private FlowRelatedData findFlowRelatedData(ConnectedDevicePacketBase data) { long cookie = data.getCookie(); if (cookie == LLDP_POST_INGRESS_COOKIE From f613b32c45986329ef0849697b2633965e01806c Mon Sep 17 00:00:00 2001 From: Sergey Nikitin Date: Wed, 2 Nov 2022 19:43:04 +0400 Subject: [PATCH 4/7] Set lacp_reply to true if value is not specified Closes #4976 --- .../dto/v2/switches/LagPortRequest.java | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) 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 8d4362bd642..0c278e745d4 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 @@ -15,11 +15,9 @@ package org.openkilda.northbound.dto.v2.switches; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; -import com.fasterxml.jackson.databind.annotation.JsonNaming; -import lombok.AllArgsConstructor; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Builder; -import lombok.Builder.Default; import lombok.Data; import lombok.NoArgsConstructor; @@ -27,11 +25,23 @@ @Data @NoArgsConstructor -@AllArgsConstructor -@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) -@Builder public class LagPortRequest { + @JsonProperty("port_numbers") private Set portNumbers; - @Default - private Boolean lacpReply = true; + + @JsonProperty("lacp_reply") + private Boolean lacpReply; + + @Builder + @JsonCreator + public LagPortRequest( + @JsonProperty("port_numbers") Set portNumbers, + @JsonProperty("lacp_reply") Boolean lacpReply) { + this.portNumbers = portNumbers; + setLacpReply(lacpReply); + } + + public void setLacpReply(Boolean lacpReply) { + this.lacpReply = lacpReply == null ? true : lacpReply; + } } From 4d6ead152d9d1684259e342b742a6cb036f66633 Mon Sep 17 00:00:00 2001 From: Sergey Nikitin Date: Thu, 3 Nov 2022 13:27:05 +0400 Subject: [PATCH 5/7] Added info about latency into create/update logs --- .../logger/FlowOperationsDashboardLogger.java | 35 ++++++++++++------- .../create/actions/FlowValidateAction.java | 3 +- .../update/actions/ValidateFlowAction.java | 3 +- .../create/actions/ValidateYFlowAction.java | 3 +- .../update/actions/ValidateYFlowAction.java | 3 +- 5 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/logger/FlowOperationsDashboardLogger.java b/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/logger/FlowOperationsDashboardLogger.java index ac1a1c59e3c..4ae94d9a48b 100644 --- a/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/logger/FlowOperationsDashboardLogger.java +++ b/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/logger/FlowOperationsDashboardLogger.java @@ -19,6 +19,7 @@ import org.openkilda.model.FlowEndpoint; import org.openkilda.model.FlowStatus; import org.openkilda.model.IslEndpoint; +import org.openkilda.model.PathComputationStrategy; import org.openkilda.model.SwitchId; import org.openkilda.reporting.AbstractDashboardLogger; @@ -149,14 +150,16 @@ public void onFlowCreate(Flow flow) { * Log a flow-create event. */ public void onFlowCreate(String flowId, SwitchId srcSwitch, int srcPort, int srcVlan, - SwitchId destSwitch, int destPort, int destVlan, String diverseFlowId, long bandwidth) { + SwitchId destSwitch, int destPort, int destVlan, String diverseFlowId, long bandwidth, + PathComputationStrategy strategy, Long maxLatency, Long maxLatencyTier2) { Map data = new HashMap<>(); data.put(TAG, "flow-create"); data.put(FLOW_ID, flowId); data.put(EVENT_TYPE, FLOW_CREATE_EVENT); invokeLogger(Level.INFO, String.format("Create the flow: %s, source %s_%d_%d, destination %s_%d_%d, " - + "diverse flowId %s, bandwidth %d", flowId, srcSwitch, srcPort, srcVlan, - destSwitch, destPort, destVlan, diverseFlowId, bandwidth), data); + + "diverse flowId %s, bandwidth %d, path computation strategy %s, max latency %s, " + + "max latency tier2 %s", flowId, srcSwitch, srcPort, srcVlan, destSwitch, destPort, destVlan, + diverseFlowId, bandwidth, strategy, maxLatency, maxLatencyTier2), data); } /** @@ -223,14 +226,16 @@ public void onFlowUpdate(Flow flow) { * Log a flow-update event. */ public void onFlowUpdate(String flowId, SwitchId srcSwitch, int srcPort, int srcVlan, - SwitchId destSwitch, int destPort, int destVlan, String diverseFlowId, long bandwidth) { + SwitchId destSwitch, int destPort, int destVlan, String diverseFlowId, long bandwidth, + PathComputationStrategy strategy, Long maxLatency, Long maxLatencyTier2) { Map data = new HashMap<>(); data.put(TAG, "flow-update"); data.put(FLOW_ID, flowId); data.put(EVENT_TYPE, FLOW_UPDATE_EVENT); invokeLogger(Level.INFO, String.format("Update the flow %s with: source %s_%d_%d, destination %s_%d_%d, " - + "diverse flowId %s, bandwidth %d", flowId, srcSwitch, srcPort, srcVlan, - destSwitch, destPort, destVlan, diverseFlowId, bandwidth), data); + + "diverse flowId %s, bandwidth %d, path computation strategy %s, max latency %s, " + + "max latency tier2 %s", flowId, srcSwitch, srcPort, srcVlan, + destSwitch, destPort, destVlan, diverseFlowId, bandwidth, strategy, maxLatency, maxLatencyTier2), data); } /** @@ -499,14 +504,17 @@ public void onFailedFlowMirrorPointDelete(String flowId, String flowMirrorPointI /** * Log a y-flow-create event. */ - public void onYFlowCreate(String yFlowId, FlowEndpoint sharedEndpoint, - List subFlowEndpoints, long maximumBandwidth) { + public void onYFlowCreate( + String yFlowId, FlowEndpoint sharedEndpoint, List subFlowEndpoints, long maximumBandwidth, + PathComputationStrategy strategy, Long maxLatency, Long maxLatencyTier2) { Map data = new HashMap<>(); data.put(TAG, "y-flow-create"); data.put(FLOW_ID, yFlowId); data.put(EVENT_TYPE, YFLOW_CREATE_EVENT); invokeLogger(Level.INFO, String.format("Create the y-flow: %s, shared endpoint %s, endpoints (%s), " - + "bandwidth %d", yFlowId, sharedEndpoint, subFlowEndpoints, maximumBandwidth), data); + + "bandwidth %d, path computation strategy %s, max latency %s, max latency tier2 %s", + yFlowId, sharedEndpoint, subFlowEndpoints, maximumBandwidth, strategy, maxLatency, maxLatencyTier2), + data); } /** @@ -538,14 +546,17 @@ public void onFailedYFlowCreate(String yFlowId, String failureReason) { /** * Log a y-flow-update event. */ - public void onYFlowUpdate(String yFlowId, FlowEndpoint sharedEndpoint, - List subFlowEndpoints, long maximumBandwidth) { + public void onYFlowUpdate( + String yFlowId, FlowEndpoint sharedEndpoint, List subFlowEndpoints, long maximumBandwidth, + PathComputationStrategy strategy, Long maxLatency, Long maxLatencyTier2) { Map data = new HashMap<>(); data.put(TAG, "y-flow-update"); data.put(FLOW_ID, yFlowId); data.put(EVENT_TYPE, YFLOW_UPDATE_EVENT); invokeLogger(Level.INFO, String.format("Update the y-flow: %s, shared endpoint %s, endpoints (%s), " - + "bandwidth %d", yFlowId, sharedEndpoint, subFlowEndpoints, maximumBandwidth), data); + + "bandwidth %d, path computation strategy %s, max latency %s, max latency tier2 %s", + yFlowId, sharedEndpoint, subFlowEndpoints, maximumBandwidth, strategy, maxLatency, maxLatencyTier2), + data); } /** diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/create/actions/FlowValidateAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/create/actions/FlowValidateAction.java index 65212138d22..16c8fe95110 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/create/actions/FlowValidateAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/create/actions/FlowValidateAction.java @@ -65,7 +65,8 @@ protected Optional performWithResponse(State from, State to, Event even dashboardLogger.onFlowCreate(request.getFlowId(), request.getSrcSwitch(), request.getSrcPort(), request.getSrcVlan(), request.getDestSwitch(), request.getDestPort(), request.getDestVlan(), - request.getDiverseFlowId(), request.getBandwidth()); + request.getDiverseFlowId(), request.getBandwidth(), request.getPathComputationStrategy(), + request.getMaxLatency(), request.getMaxLatencyTier2()); boolean isOperationAllowed = featureTogglesRepository.getOrDefault().getCreateFlowEnabled(); if (!isOperationAllowed) { diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/update/actions/ValidateFlowAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/update/actions/ValidateFlowAction.java index 0735d30d89a..bea8aa77901 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/update/actions/ValidateFlowAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/update/actions/ValidateFlowAction.java @@ -66,7 +66,8 @@ protected Optional performWithResponse(State from, State to, Event even dashboardLogger.onFlowUpdate(flowId, targetFlow.getSrcSwitch(), targetFlow.getSrcPort(), targetFlow.getSrcVlan(), targetFlow.getDestSwitch(), targetFlow.getDestPort(), targetFlow.getDestVlan(), - diverseFlowId, targetFlow.getBandwidth()); + diverseFlowId, targetFlow.getBandwidth(), targetFlow.getPathComputationStrategy(), + targetFlow.getMaxLatency(), targetFlow.getMaxLatencyTier2()); boolean isOperationAllowed = featureTogglesRepository.getOrDefault().getUpdateFlowEnabled(); if (!isOperationAllowed) { diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/ValidateYFlowAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/ValidateYFlowAction.java index 5950248590e..65914d0c60b 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/ValidateYFlowAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/ValidateYFlowAction.java @@ -96,7 +96,8 @@ protected Optional performWithResponse(State from, State to, Event even .map(SubFlowDto::getEndpoint) .collect(Collectors.toList()); dashboardLogger.onYFlowCreate(yFlowId, targetFlow.getSharedEndpoint(), subFlowEndpoints, - targetFlow.getMaximumBandwidth()); + targetFlow.getMaximumBandwidth(), targetFlow.getPathComputationStrategy(), targetFlow.getMaxLatency(), + targetFlow.getMaxLatencyTier2()); stateMachine.saveNewEventToHistory("Y-flow was validated successfully", FlowEventData.Event.CREATE); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/ValidateYFlowAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/ValidateYFlowAction.java index 9e2eb265faa..73d2766b2f8 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/ValidateYFlowAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/ValidateYFlowAction.java @@ -136,7 +136,8 @@ protected Optional performWithResponse(State from, State to, Event even .map(SubFlowDto::getEndpoint) .collect(Collectors.toList()); dashboardLogger.onYFlowUpdate(yFlowId, targetFlow.getSharedEndpoint(), subFlowEndpoints, - targetFlow.getMaximumBandwidth()); + targetFlow.getMaximumBandwidth(), targetFlow.getPathComputationStrategy(), targetFlow.getMaxLatency(), + targetFlow.getMaxLatencyTier2()); stateMachine.setTargetFlow(targetFlow); From 54427bd470c1ff37ce1c756840b6edff6e22bab5 Mon Sep 17 00:00:00 2001 From: Sergey Nikitin Date: Mon, 7 Nov 2022 14:15:49 +0400 Subject: [PATCH 6/7] Fixed updating of one switch Y flow Closes #4972 --- .../update/actions/ValidateYFlowAction.java | 23 +++++++++++++++---- .../java/org/openkilda/model/YSubFlow.java | 4 ++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/ValidateYFlowAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/ValidateYFlowAction.java index 9e2eb265faa..89b78109fba 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/ValidateYFlowAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/ValidateYFlowAction.java @@ -127,10 +127,7 @@ protected Optional performWithResponse(State from, State to, Event even format("Unable to map provided sub-flows set onto existing y-flow %s", yFlowId)); } - YSubFlow subFlow = yFlow.getSubFlows().stream().findAny() - .orElseThrow(() -> new FlowProcessingException(ErrorType.DATA_INVALID, - format("No sub-flows of the y-flow %s were found", yFlowId))); - stateMachine.setMainAffinityFlowId(subFlow.getFlow().getAffinityGroupId()); + stateMachine.setMainAffinityFlowId(getMainAffinityFlowId(yFlow)); List subFlowEndpoints = targetFlow.getSubFlows().stream() .map(SubFlowDto::getEndpoint) @@ -145,6 +142,24 @@ protected Optional performWithResponse(State from, State to, Event even return Optional.empty(); } + private String getMainAffinityFlowId(YFlow yFlow) { + // TODO: maybe we should add filtering of one switch sub flows into method getSubFlows() + YSubFlow multiSwitchFlows = yFlow.getSubFlows().stream() + .filter(sub -> !sub.isOneSwitchYFlow(yFlow.getSharedEndpoint().getSwitchId())) + .findAny() + .orElse(null); + if (multiSwitchFlows != null) { + return multiSwitchFlows.getFlow().getAffinityGroupId(); + } else { + // if there is no multi switch flows we have to use one switch flow + YSubFlow oneSwitchSubFlow = yFlow.getSubFlows().stream() + .findAny() + .orElseThrow(() -> new FlowProcessingException(ErrorType.DATA_INVALID, + format("No sub-flows of the y-flow %s were found", yFlow.getYFlowId()))); + return oneSwitchSubFlow.getSubFlowId(); + } + } + @Override protected String getGenericErrorMessage() { return "Could not update y-flow"; diff --git a/src-java/kilda-model/src/main/java/org/openkilda/model/YSubFlow.java b/src-java/kilda-model/src/main/java/org/openkilda/model/YSubFlow.java index 461140835b5..53d21e27f4d 100644 --- a/src-java/kilda-model/src/main/java/org/openkilda/model/YSubFlow.java +++ b/src-java/kilda-model/src/main/java/org/openkilda/model/YSubFlow.java @@ -83,6 +83,10 @@ public YSubFlow(@NonNull YFlow yFlow, @NonNull Flow flow, int sharedEndpointVlan this.data = builder.build(); } + public boolean isOneSwitchYFlow(SwitchId sharedEndpointSwitchId) { + return data.getEndpointSwitchId().equals(sharedEndpointSwitchId); + } + @Override public boolean equals(Object o) { if (this == o) { From 84c877a9eb3f63b3b8193418d87e3f7a7d546d13 Mon Sep 17 00:00:00 2001 From: Sergey Nikitin Date: Mon, 31 Oct 2022 15:52:11 +0400 Subject: [PATCH 7/7] Updated CHANGELOG.md (release 1.125.2) --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 453615cfa4e..9c35879d516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## v1.125.2 (09/11/2022) + +### Bug Fixes: +- [#4977](https://github.com/telstra/open-kilda/pull/4977) Set lacp_reply to true if value is not specified (Issue: [#4976](https://github.com/telstra/open-kilda/issues/4976)) [**northbound**] +- [#4981](https://github.com/telstra/open-kilda/pull/4981) Fixed updating of one switch Y flow (Issue: [#4972](https://github.com/telstra/open-kilda/issues/4972)) +- [#4951](https://github.com/telstra/open-kilda/pull/4951) Fix stats for y flow ingress endpoint (Issue: [#4926](https://github.com/telstra/open-kilda/issues/4926)) [**floodlight**][**storm-topologies**] + +### Improvements: +- [#4962](https://github.com/telstra/open-kilda/pull/4962) Implement true Y-flow sync +- [#4973](https://github.com/telstra/open-kilda/pull/4973) Added balanced grouping into connected devices topology +- [#4978](https://github.com/telstra/open-kilda/pull/4978) Added info about latency into create/update logs + + +For the complete list of changes, check out [the commit log](https://github.com/telstra/open-kilda/compare/v1.125.1...v1.125.2). + +### Affected Components: +stats, flow-hs, fl, connected, nb + +--- + ## v1.125.1 (27/10/2022) ### Bug Fixes: