From a3be7afb074ef5466b0a511d40d0262faea60706 Mon Sep 17 00:00:00 2001 From: Andrii Dovhan Date: Mon, 6 Dec 2021 18:20:05 +0200 Subject: [PATCH 01/10] [yflow] add tests for subFlows --- .../spec/flows/yflows/SubFlowSpec.groovy | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/SubFlowSpec.groovy diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/SubFlowSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/SubFlowSpec.groovy new file mode 100644 index 00000000000..f73a789b0dd --- /dev/null +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/SubFlowSpec.groovy @@ -0,0 +1,152 @@ +package org.openkilda.functionaltests.spec.flows.yflows + +import static org.openkilda.functionaltests.helpers.FlowHistoryConstants.CREATE_SUCCESS_Y +import static org.openkilda.testing.Constants.WAIT_OFFSET + +import org.openkilda.functionaltests.HealthCheckSpecification +import org.openkilda.functionaltests.extension.failfast.Tidy +import org.openkilda.functionaltests.helpers.Wrappers +import org.openkilda.functionaltests.helpers.YFlowHelper +import org.openkilda.messaging.error.MessageError +import org.openkilda.messaging.payload.flow.FlowState +import org.openkilda.model.FlowPathDirection +import org.openkilda.northbound.dto.v1.flows.PingInput +import org.openkilda.northbound.dto.v2.flows.FlowEndpointV2 +import org.openkilda.northbound.dto.v2.flows.FlowLoopPayload +import org.openkilda.northbound.dto.v2.flows.FlowMirrorPointPayload +import org.openkilda.northbound.dto.v2.flows.FlowPatchV2 +import org.openkilda.northbound.dto.v2.yflows.SubFlow + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.HttpStatus +import org.springframework.web.client.HttpClientErrorException +import spock.lang.Narrative +import spock.lang.Shared + +@Narrative("Verify different actions for a sub-flow.") +class SubFlowSpec extends HealthCheckSpecification { + @Autowired + @Shared + YFlowHelper yFlowHelper + + @Tidy + def "Unable to #data.action a sub-flow"() { + given: "Existing y-flow" + def swT = topologyHelper.switchTriplets[0] + def yFlowRequest = yFlowHelper.randomYFlow(swT) + def yFlow = yFlowHelper.addYFlow(yFlowRequest) + + when: "Invoke a certain action for a sub-flow" + def subFlow = yFlow.subFlows.first() + data.method(subFlow) + + then: "Human readable error is returned" + def e = thrown(HttpClientErrorException) + e.statusCode == HttpStatus.BAD_REQUEST + verifyAll(e.responseBodyAsString.to(MessageError)) { + errorMessage == "Could not modify flow" + errorDescription == "${subFlow.flowId} is a sub-flow of a y-flow. Operations on sub-flows are forbidden." + } + + and: "All involved switches pass switch validation" + def involvedSwitches = pathHelper.getInvolvedYSwitches(yFlow.YFlowId) + involvedSwitches.each { sw -> +// northbound.validateSwitch(sw.dpId).verifyRuleSectionsAreEmpty(["missing", "excess", "misconfigured"]) +// northbound.validateSwitch(sw.dpId).verifyMeterSectionsAreEmpty(["missing", "excess", "misconfigured"]) + northbound.validateSwitch(sw.dpId).verifyRuleSectionsAreEmpty(["missing", "misconfigured"]) + northbound.validateSwitch(sw.dpId).verifyMeterSectionsAreEmpty(["missing", "misconfigured"]) + } + + and: "Y-Flow is UP" + and: "Sub flows are UP" + Wrappers.wait(WAIT_OFFSET) { + with(northboundV2.getYFlow(yFlow.YFlowId)) { + it.status == FlowState.UP.toString() + it.subFlows.each { it.status == FlowState.UP.toString() } + } + } + + and: "Y-flow passes flow validation" + northboundV2.validateYFlow(yFlow.YFlowId).asExpected + + and: "SubFlow passes flow validation" + northbound.validateFlow(subFlow.flowId).each { direction -> assert direction.asExpected } + + and: "Flow history doesn't contain info about illegal action" + //create action only + northbound.getFlowHistory(yFlow.YFlowId).last().payload.last().action == CREATE_SUCCESS_Y + + and: "Sub flow is pingable" + verifyAll(northbound.pingFlow(subFlow.flowId, new PingInput())) { + forward.pingSuccess + reverse.pingSuccess + } + + cleanup: + yFlowHelper.deleteYFlow(yFlow.YFlowId) + + where: + data << [ + [ + action: "rerouteV1", + method: { SubFlow sFlow -> + northbound.rerouteFlow(sFlow.flowId) + } + ], + [ + action: "rerouteV2", + method: { SubFlow sFlow -> + northboundV2.rerouteFlow(sFlow.flowId) + } + ], + [ + action: "synchronize", + method: { SubFlow sFlow -> + northbound.rerouteFlow(sFlow.flowId) + } + ], + [ + action: "update", + method: { SubFlow sFlow -> + def flowToUpdate = northbound.getFlow(sFlow.flowId) + northbound.updateFlow(flowToUpdate.id, flowToUpdate.tap { it.description += " updated" }) + } + ], + [ + action: "partially update", + method: { SubFlow sFlow -> + def updateRequest = new FlowPatchV2().tap { it.description += " updated" } + northboundV2.partialUpdate(sFlow.flowId, updateRequest) + } + ], + [ + action: "delete", + method: { SubFlow sFlow -> + northboundV2.deleteFlow(sFlow.flowId) + } + ], + [ + action: "create a flowLoop on", + method: { SubFlow sFlow -> + northboundV2.createFlowLoop(sFlow.flowId, new FlowLoopPayload(sFlow.endpoint.switchId)) + } + ], + [ + action: "create a mirrorPoint on", + method: { SubFlow sFlow -> + def mirrorEndpoint = FlowMirrorPointPayload.builder() + .mirrorPointId(flowHelperV2.generateFlowId()) + .mirrorPointDirection(FlowPathDirection.FORWARD.toString().toLowerCase()) + .mirrorPointSwitchId(sFlow.endpoint.switchId) + .sinkEndpoint(FlowEndpointV2.builder().switchId(sFlow.endpoint.switchId) + .portNumber(topology.getAllowedPortsForSwitch( + topology.activeSwitches.find { it.dpId == sFlow.endpoint.switchId }).first()) + .vlanId(flowHelperV2.randomVlan()) + .build()) + .build() + northboundV2.createMirrorPoint(sFlow.flowId, mirrorEndpoint) + } + ] + ] + } +} From 6be17872b3862f729d6c9578dfdf2365505c4aff Mon Sep 17 00:00:00 2001 From: Dmitry Poltavets Date: Fri, 14 Jan 2022 12:46:18 +0400 Subject: [PATCH 02/10] Add diversity to YFlow. --- docs/design/y-flow/y-flow-nb-api.md | 17 +++- .../openkilda/messaging/model/FlowDto.java | 9 +- .../wfm/share/mappers/FlowMapper.java | 11 ++- .../wfm/share/mappers/FlowMapperTest.java | 2 +- .../messaging/command/yflow/YFlowDto.java | 3 + .../yflow/YFlowPartialUpdateRequest.java | 1 + .../messaging/command/yflow/YFlowRequest.java | 1 + ...lowProcessingWithHistorySupportAction.java | 20 +++-- .../create/actions/FlowValidateAction.java | 8 ++ .../actions/ResourcesAllocationAction.java | 21 ++++- .../actions/UpdateFlowPathsAction.java | 9 +- .../fsm/update/actions/UpdateFlowAction.java | 21 ++++- .../fsm/yflow/create/YFlowCreateFsm.java | 2 + .../actions/CreateDraftYFlowAction.java | 1 + .../create/actions/CreateSubFlowsAction.java | 1 + .../actions/OnSubFlowAllocatedAction.java | 28 +++++- .../create/actions/ValidateYFlowAction.java | 5 ++ .../delete/actions/ValidateYFlowAction.java | 34 +++++++- .../fsm/yflow/update/YFlowUpdateFsm.java | 2 + .../actions/OnSubFlowAllocatedAction.java | 28 +++++- .../update/actions/UpdateSubFlowsAction.java | 1 + .../update/actions/UpdateYFlowAction.java | 1 + .../topology/flowhs/mapper/YFlowMapper.java | 5 +- .../flowhs/mapper/YFlowRequestMapper.java | 1 + .../service/yflow/YFlowReadService.java | 38 +++++++- .../service/yflow/YFlowUpdateService.java | 1 + .../flowhs/validation/FlowValidator.java | 24 +++++- .../flowhs/service/AbstractYFlowTest.java | 9 +- .../dummy/PersistenceDummyEntityFactory.java | 43 ++++++++-- .../nbworker/bolts/FlowOperationsBolt.java | 18 +--- .../services/FlowOperationsService.java | 86 +++++++++++++------ .../dto/v2/flows/FlowResponseV2.java | 2 + .../northbound/dto/v2/yflows/YFlow.java | 5 ++ .../dto/v2/yflows/YFlowCreatePayload.java | 1 + .../dto/v2/yflows/YFlowPatchPayload.java | 1 + .../dto/v2/yflows/YFlowUpdatePayload.java | 1 + 36 files changed, 375 insertions(+), 86 deletions(-) diff --git a/docs/design/y-flow/y-flow-nb-api.md b/docs/design/y-flow/y-flow-nb-api.md index 82a73221fea..ac95f2f3df7 100644 --- a/docs/design/y-flow/y-flow-nb-api.md +++ b/docs/design/y-flow/y-flow-nb-api.md @@ -20,7 +20,8 @@ request payload: "pinned": false, "priority": 0, "strict_bandwidth": true, - "description": "description" + "description": "description", + "diverse_flow_id": "diverse_flow_id", "sub_flows": [ { "endpoint": { @@ -61,6 +62,12 @@ response payload (copy of request payload plus autogenerated fields): "shared_endpoint": ... ... : ..., "y_point": null, + "diverse_with_flows": [ + "diverse_flow" + ], + "diverse_with_y_flows": [ + "diverse_y_flow" + ], "sub_flows": [ { "flow_id": "fAAAAAAAAAAAAAAA", @@ -98,7 +105,7 @@ response payload (copy of request payload plus autogenerated fields): } ], "time_create": "YYYY-MM-DDThh:mm:ss[.SSS]", - "time_update": "YYYY-MM-DDThh:mm:ss[.SSS]", + "time_update": "YYYY-MM-DDThh:mm:ss[.SSS]" } ``` @@ -128,6 +135,12 @@ response payload: "strict_bandwidth": true, "description": "description", "y_point": "00:00:00:00:00:00:01:01", + "diverse_with_flows": [ + "diverse_flow" + ], + "diverse_with_y_flows": [ + "diverse_y_flow" + ], "sub_flows": [ { "flow_id": "fAAAAAAAAAAAAAAA", diff --git a/src-java/base-topology/base-messaging/src/main/java/org/openkilda/messaging/model/FlowDto.java b/src-java/base-topology/base-messaging/src/main/java/org/openkilda/messaging/model/FlowDto.java index 69b08f219a5..5c8adaa276c 100644 --- a/src-java/base-topology/base-messaging/src/main/java/org/openkilda/messaging/model/FlowDto.java +++ b/src-java/base-topology/base-messaging/src/main/java/org/openkilda/messaging/model/FlowDto.java @@ -190,6 +190,9 @@ public class FlowDto implements Serializable { @JsonProperty("diverse_with") private Set diverseWith; + @JsonProperty("diverse_with_y_flows") + private Set diverseWithYFlows; + @JsonProperty("affinity_with") private String affinityWith; @@ -290,6 +293,7 @@ public FlowDto(@JsonProperty(Utils.FLOW_ID) final String flowId, @JsonProperty("target_path_computation_strategy") PathComputationStrategy targetPathComputationStrategy, @JsonProperty("diverse_with") Set diverseWith, + @JsonProperty("diverse_with_y_flows") Set diverseWithYFlows, @JsonProperty("affinity_with") String affinityWith, @JsonProperty("loop_switch_id") SwitchId loopSwitchId, @JsonProperty("mirror_point_statuses") List mirrorPointStatuses, @@ -329,6 +333,7 @@ public FlowDto(@JsonProperty(Utils.FLOW_ID) final String flowId, this.pathComputationStrategy = pathComputationStrategy; this.targetPathComputationStrategy = targetPathComputationStrategy; this.diverseWith = diverseWith; + this.diverseWithYFlows = diverseWithYFlows; this.affinityWith = affinityWith; this.loopSwitchId = loopSwitchId; this.mirrorPointStatuses = mirrorPointStatuses; @@ -377,7 +382,7 @@ public FlowDto(String flowId, sourceVlan, destinationVlan, 0, 0, null, 0, null, null, null, null, null, null, pinned, null, detectConnectedDevices, null, null, null, - null, null, null, null, null, null, null); + null, null, null, null, null, null, null, null); } public FlowDto(FlowPayload input) { @@ -412,7 +417,7 @@ public FlowDto(FlowPayload input) { input.getDestination().getDetectConnectedDevices().isArp()), input.getPathComputationStrategy() != null ? PathComputationStrategy.valueOf( input.getPathComputationStrategy().toUpperCase()) : null, null, null, - null, null, null, null, null, null, null); + null, null, null, null, null, null, null, null); } @JsonIgnore diff --git a/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/mappers/FlowMapper.java b/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/mappers/FlowMapper.java index 1b179315346..135e71ca72f 100644 --- a/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/mappers/FlowMapper.java +++ b/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/mappers/FlowMapper.java @@ -74,6 +74,7 @@ public abstract class FlowMapper { @Mapping(target = "meterId", ignore = true) @Mapping(target = "transitEncapsulationId", ignore = true) @Mapping(target = "diverseWith", ignore = true) + @Mapping(target = "diverseWithYFlows", ignore = true) @Mapping(source = "affinityGroupId", target = "affinityWith") @Mapping(target = "mirrorPointStatuses", ignore = true) @Mapping(target = "forwardLatency", ignore = true) @@ -85,17 +86,19 @@ public abstract class FlowMapper { /** * Convert {@link Flow} to {@link FlowDto} with diverse flow ids and mirror paths. */ - public FlowDto map(Flow flow, Set diverseWith, List flowMirrorPaths) { - return map(flow, diverseWith, flowMirrorPaths, FlowStats.EMPTY); + public FlowDto map(Flow flow, Set diverseWith, Set diverseWithYFlows, + List flowMirrorPaths) { + return map(flow, diverseWith, diverseWithYFlows, flowMirrorPaths, FlowStats.EMPTY); } /** * Convert {@link Flow} to {@link FlowDto} with diverse flow ids, mirror paths and flow properties. */ - public FlowDto map(Flow flow, Set diverseWith, List flowMirrorPaths, - FlowStats flowStats) { + public FlowDto map(Flow flow, Set diverseWith, Set diverseWithYFlows, + List flowMirrorPaths, FlowStats flowStats) { FlowDto flowDto = map(flow); flowDto.setDiverseWith(diverseWith); + flowDto.setDiverseWithYFlows(diverseWithYFlows); flowDto.setMirrorPointStatuses(map(flowMirrorPaths)); flowDto.setForwardLatency(flowStats.getForwardLatency()); flowDto.setReverseLatency(flowStats.getReverseLatency()); diff --git a/src-java/base-topology/base-storm-topology/src/test/java/org/openkilda/wfm/share/mappers/FlowMapperTest.java b/src-java/base-topology/base-storm-topology/src/test/java/org/openkilda/wfm/share/mappers/FlowMapperTest.java index 051997601f2..ab8a0333691 100644 --- a/src-java/base-topology/base-storm-topology/src/test/java/org/openkilda/wfm/share/mappers/FlowMapperTest.java +++ b/src-java/base-topology/base-storm-topology/src/test/java/org/openkilda/wfm/share/mappers/FlowMapperTest.java @@ -136,7 +136,7 @@ public void testStatusDetailsMapping() { public void testMirrorPointStatusesMapping() { Flow flow = buildFlow(); - FlowDto flowDto = FlowMapper.INSTANCE.map(flow, new HashSet<>(), buildFlowMirrorPathList()); + FlowDto flowDto = FlowMapper.INSTANCE.map(flow, new HashSet<>(), new HashSet<>(), buildFlowMirrorPathList()); assertNotNull(flowDto.getMirrorPointStatuses()); assertEquals(2, flowDto.getMirrorPointStatuses().size()); diff --git a/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowDto.java b/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowDto.java index 44658fd516f..643202830ea 100644 --- a/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowDto.java +++ b/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowDto.java @@ -28,6 +28,7 @@ import java.io.Serializable; import java.time.Instant; import java.util.List; +import java.util.Set; @Data @JsonNaming(SnakeCaseStrategy.class) @@ -50,6 +51,8 @@ public class YFlowDto implements Serializable { String description; SwitchId yPoint; SwitchId protectedPathYPoint; + Set diverseWithFlows; + Set diverseWithYFlows; List subFlows; Instant timeCreate; Instant timeUpdate; diff --git a/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowPartialUpdateRequest.java b/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowPartialUpdateRequest.java index 841a17e9c86..07bb78f7856 100644 --- a/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowPartialUpdateRequest.java +++ b/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowPartialUpdateRequest.java @@ -51,5 +51,6 @@ public class YFlowPartialUpdateRequest extends CommandData { Boolean strictBandwidth; String description; Boolean allocateProtectedPath; + String diverseFlowId; List subFlows; } diff --git a/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowRequest.java b/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowRequest.java index 73d0a4f28f4..4aa8dfa160d 100644 --- a/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowRequest.java +++ b/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowRequest.java @@ -52,6 +52,7 @@ public class YFlowRequest extends CommandData { boolean strictBandwidth; String description; boolean allocateProtectedPath; + String diverseFlowId; List subFlows; diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/actions/FlowProcessingWithHistorySupportAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/actions/FlowProcessingWithHistorySupportAction.java index 3ad3a270eb2..c152ec09b9b 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/actions/FlowProcessingWithHistorySupportAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/actions/FlowProcessingWithHistorySupportAction.java @@ -173,10 +173,11 @@ protected List findServer42OuterVlanMatchSharedRuleUsage(FlowEndpoint ne return flowIds; } - protected Set getDiverseWithFlowIds(Flow flow) { - return flow.getDiverseGroupId() == null ? Collections.emptySet() : - flowRepository.findFlowsIdByDiverseGroupId(flow.getDiverseGroupId()).stream() - .filter(flowId -> !flowId.equals(flow.getFlowId())) + protected Collection getDiverseWithFlow(Flow flow) { + return flow.getDiverseGroupId() == null ? Collections.emptyList() : + flowRepository.findByDiverseGroupId(flow.getDiverseGroupId()).stream() + .filter(diverseFlow -> !flow.getFlowId().equals(diverseFlow.getFlowId()) + || (flow.getYFlowId() != null && !flow.getYFlowId().equals(diverseFlow.getYFlowId()))) .collect(Collectors.toSet()); } @@ -216,8 +217,17 @@ protected boolean isServer42FlowRttFeatureToggle() { } protected Message buildResponseMessage(Flow flow, CommandContext commandContext) { + Collection diverseWithFlow = getDiverseWithFlow(flow); + Set diverseFlows = diverseWithFlow.stream() + .filter(f -> f.getYFlowId() == null) + .map(Flow::getFlowId) + .collect(Collectors.toSet()); + Set diverseYFlows = diverseWithFlow.stream() + .map(Flow::getYFlowId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); InfoData flowData = - new FlowResponse(FlowMapper.INSTANCE.map(flow, getDiverseWithFlowIds(flow), + new FlowResponse(FlowMapper.INSTANCE.map(flow, diverseFlows, diverseYFlows, getFlowMirrorPaths(flow))); return new InfoMessage(flowData, commandContext.getCreateTime(), commandContext.getCorrelationId()); 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 a347d49564d..65212138d22 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 @@ -22,6 +22,7 @@ import org.openkilda.persistence.PersistenceManager; import org.openkilda.persistence.repositories.KildaFeatureTogglesRepository; import org.openkilda.persistence.repositories.RepositoryFactory; +import org.openkilda.persistence.repositories.YFlowRepository; import org.openkilda.wfm.share.history.model.FlowEventData; import org.openkilda.wfm.share.logger.FlowOperationsDashboardLogger; import org.openkilda.wfm.topology.flowhs.exception.FlowProcessingException; @@ -43,6 +44,7 @@ public class FlowValidateAction extends NbTrackableWithHistorySupportAction { private final KildaFeatureTogglesRepository featureTogglesRepository; + private final YFlowRepository yFlowRepository; private final FlowValidator flowValidator; private final FlowOperationsDashboardLogger dashboardLogger; @@ -51,6 +53,7 @@ public FlowValidateAction(PersistenceManager persistenceManager, FlowOperationsD RepositoryFactory repositoryFactory = persistenceManager.getRepositoryFactory(); this.featureTogglesRepository = repositoryFactory.createFeatureTogglesRepository(); + this.yFlowRepository = repositoryFactory.createYFlowRepository(); this.flowValidator = new FlowValidator(persistenceManager); this.dashboardLogger = dashboardLogger; } @@ -74,6 +77,11 @@ protected Optional performWithResponse(State from, State to, Event even format("Flow %s already exists", request.getFlowId())); } + if (yFlowRepository.exists(request.getFlowId())) { + throw new FlowProcessingException(ErrorType.ALREADY_EXISTS, + format("Y-flow %s already exists", request.getFlowId())); + } + try { flowValidator.validate(request); } catch (InvalidFlowException e) { diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/create/actions/ResourcesAllocationAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/create/actions/ResourcesAllocationAction.java index c1ec72b57be..624e7f6b44c 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/create/actions/ResourcesAllocationAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/create/actions/ResourcesAllocationAction.java @@ -29,6 +29,8 @@ import org.openkilda.model.PathId; import org.openkilda.model.SwitchId; import org.openkilda.model.SwitchProperties; +import org.openkilda.model.YFlow; +import org.openkilda.model.YSubFlow; import org.openkilda.model.cookie.FlowSegmentCookie; import org.openkilda.model.cookie.FlowSegmentCookie.FlowSegmentCookieBuilder; import org.openkilda.pce.GetPathsResult; @@ -42,6 +44,7 @@ import org.openkilda.persistence.repositories.IslRepository.IslEndpoints; import org.openkilda.persistence.repositories.KildaConfigurationRepository; import org.openkilda.persistence.repositories.SwitchPropertiesRepository; +import org.openkilda.persistence.repositories.YFlowRepository; import org.openkilda.wfm.CommandContext; import org.openkilda.wfm.error.FlowAlreadyExistException; import org.openkilda.wfm.error.FlowNotFoundException; @@ -73,11 +76,13 @@ import org.apache.commons.lang3.StringUtils; import java.time.Duration; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.stream.Stream; @Slf4j public class ResourcesAllocationAction extends @@ -88,6 +93,7 @@ public class ResourcesAllocationAction extends private final FlowResourcesManager resourcesManager; private final IslRepository islRepository; private final SwitchPropertiesRepository switchPropertiesRepository; + private final YFlowRepository yFlowRepository; private final FlowPathBuilder flowPathBuilder; private final FlowCommandBuilderFactory commandBuilderFactory; @@ -103,6 +109,7 @@ public ResourcesAllocationAction(PathComputer pathComputer, PersistenceManager p this.resourcesManager = resourcesManager; this.switchPropertiesRepository = persistenceManager.getRepositoryFactory().createSwitchPropertiesRepository(); this.islRepository = persistenceManager.getRepositoryFactory().createIslRepository(); + this.yFlowRepository = persistenceManager.getRepositoryFactory().createYFlowRepository(); KildaConfigurationRepository kildaConfigurationRepository = persistenceManager.getRepositoryFactory() .createKildaConfigurationRepository(); @@ -182,9 +189,17 @@ private void createFlow(RequestedFlow targetFlow) throws FlowNotFoundException, private Optional getFlowDiverseGroupFromContext(String diverseFlowId) throws FlowNotFoundException { if (StringUtils.isNotBlank(diverseFlowId)) { - return flowRepository.getOrCreateDiverseFlowGroupId(diverseFlowId) + String flowId = yFlowRepository.findById(diverseFlowId).map(Stream::of).orElseGet(Stream::empty) + .map(YFlow::getSubFlows) + .flatMap(Collection::stream) + .map(YSubFlow::getFlow) + .filter(flow -> flow.getFlowId().equals(flow.getAffinityGroupId())) + .map(Flow::getFlowId) + .findFirst() + .orElse(diverseFlowId); + return flowRepository.getOrCreateDiverseFlowGroupId(flowId) .map(Optional::of) - .orElseThrow(() -> new FlowNotFoundException(diverseFlowId)); + .orElseThrow(() -> new FlowNotFoundException(flowId)); } return Optional.empty(); } @@ -291,7 +306,7 @@ private void allocateProtectedPath(FlowCreateFsm stateMachine) throws Unroutable if (!tmpFlow.isAllocateProtectedPath()) { return; } - tmpFlow.setDiverseGroupId(flowRepository.getOrCreateDiverseFlowGroupId(flowId) + tmpFlow.setDiverseGroupId(getFlowDiverseGroupFromContext(flowId) .orElseThrow(() -> new FlowNotFoundException(flowId))); GetPathsResult protectedPath = pathComputer.getPath(tmpFlow); stateMachine.setBackUpProtectedPathComputationWayUsed(protectedPath.isBackUpPathComputationWayUsed()); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/pathswap/actions/UpdateFlowPathsAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/pathswap/actions/UpdateFlowPathsAction.java index a7166e35ad5..941c93dffc4 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/pathswap/actions/UpdateFlowPathsAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/pathswap/actions/UpdateFlowPathsAction.java @@ -16,14 +16,10 @@ package org.openkilda.wfm.topology.flowhs.fsm.pathswap.actions; import org.openkilda.messaging.Message; -import org.openkilda.messaging.info.InfoData; -import org.openkilda.messaging.info.InfoMessage; -import org.openkilda.messaging.info.flow.FlowResponse; import org.openkilda.model.Flow; import org.openkilda.model.FlowPath; import org.openkilda.persistence.PersistenceManager; import org.openkilda.wfm.CommandContext; -import org.openkilda.wfm.share.mappers.FlowMapper; import org.openkilda.wfm.topology.flowhs.fsm.common.actions.NbTrackableWithHistorySupportAction; import org.openkilda.wfm.topology.flowhs.fsm.pathswap.FlowPathSwapContext; import org.openkilda.wfm.topology.flowhs.fsm.pathswap.FlowPathSwapFsm; @@ -74,10 +70,7 @@ protected Optional performWithResponse(State from, State to, Event even stateMachine.saveActionToHistory("The flow paths were updated"); CommandContext commandContext = stateMachine.getCommandContext(); - InfoData flowData = - new FlowResponse(FlowMapper.INSTANCE.map(f, getDiverseWithFlowIds(f), getFlowMirrorPaths(f))); - Message response = new InfoMessage(flowData, commandContext.getCreateTime(), commandContext.getCorrelationId()); - return Optional.of(response); + return Optional.of(buildResponseMessage(f, commandContext)); } @Override diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/update/actions/UpdateFlowAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/update/actions/UpdateFlowAction.java index 924626821a6..6dd33568a16 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/update/actions/UpdateFlowAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/update/actions/UpdateFlowAction.java @@ -22,9 +22,12 @@ import org.openkilda.model.DetectConnectedDevices; import org.openkilda.model.Flow; import org.openkilda.model.Switch; +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.SwitchRepository; +import org.openkilda.persistence.repositories.YFlowRepository; import org.openkilda.wfm.share.history.model.FlowDumpData; import org.openkilda.wfm.share.history.model.FlowDumpData.DumpType; import org.openkilda.wfm.share.mappers.HistoryMapper; @@ -42,17 +45,21 @@ import lombok.extern.slf4j.Slf4j; import org.apache.storm.shade.com.google.common.base.Objects; +import java.util.Collection; import java.util.Optional; +import java.util.stream.Stream; @Slf4j public class UpdateFlowAction extends NbTrackableWithHistorySupportAction { private final SwitchRepository switchRepository; + private final YFlowRepository yFlowRepository; public UpdateFlowAction(PersistenceManager persistenceManager) { super(persistenceManager); RepositoryFactory repositoryFactory = persistenceManager.getRepositoryFactory(); - switchRepository = repositoryFactory.createSwitchRepository(); + this.switchRepository = repositoryFactory.createSwitchRepository(); + this.yFlowRepository = repositoryFactory.createYFlowRepository(); } @Override @@ -190,8 +197,16 @@ private RequestedFlow updateFlow(Flow flow, RequestedFlow targetFlow) { return targetFlow; } - private String getOrCreateDiverseFlowGroupId(String flowId) throws FlowProcessingException { - log.debug("Getting flow diverse group for flow with id {}", flowId); + private String getOrCreateDiverseFlowGroupId(String diverseFlowId) throws FlowProcessingException { + log.debug("Getting flow diverse group for flow with id {}", diverseFlowId); + String flowId = yFlowRepository.findById(diverseFlowId).map(Stream::of).orElseGet(Stream::empty) + .map(YFlow::getSubFlows) + .flatMap(Collection::stream) + .map(YSubFlow::getFlow) + .filter(flow -> flow.getFlowId().equals(flow.getAffinityGroupId())) + .map(Flow::getFlowId) + .findFirst() + .orElse(diverseFlowId); return flowRepository.getOrCreateDiverseFlowGroupId(flowId) .orElseThrow(() -> new FlowProcessingException(ErrorType.NOT_FOUND, format("Flow %s not found", flowId))); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/YFlowCreateFsm.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/YFlowCreateFsm.java index f6aa0902188..e78f2d30ceb 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/YFlowCreateFsm.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/YFlowCreateFsm.java @@ -85,6 +85,8 @@ public final class YFlowCreateFsm extends YFlowProcessingFsm requestedFlows; + private String diverseFlowId; + private YFlowCreateFsm(@NonNull CommandContext commandContext, @NonNull FlowGenericCarrier carrier, @NonNull String yFlowId, @NonNull Collection eventListeners) { super(Event.NEXT, Event.ERROR, commandContext, carrier, yFlowId, eventListeners); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/CreateDraftYFlowAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/CreateDraftYFlowAction.java index c4216a24d67..23a77dfbe9b 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/CreateDraftYFlowAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/CreateDraftYFlowAction.java @@ -52,6 +52,7 @@ public CreateDraftYFlowAction(PersistenceManager persistenceManager) { protected Optional performWithResponse(State from, State to, Event event, YFlowCreateContext context, YFlowCreateFsm stateMachine) { YFlowRequest targetFlow = stateMachine.getTargetFlow(); + stateMachine.setDiverseFlowId(targetFlow.getDiverseFlowId()); String yFlowId = targetFlow.getYFlowId(); if (yFlowRepository.exists(yFlowId)) { throw new FlowProcessingException(ErrorType.ALREADY_EXISTS, diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/CreateSubFlowsAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/CreateSubFlowsAction.java index 0ca24001803..720f83e0b20 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/CreateSubFlowsAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/CreateSubFlowsAction.java @@ -53,6 +53,7 @@ public void perform(State from, State to, Event event, YFlowCreateContext contex stateMachine.addCreatingSubFlow(subFlowId); stateMachine.notifyEventListeners(listener -> listener.onSubFlowProcessingStart(yFlowId, subFlowId)); CommandContext flowContext = stateMachine.getCommandContext().fork(subFlowId); + requestedFlow.setDiverseFlowId(stateMachine.getDiverseFlowId()); flowCreateService.startFlowCreation(flowContext, requestedFlow, yFlowId); }); } diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/OnSubFlowAllocatedAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/OnSubFlowAllocatedAction.java index bfcab5769ae..59b228e5c8b 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/OnSubFlowAllocatedAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/OnSubFlowAllocatedAction.java @@ -20,6 +20,7 @@ import org.openkilda.messaging.Message; import org.openkilda.messaging.command.yflow.SubFlowDto; import org.openkilda.messaging.command.yflow.SubFlowSharedEndpointEncapsulation; +import org.openkilda.messaging.command.yflow.YFlowDto; import org.openkilda.messaging.command.yflow.YFlowResponse; import org.openkilda.messaging.error.ErrorType; import org.openkilda.messaging.info.InfoMessage; @@ -42,7 +43,11 @@ import lombok.extern.slf4j.Slf4j; +import java.util.Collection; +import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; @Slf4j public class OnSubFlowAllocatedAction extends @@ -126,10 +131,31 @@ protected Optional performWithResponse(State from, State to, Event even } private Message buildResponseMessage(YFlow yFlow, CommandContext commandContext) { - YFlowResponse response = YFlowResponse.builder().yFlow(YFlowMapper.INSTANCE.toYFlowDto(yFlow)).build(); + YFlowResponse response = YFlowResponse.builder() + .yFlow(convertToYFlowDto(yFlow)) + .build(); return new InfoMessage(response, commandContext.getCreateTime(), commandContext.getCorrelationId()); } + private YFlowDto convertToYFlowDto(YFlow yFlow) { + Flow mainAffinityFlow = yFlow.getSubFlows().stream() + .map(YSubFlow::getFlow) + .filter(flow -> flow.getFlowId().equals(flow.getAffinityGroupId())) + .findFirst().orElseThrow(() -> new FlowProcessingException(ErrorType.INTERNAL_ERROR, + "Main affinity flow not found")); + Collection diverseWithFlow = getDiverseWithFlow(mainAffinityFlow); + Set diverseFlows = diverseWithFlow.stream() + .filter(flow -> flow.getYFlowId() == null) + .map(Flow::getFlowId) + .collect(Collectors.toSet()); + Set diverseYFlows = diverseWithFlow.stream() + .map(Flow::getYFlowId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + return YFlowMapper.INSTANCE.toYFlowDto(yFlow, diverseFlows, diverseYFlows); + } + @Override protected String getGenericErrorMessage() { return "Could not create y-flow"; 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 f4cec39089b..5950248590e 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 @@ -72,6 +72,11 @@ protected Optional performWithResponse(State from, State to, Event even throw new FlowProcessingException(ErrorType.NOT_PERMITTED, "Y-flow create feature is disabled"); } + if (flowRepository.exists(yFlowId)) { + throw new FlowProcessingException(ErrorType.ALREADY_EXISTS, + format("Flow %s already exists", yFlowId)); + } + if (yFlowRepository.exists(yFlowId)) { throw new FlowProcessingException(ErrorType.ALREADY_EXISTS, format("Y-flow %s already exists", yFlowId)); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/delete/actions/ValidateYFlowAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/delete/actions/ValidateYFlowAction.java index 511a1d28c0c..abd638dfc9b 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/delete/actions/ValidateYFlowAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/delete/actions/ValidateYFlowAction.java @@ -18,11 +18,14 @@ import static java.lang.String.format; import org.openkilda.messaging.Message; +import org.openkilda.messaging.command.yflow.YFlowDto; import org.openkilda.messaging.command.yflow.YFlowResponse; 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.model.YSubFlow; import org.openkilda.persistence.PersistenceManager; import org.openkilda.persistence.repositories.KildaFeatureTogglesRepository; import org.openkilda.persistence.repositories.RepositoryFactory; @@ -40,7 +43,12 @@ import lombok.extern.slf4j.Slf4j; +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; @Slf4j public class ValidateYFlowAction extends @@ -90,10 +98,34 @@ protected Optional performWithResponse(State from, State to, Event even } private Message buildResponseMessage(YFlow yFlow, CommandContext commandContext) { - YFlowResponse response = YFlowResponse.builder().yFlow(YFlowMapper.INSTANCE.toYFlowDto(yFlow)).build(); + YFlowResponse response = YFlowResponse.builder() + .yFlow(convertToYFlowDto(yFlow)) + .build(); return new InfoMessage(response, commandContext.getCreateTime(), commandContext.getCorrelationId()); } + private YFlowDto convertToYFlowDto(YFlow yFlow) { + Optional flow = yFlow.getSubFlows().stream() + .map(YSubFlow::getFlow) + .filter(f -> f.getFlowId().equals(f.getAffinityGroupId())) + .findFirst(); + Set diverseFlows = new HashSet<>(); + Set diverseYFlows = new HashSet<>(); + if (flow.isPresent()) { + Collection diverseWithFlow = getDiverseWithFlow(flow.get()); + diverseFlows = diverseWithFlow.stream() + .filter(f -> f.getYFlowId() == null) + .map(Flow::getFlowId) + .collect(Collectors.toSet()); + diverseYFlows = diverseWithFlow.stream() + .map(Flow::getYFlowId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + } + + return YFlowMapper.INSTANCE.toYFlowDto(yFlow, diverseFlows, diverseYFlows); + } + @Override protected String getGenericErrorMessage() { return "Could not delete y-flow"; diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/YFlowUpdateFsm.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/YFlowUpdateFsm.java index 0c77ae44d47..45b4e38a934 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/YFlowUpdateFsm.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/YFlowUpdateFsm.java @@ -97,6 +97,8 @@ public final class YFlowUpdateFsm extends YFlowProcessingFsm requestedFlows; + private String diverseFlowId; + private Collection deleteOldYFlowCommands; private YFlowUpdateFsm(@NonNull CommandContext commandContext, @NonNull FlowGenericCarrier carrier, diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnSubFlowAllocatedAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnSubFlowAllocatedAction.java index 9d5f84876f2..d7f995f162a 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnSubFlowAllocatedAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnSubFlowAllocatedAction.java @@ -20,6 +20,7 @@ import org.openkilda.messaging.Message; import org.openkilda.messaging.command.yflow.SubFlowDto; import org.openkilda.messaging.command.yflow.SubFlowSharedEndpointEncapsulation; +import org.openkilda.messaging.command.yflow.YFlowDto; import org.openkilda.messaging.command.yflow.YFlowResponse; import org.openkilda.messaging.error.ErrorType; import org.openkilda.messaging.info.InfoMessage; @@ -42,7 +43,11 @@ import lombok.extern.slf4j.Slf4j; +import java.util.Collection; +import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; @Slf4j public class OnSubFlowAllocatedAction extends @@ -125,10 +130,31 @@ protected Optional performWithResponse(State from, State to, Event even } private Message buildResponseMessage(YFlow yFlow, CommandContext commandContext) { - YFlowResponse response = YFlowResponse.builder().yFlow(YFlowMapper.INSTANCE.toYFlowDto(yFlow)).build(); + YFlowResponse response = YFlowResponse.builder() + .yFlow(convertToYFlowDto(yFlow)) + .build(); return new InfoMessage(response, commandContext.getCreateTime(), commandContext.getCorrelationId()); } + private YFlowDto convertToYFlowDto(YFlow yFlow) { + Flow mainAffinityFlow = yFlow.getSubFlows().stream() + .map(YSubFlow::getFlow) + .filter(flow -> flow.getFlowId().equals(flow.getAffinityGroupId())) + .findFirst().orElseThrow(() -> new FlowProcessingException(ErrorType.INTERNAL_ERROR, + "Main affinity flow not found")); + Collection diverseWithFlow = getDiverseWithFlow(mainAffinityFlow); + Set diverseFlows = diverseWithFlow.stream() + .filter(flow -> flow.getYFlowId() == null) + .map(Flow::getFlowId) + .collect(Collectors.toSet()); + Set diverseYFlows = diverseWithFlow.stream() + .map(Flow::getYFlowId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + return YFlowMapper.INSTANCE.toYFlowDto(yFlow, diverseFlows, diverseYFlows); + } + @Override protected String getGenericErrorMessage() { return "Could not update y-flow"; diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/UpdateSubFlowsAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/UpdateSubFlowsAction.java index 2cc124a8e64..ce17d53d65a 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/UpdateSubFlowsAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/UpdateSubFlowsAction.java @@ -55,6 +55,7 @@ public void perform(State from, State to, Event event, YFlowUpdateContext contex stateMachine.notifyEventListeners(listener -> listener.onSubFlowProcessingStart(yFlowId, subFlowId)); CommandContext flowContext = stateMachine.getCommandContext().fork(subFlowId); + requestedFlow.setDiverseFlowId(stateMachine.getDiverseFlowId()); flowUpdateService.startFlowUpdating(flowContext, requestedFlow, yFlowId); }); } diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/UpdateYFlowAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/UpdateYFlowAction.java index ecfecb4d941..456c5c6a24b 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/UpdateYFlowAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/UpdateYFlowAction.java @@ -43,6 +43,7 @@ public UpdateYFlowAction(PersistenceManager persistenceManager, RuleManager rule @Override protected void perform(State from, State to, Event event, YFlowUpdateContext context, YFlowUpdateFsm stateMachine) { YFlowRequest targetFlow = stateMachine.getTargetFlow(); + stateMachine.setDiverseFlowId(targetFlow.getDiverseFlowId()); FlowStatus flowStatus = transactionManager.doInTransaction(() -> { YFlow yFlow = getYFlow(targetFlow.getYFlowId()); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/mapper/YFlowMapper.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/mapper/YFlowMapper.java index c4b5ae0f3d4..feaaf8c1ff3 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/mapper/YFlowMapper.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/mapper/YFlowMapper.java @@ -29,13 +29,14 @@ import org.mapstruct.factory.Mappers; import java.util.Optional; +import java.util.Set; @Mapper public abstract class YFlowMapper { public static final YFlowMapper INSTANCE = Mappers.getMapper(YFlowMapper.class); - @Mapping(target = "timeUpdate", source = "timeModify") - public abstract YFlowDto toYFlowDto(YFlow flow); + @Mapping(target = "timeUpdate", source = "flow.timeModify") + public abstract YFlowDto toYFlowDto(YFlow flow, Set diverseWithFlows, Set diverseWithYFlows); @Mapping(target = "outerVlanId", ignore = true) @Mapping(target = "innerVlanId", ignore = true) diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/mapper/YFlowRequestMapper.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/mapper/YFlowRequestMapper.java index f5ee3ecd4bf..5a86a532576 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/mapper/YFlowRequestMapper.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/mapper/YFlowRequestMapper.java @@ -52,6 +52,7 @@ public abstract class YFlowRequestMapper { @Mapping(target = "sharedEndpoint.innerVlanId", ignore = true) @Mapping(target = "sharedEndpoint.trackLldpConnectedDevices", ignore = true) @Mapping(target = "sharedEndpoint.trackArpConnectedDevices", ignore = true) + @Mapping(target = "diverseFlowId", ignore = true) public abstract YFlowRequest toYFlowRequest(YFlow yFlow); @Mapping(target = "flowId", source = "subFlowId") diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowReadService.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowReadService.java index b50bdaf2fda..bc2243c6888 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowReadService.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowReadService.java @@ -18,8 +18,10 @@ import org.openkilda.messaging.command.yflow.SubFlowDto; import org.openkilda.messaging.command.yflow.SubFlowPathDto; import org.openkilda.messaging.command.yflow.SubFlowsResponse; +import org.openkilda.messaging.command.yflow.YFlowDto; import org.openkilda.messaging.command.yflow.YFlowPathsResponse; import org.openkilda.messaging.command.yflow.YFlowResponse; +import org.openkilda.messaging.error.ErrorType; import org.openkilda.messaging.info.event.PathInfoData; import org.openkilda.model.Flow; import org.openkilda.model.FlowPath; @@ -28,11 +30,13 @@ import org.openkilda.model.YSubFlow; import org.openkilda.persistence.PersistenceManager; import org.openkilda.persistence.exceptions.PersistenceException; +import org.openkilda.persistence.repositories.FlowRepository; import org.openkilda.persistence.repositories.YFlowRepository; import org.openkilda.persistence.tx.TransactionManager; import org.openkilda.wfm.error.FlowNotFoundException; 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.mapper.YFlowMapper; import lombok.NonNull; @@ -41,14 +45,17 @@ import java.time.Duration; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @Slf4j public class YFlowReadService { private final YFlowRepository yFlowRepository; + private final FlowRepository flowRepository; private final TransactionManager transactionManager; private final int readOperationRetriesLimit; private final Duration readOperationRetryDelay; @@ -56,6 +63,7 @@ public class YFlowReadService { public YFlowReadService(@NonNull PersistenceManager persistenceManager, int readOperationRetriesLimit, @NonNull Duration readOperationRetryDelay) { this.yFlowRepository = persistenceManager.getRepositoryFactory().createYFlowRepository(); + this.flowRepository = persistenceManager.getRepositoryFactory().createFlowRepository(); this.transactionManager = persistenceManager.getTransactionManager(); this.readOperationRetriesLimit = readOperationRetriesLimit; this.readOperationRetryDelay = readOperationRetryDelay; @@ -78,7 +86,7 @@ public List getAllYFlows() { Collection yFlows = transactionManager.doInTransaction(getReadOperationRetryPolicy(), yFlowRepository::findAll); return yFlows.stream() - .map(YFlowMapper.INSTANCE::toYFlowDto) + .map(this::convertToYFlowDto) .map(YFlowResponse::new) .collect(Collectors.toList()); } @@ -89,7 +97,7 @@ public List getAllYFlows() { public YFlowResponse getYFlow(@NonNull String yFlowId) throws FlowNotFoundException { return transactionManager.doInTransaction(getReadOperationRetryPolicy(), () -> yFlowRepository.findById(yFlowId)) - .map(YFlowMapper.INSTANCE::toYFlowDto) + .map(this::convertToYFlowDto) .map(YFlowResponse::new) .orElseThrow(() -> new FlowNotFoundException(yFlowId)); } @@ -147,4 +155,30 @@ public SubFlowsResponse getYFlowSubFlows(@NonNull String yFlowId) throws FlowNot return new SubFlowsResponse(subFlows); }); } + + private YFlowDto convertToYFlowDto(YFlow yFlow) { + Flow mainAffinityFlow = yFlow.getSubFlows().stream() + .map(YSubFlow::getFlow) + .filter(flow -> flow.getFlowId().equals(flow.getAffinityGroupId())) + .findFirst().orElseThrow(() -> new FlowProcessingException(ErrorType.INTERNAL_ERROR, + "Main affinity flow not found")); + Collection diverseWithFlow = getDiverseWithFlow(mainAffinityFlow); + Set diverseFlows = diverseWithFlow.stream() + .filter(flow -> flow.getYFlowId() == null) + .map(Flow::getFlowId) + .collect(Collectors.toSet()); + Set diverseYFlows = diverseWithFlow.stream() + .map(Flow::getYFlowId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + return YFlowMapper.INSTANCE.toYFlowDto(yFlow, diverseFlows, diverseYFlows); + } + + private Collection getDiverseWithFlow(Flow flow) { + return flow.getDiverseGroupId() == null ? Collections.emptyList() : + flowRepository.findByDiverseGroupId(flow.getDiverseGroupId()).stream() + .filter(diverseFlow -> !flow.getYFlowId().equals(diverseFlow.getYFlowId())) + .collect(Collectors.toSet()); + } } diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowUpdateService.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowUpdateService.java index b29fc98d6b6..4179f616cad 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowUpdateService.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowUpdateService.java @@ -222,6 +222,7 @@ public void handlePartialUpdateRequest(@NonNull String key, @NonNull CommandCont Optional.ofNullable(request.getStrictBandwidth()).ifPresent(target::setStrictBandwidth); Optional.ofNullable(request.getDescription()).ifPresent(target::setDescription); Optional.ofNullable(request.getAllocateProtectedPath()).ifPresent(target::setAllocateProtectedPath); + Optional.ofNullable(request.getDiverseFlowId()).ifPresent(target::setDiverseFlowId); if (request.getSubFlows() != null && !request.getSubFlows().isEmpty()) { Map stringSubFlowDtoMap; diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/validation/FlowValidator.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/validation/FlowValidator.java index 309d5225ae9..ffca4942e1b 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/validation/FlowValidator.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/validation/FlowValidator.java @@ -29,6 +29,8 @@ import org.openkilda.model.Switch; import org.openkilda.model.SwitchId; import org.openkilda.model.SwitchProperties; +import org.openkilda.model.YFlow; +import org.openkilda.model.YSubFlow; import org.openkilda.persistence.PersistenceManager; import org.openkilda.persistence.repositories.FlowMirrorPathRepository; import org.openkilda.persistence.repositories.FlowMirrorPointsRepository; @@ -37,6 +39,7 @@ import org.openkilda.persistence.repositories.PhysicalPortRepository; import org.openkilda.persistence.repositories.SwitchPropertiesRepository; import org.openkilda.persistence.repositories.SwitchRepository; +import org.openkilda.persistence.repositories.YFlowRepository; import org.openkilda.wfm.topology.flowhs.mapper.RequestedFlowMapper; import org.openkilda.wfm.topology.flowhs.model.RequestedFlow; import org.openkilda.wfm.topology.flowhs.model.RequestedFlowMirrorPoint; @@ -63,6 +66,7 @@ public class FlowValidator { private final FlowRepository flowRepository; + private final YFlowRepository yFlowRepository; private final SwitchRepository switchRepository; private final IslRepository islRepository; private final SwitchPropertiesRepository switchPropertiesRepository; @@ -72,6 +76,7 @@ public class FlowValidator { public FlowValidator(PersistenceManager persistenceManager) { this.flowRepository = persistenceManager.getRepositoryFactory().createFlowRepository(); + this.yFlowRepository = persistenceManager.getRepositoryFactory().createYFlowRepository(); this.switchRepository = persistenceManager.getRepositoryFactory().createSwitchRepository(); this.islRepository = persistenceManager.getRepositoryFactory().createIslRepository(); this.switchPropertiesRepository = persistenceManager.getRepositoryFactory().createSwitchPropertiesRepository(); @@ -361,10 +366,21 @@ void checkDiverseFlow(RequestedFlow targetFlow) throws InvalidFlowException { ErrorType.PARAMETERS_INVALID); } - Flow diverseFlow = flowRepository.findById(targetFlow.getDiverseFlowId()) - .orElseThrow(() -> - new InvalidFlowException(format("Failed to find diverse flow id %s", - targetFlow.getDiverseFlowId()), ErrorType.PARAMETERS_INVALID)); + Flow diverseFlow = flowRepository.findById(targetFlow.getDiverseFlowId()).orElse(null); + if (diverseFlow == null) { + YFlow diverseYFlow = yFlowRepository.findById(targetFlow.getDiverseFlowId()) + .orElseThrow(() -> + new InvalidFlowException(format("Failed to find diverse flow id %s", + targetFlow.getDiverseFlowId()), ErrorType.PARAMETERS_INVALID)); + diverseFlow = diverseYFlow.getSubFlows().stream() + .map(YSubFlow::getFlow) + .filter(flow -> flow.getFlowId().equals(flow.getAffinityGroupId())) + .findFirst() + .orElseThrow(() -> + new InvalidFlowException( + format("Failed to find main affinity flow for diverse y-flow id %s", + targetFlow.getDiverseFlowId()), ErrorType.INTERNAL_ERROR)); + } if (StringUtils.isNotBlank(diverseFlow.getAffinityGroupId())) { diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/test/java/org/openkilda/wfm/topology/flowhs/service/AbstractYFlowTest.java b/src-java/flowhs-topology/flowhs-storm-topology/src/test/java/org/openkilda/wfm/topology/flowhs/service/AbstractYFlowTest.java index dca2c4a80c9..3703b5e6fd7 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/test/java/org/openkilda/wfm/topology/flowhs/service/AbstractYFlowTest.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/test/java/org/openkilda/wfm/topology/flowhs/service/AbstractYFlowTest.java @@ -660,9 +660,11 @@ protected Flow buildFlowIdArgumentMatch(String flowId) { protected YFlow createYFlowViaTransit(String yFlowId) { // Create sub-flows Flow firstFlow = - dummyFactory.makeFlow(firstSharedEndpoint, firstEndpoint, islSharedToTransit, islTransitToFirst); + dummyFactory.makeMainAffinityFlow(firstSharedEndpoint, firstEndpoint, + islSharedToTransit, islTransitToFirst); Flow secondFlow = - dummyFactory.makeFlow(secondSharedEndpoint, secondEndpoint, islSharedToTransit, islTransitToSecond); + dummyFactory.makeFlow(secondSharedEndpoint, secondEndpoint, firstFlow.getAffinityGroupId(), + islSharedToTransit, islTransitToSecond); SwitchId yPoint = SWITCH_TRANSIT; FlowMeter yPointMeter = dummyFactory.makeFlowMeter(yPoint, yFlowId, null); @@ -698,10 +700,11 @@ protected YFlow createYFlowViaTransit(String yFlowId) { protected YFlow createYFlowWithProtected(String yFlowId) { dummyFactory.getFlowDefaults().setAllocateProtectedPath(true); // Create sub-flows - Flow firstFlow = dummyFactory.makeFlowWithProtectedPath(firstSharedEndpoint, firstEndpoint, + Flow firstFlow = dummyFactory.makeMainAffinityFlowWithProtectedPath(firstSharedEndpoint, firstEndpoint, asList(islSharedToTransit, islTransitToFirst), asList(islSharedToAltTransit, islAltTransitToFirst)); Flow secondFlow = dummyFactory.makeFlowWithProtectedPath(secondSharedEndpoint, secondEndpoint, + firstFlow.getAffinityGroupId(), asList(islSharedToTransit, islTransitToSecond), asList(islSharedToAltTransit, islAltTransitToSecond)); diff --git a/src-java/kilda-persistence-api/src/test/java/org/openkilda/persistence/dummy/PersistenceDummyEntityFactory.java b/src-java/kilda-persistence-api/src/test/java/org/openkilda/persistence/dummy/PersistenceDummyEntityFactory.java index d077539f87a..8cebb36965a 100644 --- a/src-java/kilda-persistence-api/src/test/java/org/openkilda/persistence/dummy/PersistenceDummyEntityFactory.java +++ b/src-java/kilda-persistence-api/src/test/java/org/openkilda/persistence/dummy/PersistenceDummyEntityFactory.java @@ -170,22 +170,34 @@ public Isl makeIsl(IslEndpoint source, IslEndpoint dest) { return isl; } + public Flow makeMainAffinityFlow(FlowEndpoint source, FlowEndpoint dest, IslDirectionalReference... trace) { + String flowId = idProvider.provideFlowId(); + return makeFlow(flowId, source, dest, flowId, Arrays.asList(trace)); + } + public Flow makeFlow(FlowEndpoint source, FlowEndpoint dest, IslDirectionalReference... trace) { - return makeFlow(source, dest, Arrays.asList(trace)); + return makeFlow(source, dest, null, trace); + } + + public Flow makeFlow(FlowEndpoint source, FlowEndpoint dest, String affinityGroupId, + IslDirectionalReference... trace) { + return makeFlow(idProvider.provideFlowId(), source, dest, affinityGroupId, Arrays.asList(trace)); } /** * Create {@link Flow} object. */ - public Flow makeFlow(FlowEndpoint source, FlowEndpoint dest, List pathHint) { + public Flow makeFlow(String flowId, FlowEndpoint source, FlowEndpoint dest, String affinityGroupId, + List pathHint) { Flow flow = flowDefaults.fill(Flow.builder()) - .flowId(idProvider.provideFlowId()) + .flowId(flowId) .srcSwitch(fetchOrCreateSwitch(source.getSwitchId())) .srcPort(source.getPortNumber()) .srcVlan(source.getOuterVlanId()) .destSwitch(fetchOrCreateSwitch(dest.getSwitchId())) .destPort(dest.getPortNumber()) .destVlan(dest.getOuterVlanId()) + .affinityGroupId(affinityGroupId) .build(); return txManager.doInTransaction(() -> { makeFlowPathPair(flow, source, dest, pathHint); @@ -199,14 +211,34 @@ public Flow makeFlow(FlowEndpoint source, FlowEndpoint dest, List pathHint, + List protectedPathHint) { + String flowId = idProvider.provideFlowId(); + return makeFlowWithProtectedPath(flowId, source, dest, flowId, pathHint, protectedPathHint); + } + + public Flow makeFlowWithProtectedPath(FlowEndpoint source, FlowEndpoint dest, + List pathHint, + List protectedPathHint) { + return makeFlowWithProtectedPath(idProvider.provideFlowId(), source, dest, null, pathHint, protectedPathHint); + } + + public Flow makeFlowWithProtectedPath(FlowEndpoint source, FlowEndpoint dest, String affinityGroupId, + List pathHint, + List protectedPathHint) { + return makeFlowWithProtectedPath( + idProvider.provideFlowId(), source, dest, affinityGroupId, pathHint, protectedPathHint); + } + /** * Create {@link Flow} object with protected paths. */ - public Flow makeFlowWithProtectedPath(FlowEndpoint source, FlowEndpoint dest, + public Flow makeFlowWithProtectedPath(String flowId, FlowEndpoint source, FlowEndpoint dest, String affinityGroupId, List pathHint, List protectedPathHint) { Flow flow = flowDefaults.fill(Flow.builder()) - .flowId(idProvider.provideFlowId()) + .flowId(flowId) .srcSwitch(fetchOrCreateSwitch(source.getSwitchId())) .srcPort(source.getPortNumber()) .srcVlan(source.getOuterVlanId()) @@ -214,6 +246,7 @@ public Flow makeFlowWithProtectedPath(FlowEndpoint source, FlowEndpoint dest, .destPort(dest.getPortNumber()) .destVlan(dest.getOuterVlanId()) .allocateProtectedPath(true) + .affinityGroupId(affinityGroupId) .build(); return txManager.doInTransaction(() -> { makeFlowPathPair(flow, source, dest, protectedPathHint, Collections.singletonList("protected")); diff --git a/src-java/nbworker-topology/nbworker-storm-topology/src/main/java/org/openkilda/wfm/topology/nbworker/bolts/FlowOperationsBolt.java b/src-java/nbworker-topology/nbworker-storm-topology/src/main/java/org/openkilda/wfm/topology/nbworker/bolts/FlowOperationsBolt.java index 302c1dba448..00391e09574 100644 --- a/src-java/nbworker-topology/nbworker-storm-topology/src/main/java/org/openkilda/wfm/topology/nbworker/bolts/FlowOperationsBolt.java +++ b/src-java/nbworker-topology/nbworker-storm-topology/src/main/java/org/openkilda/wfm/topology/nbworker/bolts/FlowOperationsBolt.java @@ -26,7 +26,6 @@ import org.openkilda.messaging.info.InfoData; import org.openkilda.messaging.info.flow.FlowResponse; import org.openkilda.messaging.info.flow.FlowsResponse; -import org.openkilda.messaging.model.FlowDto; import org.openkilda.messaging.nbtopology.request.BaseRequest; import org.openkilda.messaging.nbtopology.request.FlowConnectedDeviceRequest; import org.openkilda.messaging.nbtopology.request.FlowMirrorPointsDumpRequest; @@ -57,7 +56,6 @@ import org.openkilda.wfm.error.IslNotFoundException; import org.openkilda.wfm.error.SwitchNotFoundException; import org.openkilda.wfm.share.mappers.ConnectedDeviceMapper; -import org.openkilda.wfm.share.mappers.FlowMapper; import org.openkilda.wfm.share.metrics.TimedExecution; import org.openkilda.wfm.topology.nbworker.StreamType; import org.openkilda.wfm.topology.nbworker.services.FlowOperationsService; @@ -135,9 +133,7 @@ private List processGetFlowsForLinkRequest(GetFlowsForIslRequest r .filter(flowPath -> flowPath.getFlow().isActualPathId(flowPath.getPathId())) .map(FlowPath::getFlow) .distinct() - .map(f -> FlowMapper.INSTANCE.map(f, flowOperationsService.getDiverseFlowsId(f), - flowOperationsService.getFlowMirrorPaths(f))) - .map(FlowResponse::new) + .map(flowOperationsService::buildFlowResponse) .collect(Collectors.toList()); } catch (IslNotFoundException e) { throw new MessageException(ErrorType.NOT_FOUND, e.getMessage(), "ISL was not found."); @@ -152,9 +148,7 @@ private List processGetFlowsForSwitchRequest(GetFlowsForSwitchRequ try { return flowOperationsService.getFlowsForEndpoint(srcSwitch, srcPort).stream() .distinct() - .map(f -> FlowMapper.INSTANCE.map(f, flowOperationsService.getDiverseFlowsId(f), - flowOperationsService.getFlowMirrorPaths(f))) - .map(FlowResponse::new) + .map(flowOperationsService::buildFlowResponse) .collect(Collectors.toList()); } catch (SwitchNotFoundException e) { throw new MessageException(ErrorType.NOT_FOUND, e.getMessage(), "Switch was not found."); @@ -252,9 +246,7 @@ private List processFlowReadRequest(FlowReadRequest readRequest) { String flowId = readRequest.getFlowId(); Flow flow = flowOperationsService.getFlow(flowId); FlowStats flowStats = flowOperationsService.getFlowStats(flowId); - FlowDto dto = FlowMapper.INSTANCE.map(flow, flowOperationsService.getDiverseFlowsId(flow), - flowOperationsService.getFlowMirrorPaths(flow), flowStats); - FlowResponse response = new FlowResponse(dto); + FlowResponse response = flowOperationsService.buildFlowResponse(flow, flowStats); return Collections.singletonList(response); } catch (FlowNotFoundException e) { throw new MessageException(ErrorType.NOT_FOUND, "Can not get flow: " + e.getMessage(), @@ -271,10 +263,8 @@ private List processFlowsDumpRequest(FlowsDumpRequest request) { Map flowStats = flowOperationsService.getFlowStats() .stream().collect(toMap(FlowStats::getFlowId, Function.identity())); return flowOperationsService.getAllFlows(request).stream() - .map(f -> FlowMapper.INSTANCE.map(f, flowOperationsService.getDiverseFlowsId(f), - flowOperationsService.getFlowMirrorPaths(f), + .map(f -> flowOperationsService.buildFlowResponse(f, flowStats.getOrDefault(f.getFlowId(), FlowStats.EMPTY))) - .map(FlowResponse::new) .collect(Collectors.toList()); } catch (Exception e) { log.error("Can not dump flows", e); diff --git a/src-java/nbworker-topology/nbworker-storm-topology/src/main/java/org/openkilda/wfm/topology/nbworker/services/FlowOperationsService.java b/src-java/nbworker-topology/nbworker-storm-topology/src/main/java/org/openkilda/wfm/topology/nbworker/services/FlowOperationsService.java index 5c435c3b987..15d8e0bdae9 100644 --- a/src-java/nbworker-topology/nbworker-storm-topology/src/main/java/org/openkilda/wfm/topology/nbworker/services/FlowOperationsService.java +++ b/src-java/nbworker-topology/nbworker-storm-topology/src/main/java/org/openkilda/wfm/topology/nbworker/services/FlowOperationsService.java @@ -45,6 +45,8 @@ import org.openkilda.model.PathComputationStrategy; import org.openkilda.model.SwitchConnectedDevice; import org.openkilda.model.SwitchId; +import org.openkilda.model.YFlow; +import org.openkilda.model.YSubFlow; import org.openkilda.persistence.exceptions.PersistenceException; import org.openkilda.persistence.repositories.FlowPathRepository; import org.openkilda.persistence.repositories.FlowRepository; @@ -79,6 +81,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -93,14 +96,14 @@ public class FlowOperationsService { private static final int RETRY_DELAY = 100; private static final Set LATENCY_BASED_STRATEGIES = Sets.newHashSet(MAX_LATENCY, LATENCY); - private TransactionManager transactionManager; - private IslRepository islRepository; - private SwitchRepository switchRepository; - private FlowRepository flowRepository; - private FlowStatsRepository flowStatsRepository; - private FlowPathRepository flowPathRepository; - private SwitchConnectedDeviceRepository switchConnectedDeviceRepository; - private YFlowRepository yFlowRepository; + private final TransactionManager transactionManager; + private final IslRepository islRepository; + private final SwitchRepository switchRepository; + private final FlowRepository flowRepository; + private final FlowStatsRepository flowStatsRepository; + private final FlowPathRepository flowPathRepository; + private final SwitchConnectedDeviceRepository switchConnectedDeviceRepository; + private final YFlowRepository yFlowRepository; public FlowOperationsService(RepositoryFactory repositoryFactory, TransactionManager transactionManager) { this.islRepository = repositoryFactory.createIslRepository(); @@ -109,7 +112,7 @@ public FlowOperationsService(RepositoryFactory repositoryFactory, TransactionMan this.flowStatsRepository = repositoryFactory.createFlowStatsRepository(); this.flowPathRepository = repositoryFactory.createFlowPathRepository(); this.switchConnectedDeviceRepository = repositoryFactory.createSwitchConnectedDeviceRepository(); - yFlowRepository = repositoryFactory.createYFlowRepository(); + this.yFlowRepository = repositoryFactory.createYFlowRepository(); this.transactionManager = transactionManager; } @@ -149,16 +152,6 @@ public Collection getFlowStats() { () -> flowStatsRepository.findAll()); } - /** - * Return flow ids in the same flow diverse group. - */ - public Set getDiverseFlowsId(Flow flow) { - return flow.getDiverseGroupId() == null ? Collections.emptySet() : - flowRepository.findFlowsIdByDiverseGroupId(flow.getDiverseGroupId()).stream() - .filter(flowId -> !flowId.equals(flow.getFlowId())) - .collect(Collectors.toSet()); - } - /** * Return flow mirror paths by flow. */ @@ -398,8 +391,7 @@ public Flow updateFlow(FlowOperationsCarrier carrier, FlowPatch flowPatch) throw carrier.sendUpdateRequest(addChangedFields(flowRequest, flowPatch)); } else { flowDashboardLogger.onFlowPatchUpdate(updatedFlow); - carrier.sendNorthboundResponse(new FlowResponse(FlowMapper.INSTANCE.map(updatedFlow, - getDiverseFlowsId(updatedFlow), getFlowMirrorPaths(updatedFlow)))); + carrier.sendNorthboundResponse(buildFlowResponse(updatedFlow)); } return updateFlowResult.getUpdatedFlow(); @@ -487,10 +479,21 @@ private boolean updateRequiredByDestination(FlowPatch flowPatch, Flow flow) { } private boolean updateRequiredByDiverseFlowIdField(FlowPatch flowPatch, Flow flow) { - return flowPatch.getDiverseFlowId() != null - && flowRepository.getOrCreateDiverseFlowGroupId(flowPatch.getDiverseFlowId()) - .map(groupId -> !flowRepository.findFlowsIdByDiverseGroupId(groupId).contains(flow.getFlowId())) - .orElse(true); + if (flowPatch.getDiverseFlowId() != null) { + String diverseFlowId = yFlowRepository.findById(flowPatch.getDiverseFlowId()) + .map(Stream::of).orElseGet(Stream::empty) + .map(YFlow::getSubFlows) + .flatMap(Collection::stream) + .map(YSubFlow::getFlow) + .filter(f -> f.getFlowId().equals(f.getAffinityGroupId())) + .map(Flow::getFlowId) + .findFirst() + .orElse(flowPatch.getDiverseFlowId()); + return flowRepository.getOrCreateDiverseFlowGroupId(diverseFlowId) + .map(groupId -> !flowRepository.findFlowsIdByDiverseGroupId(groupId).contains(flow.getFlowId())) + .orElse(true); + } + return false; } private FlowRequest addChangedFields(FlowRequest flowRequest, FlowPatch flowPatch) { @@ -639,6 +642,39 @@ public List getFlowMirrorPoints(String flowId) throws FlowNotFo }).orElseThrow(() -> new FlowNotFoundException(flowId)); } + /** + * Build flow response message. + */ + public FlowResponse buildFlowResponse(Flow flow, FlowStats flowStats) { + Collection diverseWithFlow = getDiverseWithFlow(flow); + Set diverseFlows = diverseWithFlow.stream() + .filter(f -> f.getYFlowId() == null) + .map(Flow::getFlowId) + .collect(Collectors.toSet()); + Set diverseYFlows = diverseWithFlow.stream() + .map(Flow::getYFlowId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + return new FlowResponse( + FlowMapper.INSTANCE.map(flow, diverseFlows, diverseYFlows, getFlowMirrorPaths(flow), flowStats)); + } + + /** + * Build flow response message with FlowStats.EMPTY. + */ + public FlowResponse buildFlowResponse(Flow flow) { + return buildFlowResponse(flow, FlowStats.EMPTY); + } + + private Collection getDiverseWithFlow(Flow flow) { + return flow.getDiverseGroupId() == null ? Collections.emptyList() : + flowRepository.findByDiverseGroupId(flow.getDiverseGroupId()).stream() + .filter(diverseFlow -> !flow.getFlowId().equals(diverseFlow.getFlowId()) + || (flow.getYFlowId() != null && !flow.getYFlowId().equals(diverseFlow.getYFlowId()))) + .collect(Collectors.toSet()); + } + @Data @Builder static class UpdateFlowResult { diff --git a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/flows/FlowResponseV2.java b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/flows/FlowResponseV2.java index 38cc961893f..4fb7fa4b663 100644 --- a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/flows/FlowResponseV2.java +++ b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/flows/FlowResponseV2.java @@ -56,6 +56,8 @@ public class FlowResponseV2 { private Integer priority; private Set diverseWith; + @JsonProperty("diverse_with_y_flows") + private Set diverseWithYFlows; private String affinityWith; private boolean pinned; private boolean allocateProtectedPath; diff --git a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlow.java b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlow.java index 15e841462b9..87b708b3e87 100644 --- a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlow.java +++ b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlow.java @@ -27,6 +27,7 @@ import lombok.Data; import java.util.List; +import java.util.Set; @Data @Builder @@ -57,6 +58,10 @@ public class YFlow { @JsonProperty("protected_path_y_point") SwitchId protectedPathYPoint; + Set diverseWithFlows; + @JsonProperty("diverse_with_y_flows") + Set diverseWithYFlows; + List subFlows; String timeCreate; diff --git a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlowCreatePayload.java b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlowCreatePayload.java index b4fb7d9c336..65a6be3b5e3 100644 --- a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlowCreatePayload.java +++ b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlowCreatePayload.java @@ -49,6 +49,7 @@ public class YFlowCreatePayload { boolean strictBandwidth; String description; boolean allocateProtectedPath; + String diverseFlowId; List subFlows; } diff --git a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlowPatchPayload.java b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlowPatchPayload.java index 96c8ee619d6..4ad0263d2bc 100644 --- a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlowPatchPayload.java +++ b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlowPatchPayload.java @@ -46,6 +46,7 @@ public class YFlowPatchPayload { Boolean strictBandwidth; String description; Boolean allocateProtectedPath; + String diverseFlowId; List subFlows; } diff --git a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlowUpdatePayload.java b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlowUpdatePayload.java index 1d227feb075..caac4754a8e 100644 --- a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlowUpdatePayload.java +++ b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlowUpdatePayload.java @@ -49,6 +49,7 @@ public class YFlowUpdatePayload { boolean strictBandwidth; String description; boolean allocateProtectedPath; + String diverseFlowId; List subFlows; } From 11efd2e4b571ee3624561048d0c1595e42cab82b Mon Sep 17 00:00:00 2001 From: Dmitriy Bogun Date: Tue, 18 Jan 2022 18:13:35 +0200 Subject: [PATCH 03/10] Fix switch connections transaction retry policy Removing default retries count limit, because this retry policy aims to the maximum number of attempts. The only required limit here is the duration limit. --- .../openkilda/wfm/topology/network/controller/sw/SwitchFsm.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src-java/network-topology/network-storm-topology/src/main/java/org/openkilda/wfm/topology/network/controller/sw/SwitchFsm.java b/src-java/network-topology/network-storm-topology/src/main/java/org/openkilda/wfm/topology/network/controller/sw/SwitchFsm.java index 64d2e608075..c45b2ba5de5 100644 --- a/src-java/network-topology/network-storm-topology/src/main/java/org/openkilda/wfm/topology/network/controller/sw/SwitchFsm.java +++ b/src-java/network-topology/network-storm-topology/src/main/java/org/openkilda/wfm/topology/network/controller/sw/SwitchFsm.java @@ -521,6 +521,7 @@ private void persistSwitchProperties(Switch sw) { private void persistSwitchConnections(SwitchAvailabilityData availabilityData) { RetryPolicy retryPolicy = new RetryPolicy<>() .handle(RecoverablePersistenceException.class, ConstraintViolationException.class) + .withMaxRetries(-1) // removing default(==2) retries limit .withMaxDuration(Duration.ofSeconds(options.getDbRepeatMaxDurationSeconds())) .withDelay(Duration.ofMillis(20)) .withJitter(Duration.ofMillis(8)) From 91f52f80531b44f6383779737d4e2fe9e8c1dbc2 Mon Sep 17 00:00:00 2001 From: Andrii Dovhan Date: Mon, 24 Jan 2022 13:57:29 +0200 Subject: [PATCH 04/10] [test] use yFlowPing in func-tests --- .../spec/flows/yflows/YFlowCreateSpec.groovy | 16 ++++++++++++++-- .../spec/flows/yflows/YFlowRerouteSpec.groovy | 2 +- .../spec/flows/yflows/YFlowValidationSpec.groovy | 2 +- .../service/northbound/NorthboundServiceV2.java | 4 ++++ .../northbound/NorthboundServiceV2Impl.java | 9 +++++++++ 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowCreateSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowCreateSpec.groovy index 00b5cd52aac..694a649c09e 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowCreateSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowCreateSpec.groovy @@ -23,6 +23,7 @@ import org.openkilda.messaging.payload.flow.FlowState import org.openkilda.model.SwitchFeature import org.openkilda.northbound.dto.v2.switches.CreateLagPortDto import org.openkilda.northbound.dto.v2.yflows.YFlowCreatePayload +import org.openkilda.northbound.dto.v2.yflows.YFlowPingPayload import org.openkilda.testing.model.topology.TopologyDefinition.Switch import org.openkilda.testing.service.traffexam.TraffExamService import org.openkilda.testing.service.traffexam.model.Exam @@ -76,14 +77,25 @@ class YFlowCreateSpec extends HealthCheckSpecification { and: "User is able to view y-flow paths" def paths = northboundV2.getYFlowPaths(yFlow.YFlowId) -// and: "Y-flow passes flow validation" -// northbound.validateFlow(yFlow.YFlowId) + and: "Y-flow passes flow validation" + with(northboundV2.validateYFlow(yFlow.YFlowId)) { + it.asExpected + it.subFlowValidationResults.each { assert it.asExpected } + } and: "Both sub-flows pass flow validation" yFlow.subFlows.each { assert northbound.validateFlow(it.flowId).every { it.asExpected } } + and: "YFlow is pingable" + def response = northboundV2.pingYFlow(yFlow.YFlowId, new YFlowPingPayload(2000)) + !response.error + response.subFlows.each { + assert it.forward.pingSuccess + assert it.reverse.pingSuccess + } + and: "All involved switches pass switch validation" def involvedSwitches = pathHelper.getInvolvedYSwitches(paths) // involvedSwitches.each { sw -> diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowRerouteSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowRerouteSpec.groovy index 1c8e5db00c5..7398655e8f2 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowRerouteSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowRerouteSpec.groovy @@ -79,7 +79,7 @@ class YFlowRerouteSpec extends HealthCheckSpecification { } and: "Y-flow passes flow validation" - northboundV2.validateYFlow(yFlow.YFlowId) + northboundV2.validateYFlow(yFlow.YFlowId).asExpected and: "Both sub-flows pass flow validation" yFlow.subFlows.each { 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 7d967c685f1..fd6ace0e99c 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 @@ -63,7 +63,7 @@ class YFlowValidationSpec extends HealthCheckSpecification { northbound.synchronizeSwitch(swIdToManipulate, false) then: "Y-Flow/subFlow passes flow validation" - northboundV2.validateYFlow(yFlow.YFlowId).each { direction -> assert direction.asExpected } + northboundV2.validateYFlow(yFlow.YFlowId).asExpected yFlow.subFlows.each { northbound.validateFlow(it.flowId).each { direction -> assert direction.asExpected } } diff --git a/src-java/testing/test-library/src/main/java/org/openkilda/testing/service/northbound/NorthboundServiceV2.java b/src-java/testing/test-library/src/main/java/org/openkilda/testing/service/northbound/NorthboundServiceV2.java index fb887646751..2755ffc80d1 100644 --- a/src-java/testing/test-library/src/main/java/org/openkilda/testing/service/northbound/NorthboundServiceV2.java +++ b/src-java/testing/test-library/src/main/java/org/openkilda/testing/service/northbound/NorthboundServiceV2.java @@ -43,6 +43,8 @@ import org.openkilda.northbound.dto.v2.yflows.YFlowCreatePayload; import org.openkilda.northbound.dto.v2.yflows.YFlowPatchPayload; import org.openkilda.northbound.dto.v2.yflows.YFlowPaths; +import org.openkilda.northbound.dto.v2.yflows.YFlowPingPayload; +import org.openkilda.northbound.dto.v2.yflows.YFlowPingResult; import org.openkilda.northbound.dto.v2.yflows.YFlowRerouteResult; import org.openkilda.northbound.dto.v2.yflows.YFlowSyncResult; import org.openkilda.northbound.dto.v2.yflows.YFlowUpdatePayload; @@ -158,4 +160,6 @@ BfdPropertiesPayload setLinkBfd(SwitchId srcSwId, Integer srcPort, SwitchId dstS YFlowValidationResult validateYFlow(String yFlowId); YFlowSyncResult synchronizeYFlow(String yFlowId); + + YFlowPingResult pingYFlow(String yFlowId, YFlowPingPayload payload); } diff --git a/src-java/testing/test-library/src/main/java/org/openkilda/testing/service/northbound/NorthboundServiceV2Impl.java b/src-java/testing/test-library/src/main/java/org/openkilda/testing/service/northbound/NorthboundServiceV2Impl.java index e883d4acc45..880cb7e1e91 100644 --- a/src-java/testing/test-library/src/main/java/org/openkilda/testing/service/northbound/NorthboundServiceV2Impl.java +++ b/src-java/testing/test-library/src/main/java/org/openkilda/testing/service/northbound/NorthboundServiceV2Impl.java @@ -46,6 +46,8 @@ import org.openkilda.northbound.dto.v2.yflows.YFlowDump; import org.openkilda.northbound.dto.v2.yflows.YFlowPatchPayload; import org.openkilda.northbound.dto.v2.yflows.YFlowPaths; +import org.openkilda.northbound.dto.v2.yflows.YFlowPingPayload; +import org.openkilda.northbound.dto.v2.yflows.YFlowPingResult; import org.openkilda.northbound.dto.v2.yflows.YFlowRerouteResult; import org.openkilda.northbound.dto.v2.yflows.YFlowSyncResult; import org.openkilda.northbound.dto.v2.yflows.YFlowUpdatePayload; @@ -446,6 +448,13 @@ public YFlowSyncResult synchronizeYFlow(String yFlowId) { new HttpEntity<>(buildHeadersWithCorrelationId()), YFlowSyncResult.class, yFlowId).getBody(); } + @Override + public YFlowPingResult pingYFlow(String yFlowId, YFlowPingPayload payload) { + HttpEntity httpEntity = new HttpEntity<>(payload, buildHeadersWithCorrelationId()); + return restTemplate.exchange("/api/v2/y-flows/{y_flow_id}/ping", HttpMethod.POST, httpEntity, + YFlowPingResult.class, yFlowId).getBody(); + } + private HttpHeaders buildHeadersWithCorrelationId() { HttpHeaders headers = new HttpHeaders(); headers.set(Utils.CORRELATION_ID, "fn-tests-" + UUID.randomUUID().toString()); From 27a65beb39c7e4ecfd8a2df1e515827cd053751c Mon Sep 17 00:00:00 2001 From: Sergii Iakovenko Date: Thu, 20 Jan 2022 08:55:19 +0200 Subject: [PATCH 05/10] Fix Y-flow API - add allocateProtectedPath to returned entities --- .../java/org/openkilda/messaging/command/yflow/YFlowDto.java | 1 + .../main/java/org/openkilda/northbound/dto/v2/yflows/YFlow.java | 1 + 2 files changed, 2 insertions(+) diff --git a/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowDto.java b/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowDto.java index 44658fd516f..a118cac8874 100644 --- a/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowDto.java +++ b/src-java/flowhs-topology/flowhs-messaging/src/main/java/org/openkilda/messaging/command/yflow/YFlowDto.java @@ -48,6 +48,7 @@ public class YFlowDto implements Serializable { Integer priority; boolean strictBandwidth; String description; + boolean allocateProtectedPath; SwitchId yPoint; SwitchId protectedPathYPoint; List subFlows; diff --git a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlow.java b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlow.java index 15e841462b9..56ba73d51e7 100644 --- a/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlow.java +++ b/src-java/northbound-service/northbound-api/src/main/java/org/openkilda/northbound/dto/v2/yflows/YFlow.java @@ -51,6 +51,7 @@ public class YFlow { Integer priority; boolean strictBandwidth; String description; + boolean allocateProtectedPath; @JsonProperty("y_point") SwitchId yPoint; From a7c14aa1f4896a128cce3d5c70f81c00419d73e1 Mon Sep 17 00:00:00 2001 From: Sergey Nikitin Date: Mon, 24 Jan 2022 03:30:39 +0400 Subject: [PATCH 06/10] Y Flow Ping: Periodic pings --- .../repositories/YFlowRepository.java | 2 + .../repositories/FermaYFlowRepository.java | 17 ++ .../FermaYFlowRepositoryTest.java | 185 ++++++++++++++++++ .../wfm/topology/ping/bolt/FlowFetcher.java | 7 +- .../wfm/topology/ping/bolt/StatsProducer.java | 3 + 5 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaYFlowRepositoryTest.java diff --git a/src-java/kilda-persistence-api/src/main/java/org/openkilda/persistence/repositories/YFlowRepository.java b/src-java/kilda-persistence-api/src/main/java/org/openkilda/persistence/repositories/YFlowRepository.java index 4988a92f3da..9271fe11511 100644 --- a/src-java/kilda-persistence-api/src/main/java/org/openkilda/persistence/repositories/YFlowRepository.java +++ b/src-java/kilda-persistence-api/src/main/java/org/openkilda/persistence/repositories/YFlowRepository.java @@ -32,5 +32,7 @@ public interface YFlowRepository extends Repository { boolean isSubFlow(String flowId); + Optional findYFlowId(String subFlowId); + Optional remove(String yFlowId); } diff --git a/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/repositories/FermaYFlowRepository.java b/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/repositories/FermaYFlowRepository.java index bee74f7245f..8a7ee0218d9 100644 --- a/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/repositories/FermaYFlowRepository.java +++ b/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/repositories/FermaYFlowRepository.java @@ -81,6 +81,23 @@ public boolean isSubFlow(String flowId) { } } + @Override + public Optional findYFlowId(String subFlowId) { + try (GraphTraversal traversal = framedGraph().traverse(g -> g.E() + .hasLabel(YSubFlowFrame.FRAME_LABEL) + .has(YSubFlowFrame.SUBFLOW_ID_PROPERTY, subFlowId) + .values(YSubFlowFrame.YFLOW_ID_PROPERTY)) + .getRawTraversal()) { + if (traversal.hasNext()) { + return Optional.of((String) traversal.next()); + } else { + return Optional.empty(); + } + } catch (Exception e) { + throw new PersistenceException("Failed to traverse", e); + } + } + @Override public Optional remove(String flowId) { TransactionManager transactionManager = getTransactionManager(); diff --git a/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaYFlowRepositoryTest.java b/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaYFlowRepositoryTest.java new file mode 100644 index 00000000000..390c77411d8 --- /dev/null +++ b/src-java/kilda-persistence-tinkerpop/src/test/java/org/openkilda/persistence/ferma/repositories/FermaYFlowRepositoryTest.java @@ -0,0 +1,185 @@ +/* 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.persistence.ferma.repositories; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import org.openkilda.model.Flow; +import org.openkilda.model.FlowEncapsulationType; +import org.openkilda.model.FlowPath; +import org.openkilda.model.FlowPathDirection; +import org.openkilda.model.FlowPathStatus; +import org.openkilda.model.FlowStatus; +import org.openkilda.model.MeterId; +import org.openkilda.model.PathId; +import org.openkilda.model.PathSegment; +import org.openkilda.model.Switch; +import org.openkilda.model.SwitchId; +import org.openkilda.model.YFlow; +import org.openkilda.model.YFlow.SharedEndpoint; +import org.openkilda.model.YSubFlow; +import org.openkilda.model.cookie.FlowSegmentCookie; +import org.openkilda.persistence.inmemory.InMemoryGraphBasedTest; +import org.openkilda.persistence.repositories.FlowPathRepository; +import org.openkilda.persistence.repositories.FlowRepository; +import org.openkilda.persistence.repositories.SwitchRepository; +import org.openkilda.persistence.repositories.YFlowRepository; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Collections; + +public class FermaYFlowRepositoryTest extends InMemoryGraphBasedTest { + static final String Y_FLOW_ID_1 = "y_flow_1"; + static final String FLOW_ID_1 = "test_flow_1"; + static final String FLOW_ID_2 = "test_flow_2"; + static final String FLOW_ID_3 = "test_flow_3"; + static final SwitchId SWITCH_ID_1 = new SwitchId(1); + static final SwitchId SWITCH_ID_2 = new SwitchId(2); + static final SwitchId SWITCH_ID_3 = new SwitchId(3); + static final int PORT_1 = 1; + static final int PORT_2 = 2; + static final int PORT_3 = 3; + static final int PORT_4 = 4; + public static final int VLAN_1 = 3; + public static final int VLAN_2 = 4; + public static final int VLAN_3 = 5; + + FlowRepository flowRepository; + YFlowRepository yFlowRepository; + FlowPathRepository flowPathRepository; + SwitchRepository switchRepository; + + Switch switch1; + Switch switch2; + Switch switch3; + + @Before + public void setUp() { + flowRepository = repositoryFactory.createFlowRepository(); + yFlowRepository = repositoryFactory.createYFlowRepository(); + flowPathRepository = repositoryFactory.createFlowPathRepository(); + switchRepository = repositoryFactory.createSwitchRepository(); + switch1 = createTestSwitch(SWITCH_ID_1.getId()); + switch2 = createTestSwitch(SWITCH_ID_2.getId()); + switch3 = createTestSwitch(SWITCH_ID_3.getId()); + } + + @Test + public void shouldCreateFlow() { + createYFlow(Y_FLOW_ID_1, FLOW_ID_1, FLOW_ID_2); + createTestFlow(FLOW_ID_3, switch1, PORT_3, VLAN_2, switch2, PORT_2, VLAN_1); + + assertEquals(Y_FLOW_ID_1, yFlowRepository.findYFlowId(FLOW_ID_1).get()); + assertEquals(Y_FLOW_ID_1, yFlowRepository.findYFlowId(FLOW_ID_2).get()); + assertFalse(yFlowRepository.findYFlowId(FLOW_ID_3).isPresent()); + } + + private YFlow createYFlow(String yFlowId, String flowId1, String flowId2) { + YFlow yFlow = YFlow.builder() + .yFlowId(yFlowId) + .encapsulationType(FlowEncapsulationType.TRANSIT_VLAN) + .sharedEndpoint(new SharedEndpoint(SWITCH_ID_1, PORT_1)) + .build(); + Flow flow1 = createTestFlow(flowId1, switch1, PORT_1, VLAN_1, switch2, PORT_3, VLAN_3); + Flow flow2 = createTestFlow(flowId2, switch1, PORT_1, VLAN_2, switch3, PORT_4, VLAN_3); + + YSubFlow subFlow1 = YSubFlow.builder() + .yFlow(yFlow) + .flow(flow1) + .endpointSwitchId(SWITCH_ID_2) + .endpointPort(PORT_3) + .endpointVlan(VLAN_3) + .sharedEndpointVlan(VLAN_1) + .build(); + + YSubFlow subFlow2 = YSubFlow.builder() + .yFlow(yFlow) + .flow(flow2) + .endpointSwitchId(SWITCH_ID_3) + .endpointPort(PORT_4) + .endpointVlan(VLAN_3) + .sharedEndpointVlan(VLAN_2) + .build(); + + yFlow.addSubFlow(subFlow1); + yFlow.addSubFlow(subFlow2); + yFlowRepository.add(yFlow); + return yFlow; + } + + private Flow createTestFlow( + String flowId, Switch srcSwitch, int srcPort, int srcVlan, Switch destSwitch, int destPort, int destVlan) { + Flow flow = Flow.builder() + .flowId(flowId) + .srcSwitch(srcSwitch) + .srcPort(srcPort) + .srcVlan(srcVlan) + .destSwitch(destSwitch) + .destPort(destPort) + .destVlan(destVlan) + .encapsulationType(FlowEncapsulationType.TRANSIT_VLAN) + .status(FlowStatus.UP) + .build(); + flowRepository.add(flow); + + FlowPath forwardFlowPath = FlowPath.builder() + .pathId(new PathId(flowId + "_forward_path")) + .cookie(new FlowSegmentCookie(FlowPathDirection.FORWARD, 1L)) + .meterId(new MeterId(1)) + .srcSwitch(srcSwitch) + .destSwitch(destSwitch) + .status(FlowPathStatus.ACTIVE) + .build(); + + PathSegment forwardSegment = PathSegment.builder() + .pathId(forwardFlowPath.getPathId()) + .srcSwitch(srcSwitch) + .srcPort(srcPort) + .destSwitch(destSwitch) + .destPort(destPort) + .build(); + forwardFlowPath.setSegments(Collections.singletonList(forwardSegment)); + + flowPathRepository.add(forwardFlowPath); + flow.setForwardPath(forwardFlowPath); + + FlowPath reverseFlowPath = FlowPath.builder() + .pathId(new PathId(flowId + "_reverse_path")) + .cookie(new FlowSegmentCookie(FlowPathDirection.REVERSE, 1L)) + .meterId(new MeterId(2)) + .srcSwitch(destSwitch) + .destSwitch(srcSwitch) + .status(FlowPathStatus.ACTIVE) + .build(); + + PathSegment reverseSegment = PathSegment.builder() + .pathId(reverseFlowPath.getPathId()) + .srcSwitch(destSwitch) + .srcPort(destPort) + .destSwitch(srcSwitch) + .destPort(srcPort) + .build(); + reverseFlowPath.setSegments(Collections.singletonList(reverseSegment)); + + flowPathRepository.add(reverseFlowPath); + flow.setReversePath(reverseFlowPath); + + return flow; + } +} diff --git a/src-java/ping-topology/ping-storm-topology/src/main/java/org/openkilda/wfm/topology/ping/bolt/FlowFetcher.java b/src-java/ping-topology/ping-storm-topology/src/main/java/org/openkilda/wfm/topology/ping/bolt/FlowFetcher.java index 0c20e574959..ee04ec985b6 100644 --- a/src-java/ping-topology/ping-storm-topology/src/main/java/org/openkilda/wfm/topology/ping/bolt/FlowFetcher.java +++ b/src-java/ping-topology/ping-storm-topology/src/main/java/org/openkilda/wfm/topology/ping/bolt/FlowFetcher.java @@ -149,8 +149,10 @@ private void handlePeriodicRequest(Tuple input) throws PipelineException { final CommandContext commandContext = pullContext(input); for (FlowWithTransitEncapsulation flow : flowsSet) { PingContext pingContext = PingContext.builder() + .group(new GroupId(DIRECTION_COUNT_PER_FLOW)) .kind(Kinds.PERIODIC) .flow(flow.getFlow()) + .yFlowId(flow.yFlowId) .transitEncapsulation(flow.getTransitEncapsulation()) .build(); emit(input, pingContext, commandContext); @@ -244,8 +246,10 @@ private void handleOnDemandYFlowRequest(Tuple input) throws PipelineException { private Optional getFlowWithTransitEncapsulation(Flow flow) { if (!flow.isOneSwitchFlow()) { + Optional yFlowId = yFlowRepository.findYFlowId(flow.getFlowId()); return getTransitEncapsulation(flow) - .map(transitEncapsulation -> new FlowWithTransitEncapsulation(flow, transitEncapsulation)); + .map(transitEncapsulation -> new FlowWithTransitEncapsulation( + flow, yFlowId.orElse(null), transitEncapsulation)); } return Optional.empty(); } @@ -325,6 +329,7 @@ public void init() { @EqualsAndHashCode(exclude = {"transitEncapsulation"}) private static class FlowWithTransitEncapsulation { Flow flow; + String yFlowId; FlowTransitEncapsulation transitEncapsulation; } } diff --git a/src-java/ping-topology/ping-storm-topology/src/main/java/org/openkilda/wfm/topology/ping/bolt/StatsProducer.java b/src-java/ping-topology/ping-storm-topology/src/main/java/org/openkilda/wfm/topology/ping/bolt/StatsProducer.java index 62cafaf4f90..e3ac00b9b94 100644 --- a/src-java/ping-topology/ping-storm-topology/src/main/java/org/openkilda/wfm/topology/ping/bolt/StatsProducer.java +++ b/src-java/ping-topology/ping-storm-topology/src/main/java/org/openkilda/wfm/topology/ping/bolt/StatsProducer.java @@ -53,6 +53,9 @@ protected void handleInput(Tuple input) throws Exception { HashMap tags = new HashMap<>(); tags.put("flowid", pingContext.getFlowId()); + if (pingContext.getYFlowId() != null) { + tags.put("y_flow_id", pingContext.getYFlowId()); + } produceMetersStats(input, tags, pingContext); } From b4cfd4a7cfada5d1c2e6701e1362d333d3a966b7 Mon Sep 17 00:00:00 2001 From: Sergii Iakovenko Date: Wed, 19 Jan 2022 19:51:22 +0200 Subject: [PATCH 07/10] Fix Y-flow update - handle allocateProtectedPath changes, recalculate y-flow status based on sub-flow statuses, fix RuleManager error reporting --- .../command/rulemanager/OfBatchExecutor.java | 21 ++- .../command/rulemanager/OfBatchHolder.java | 29 +++- .../actions/AllocateYFlowResourcesAction.java | 80 +++++++--- .../CompleteYFlowInstallationAction.java | 6 +- .../OnReceivedInstallResponseAction.java | 2 +- .../OnReceivedRemoveResponseAction.java | 2 +- .../OnReceivedRemoveResponseAction.java | 2 +- .../actions/CompleteYFlowReroutingAction.java | 20 +-- .../OnReceivedInstallResponseAction.java | 2 +- .../OnReceivedRemoveResponseAction.java | 2 +- .../fsm/yflow/update/YFlowUpdateFsm.java | 54 +++---- .../actions/CompleteYFlowUpdatingAction.java | 12 +- .../OnReceivedInstallResponseAction.java | 2 +- .../OnReceivedRemoveResponseAction.java | 2 +- .../ReallocateYFlowResourcesAction.java | 145 ------------------ .../update/actions/RevertYFlowAction.java | 2 +- .../service/yflow/YFlowReadService.java | 5 +- .../main/java/org/openkilda/model/YFlow.java | 21 +++ .../rulemanager/MeterSpeakerData.java | 2 +- 19 files changed, 159 insertions(+), 252 deletions(-) delete mode 100644 src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/ReallocateYFlowResourcesAction.java diff --git a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/rulemanager/OfBatchExecutor.java b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/rulemanager/OfBatchExecutor.java index 8f998bf4563..f6218406536 100644 --- a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/rulemanager/OfBatchExecutor.java +++ b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/rulemanager/OfBatchExecutor.java @@ -15,6 +15,8 @@ package org.openkilda.floodlight.command.rulemanager; +import static java.lang.String.format; + import org.openkilda.floodlight.KafkaChannel; import org.openkilda.floodlight.converter.rulemanager.OfFlowModConverter; import org.openkilda.floodlight.converter.rulemanager.OfGroupConverter; @@ -29,6 +31,7 @@ import org.openkilda.rulemanager.GroupSpeakerData; import org.openkilda.rulemanager.MeterSpeakerData; +import com.google.common.base.Joiner; import lombok.Builder; import lombok.extern.slf4j.Slf4j; import net.floodlightcontroller.core.IOFSwitch; @@ -40,6 +43,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -98,7 +102,10 @@ public void executeBatch() { hasGroups |= batchData.isGroup(); ofMessages.add(batchData.getMessage()); } else { - holder.recordFailedUuid(uuid, "Not all dependencies are satisfied"); + Map blockingDependencies = holder.getBlockingDependencies(uuid); + holder.recordFailedUuid(uuid, "Not all dependencies are satisfied: " + + (blockingDependencies.isEmpty() ? "can't execute" + : Joiner.on(",").withKeyValueSeparator("=").join(blockingDependencies))); } } List>> requests = new ArrayList<>(); @@ -191,7 +198,9 @@ private void verifyFlows() { if (switchFlow.equals(expectedFlow)) { holder.recordSuccessUuid(expectedFlow.getUuid()); } else { - holder.recordFailedUuid(expectedFlow.getUuid(), "Failed to validate flow on a switch"); + holder.recordFailedUuid(expectedFlow.getUuid(), + format("Failed to validate flow on a switch. Expected: %s, actual: %s", expectedFlow, + switchFlow)); } } } @@ -218,7 +227,9 @@ private void verifyMeters() { if (switchMeter.equals(expectedMeter)) { holder.recordSuccessUuid(expectedMeter.getUuid()); } else { - holder.recordFailedUuid(expectedMeter.getUuid(), "Failed to validate meter on a switch"); + holder.recordFailedUuid(expectedMeter.getUuid(), + format("Failed to validate meter on a switch. Expected: %s, actual: %s. " + + "Switch features: %s.", expectedMeter, switchMeter, switchFeatures)); } } } @@ -245,7 +256,9 @@ private void verifyGroups() { if (switchGroup.equals(expectedGroup)) { holder.recordSuccessUuid(expectedGroup.getUuid()); } else { - holder.recordFailedUuid(expectedGroup.getUuid(), "Failed to validate group on a switch"); + holder.recordFailedUuid(expectedGroup.getUuid(), + format("Failed to validate group on a switch. Expected: %s, actual: %s", expectedGroup, + switchGroup)); } } } diff --git a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/rulemanager/OfBatchHolder.java b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/rulemanager/OfBatchHolder.java index e15fde4a2d0..7351f8b27d6 100644 --- a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/rulemanager/OfBatchHolder.java +++ b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/rulemanager/OfBatchHolder.java @@ -82,9 +82,18 @@ public void recordSuccessUuid(UUID failedUuid) { successUuids.add(failedUuid); } + /** + * Record the given uuid as failed with corresponding error message. + */ public void recordFailedUuid(UUID failedUuid, String message) { log.debug("Record failed for {}, error message: {}", failedUuid, message); - failedUuids.put(failedUuid, message); + if (failedUuids.containsKey(failedUuid)) { + // Append error messages. + String previousMessage = failedUuids.get(failedUuid); + failedUuids.put(failedUuid, previousMessage + "; " + message); + } else { + failedUuids.put(failedUuid, message); + } } /** @@ -100,6 +109,24 @@ public boolean canExecute(UUID uuid) { return commandMap.get(uuid) != null; } + /** + * Return a map of uuids (with description message) which block the given uuid. + */ + public Map getBlockingDependencies(UUID uuid) { + Map result = new HashMap<>(); + List nodeInEdges = executionGraph.getNodeDependsOn(uuid); + for (UUID dep : nodeInEdges) { + if (!successUuids.contains(dep)) { + if (failedUuids.containsKey(dep)) { + result.put(dep, failedUuids.get(dep)); + } else { + result.put(dep, "Not executed yet"); + } + } + } + return result; + } + public BatchData getByUUid(UUID uuid) { return commandMap.get(uuid); } diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/actions/AllocateYFlowResourcesAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/actions/AllocateYFlowResourcesAction.java index 7aa4e7801f2..aa0d3048eeb 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/actions/AllocateYFlowResourcesAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/common/actions/AllocateYFlowResourcesAction.java @@ -17,17 +17,19 @@ import static java.lang.String.format; +import org.openkilda.messaging.error.ErrorType; import org.openkilda.model.Flow; import org.openkilda.model.FlowPath; +import org.openkilda.model.FlowStatus; import org.openkilda.model.MeterId; import org.openkilda.model.SwitchId; import org.openkilda.model.YFlow; -import org.openkilda.model.YSubFlow; import org.openkilda.pce.PathComputer; import org.openkilda.persistence.PersistenceManager; import org.openkilda.persistence.exceptions.ConstraintViolationException; import org.openkilda.wfm.share.flow.resources.FlowResourcesManager; import org.openkilda.wfm.share.flow.resources.ResourceAllocationException; +import org.openkilda.wfm.topology.flowhs.exception.FlowProcessingException; import org.openkilda.wfm.topology.flowhs.fsm.common.YFlowProcessingFsm; import org.openkilda.wfm.topology.flowhs.model.yflow.YFlowResources; import org.openkilda.wfm.topology.flowhs.model.yflow.YFlowResources.EndpointResources; @@ -36,6 +38,9 @@ import lombok.extern.slf4j.Slf4j; import net.jodah.failsafe.RetryPolicy; +import java.util.ArrayList; +import java.util.List; + @Slf4j public class AllocateYFlowResourcesAction, S, E, C> extends YFlowProcessingWithHistorySupportAction { @@ -87,13 +92,20 @@ public void perform(S from, S to, E event, C context, T stateMachine) { } if (newResources.getMainPathYPointResources() == null) { - FlowPath[] subFlowsReversePaths = yFlow.getSubFlows().stream() - .map(YSubFlow::getFlow) - .map(Flow::getReversePath) - .toArray(FlowPath[]::new); + List subFlowsReversePaths = new ArrayList<>(); + yFlow.getSubFlows().forEach(subFlow -> { + Flow flow = subFlow.getFlow(); + FlowPath path = flow.getReversePath(); + if (path == null) { + throw new FlowProcessingException(ErrorType.INTERNAL_ERROR, + format("Missing a reverse path for %s sub-flow", flow.getFlowId())); + } else { + subFlowsReversePaths.add(path); + } + }); EndpointResources yPointResources = allocateYPointResources(yFlowId, sharedEndpoint, - yFlow.getMaximumBandwidth(), subFlowsReversePaths); + yFlow.getMaximumBandwidth(), subFlowsReversePaths.toArray(new FlowPath[0])); newResources.setMainPathYPointResources(yPointResources); stateMachine.saveActionToHistory("A new meter was allocated for the y-flow y-point", @@ -102,29 +114,49 @@ public void perform(S from, S to, E event, C context, T stateMachine) { } if (yFlow.isAllocateProtectedPath() && newResources.getProtectedPathYPointResources() == null) { - FlowPath[] subFlowsReversePaths = yFlow.getSubFlows().stream() - .map(YSubFlow::getFlow) - .map(Flow::getProtectedReversePath) - .toArray(FlowPath[]::new); - - EndpointResources yPointResources = allocateYPointResources(yFlowId, sharedEndpoint, - yFlow.getMaximumBandwidth(), subFlowsReversePaths); - newResources.setProtectedPathYPointResources(yPointResources); - - stateMachine.saveActionToHistory("A new meter was allocated for the y-flow protected path y-point", - format("A new meter %s / %s was allocated", yPointResources.getMeterId(), - yPointResources.getEndpoint())); + List subFlowsReversePaths = new ArrayList<>(); + yFlow.getSubFlows().forEach(subFlow -> { + Flow flow = subFlow.getFlow(); + FlowPath path = flow.getProtectedReversePath(); + if (path == null) { + if (flow.getStatus() == FlowStatus.UP) { + throw new FlowProcessingException(ErrorType.INTERNAL_ERROR, + format("Missing a protected path for %s sub-flow", flow.getFlowId())); + } else { + log.warn("Sub-flow {} has no expected protected path and status {}", + flow.getFlowId(), flow.getStatus()); + } + } else { + subFlowsReversePaths.add(path); + } + }); + + if (subFlowsReversePaths.size() > 1) { + EndpointResources yPointResources = allocateYPointResources(yFlowId, sharedEndpoint, + yFlow.getMaximumBandwidth(), subFlowsReversePaths.toArray(new FlowPath[0])); + newResources.setProtectedPathYPointResources(yPointResources); + + stateMachine.saveActionToHistory("A new meter was allocated for the y-flow protected path y-point", + format("A new meter %s / %s was allocated", yPointResources.getMeterId(), + yPointResources.getEndpoint())); + } else { + stateMachine.saveActionToHistory("Skip meter allocation for the y-flow protected path y-point", + "Y-flow protected path y-point can't be found - sub-flow(s) lacks a protected path"); + } } transactionManager.doInTransaction(() -> { - YFlow flow = getYFlow(yFlowId); - flow.setYPoint(newResources.getMainPathYPointResources().getEndpoint()); - flow.setMeterId(newResources.getMainPathYPointResources().getMeterId()); + YFlow yFlowToUpdate = getYFlow(yFlowId); + yFlowToUpdate.setYPoint(newResources.getMainPathYPointResources().getEndpoint()); + yFlowToUpdate.setMeterId(newResources.getMainPathYPointResources().getMeterId()); if (newResources.getProtectedPathYPointResources() != null) { - flow.setProtectedPathYPoint(newResources.getProtectedPathYPointResources().getEndpoint()); - flow.setProtectedPathMeterId(newResources.getProtectedPathYPointResources().getMeterId()); + yFlowToUpdate.setProtectedPathYPoint(newResources.getProtectedPathYPointResources().getEndpoint()); + yFlowToUpdate.setProtectedPathMeterId(newResources.getProtectedPathYPointResources().getMeterId()); + } else { + yFlowToUpdate.setProtectedPathYPoint(null); + yFlowToUpdate.setProtectedPathMeterId(null); } - flow.setSharedEndpointMeterId(newResources.getSharedEndpointResources().getMeterId()); + yFlowToUpdate.setSharedEndpointMeterId(newResources.getSharedEndpointResources().getMeterId()); }); notifyStats(stateMachine, newResources); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/CompleteYFlowInstallationAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/CompleteYFlowInstallationAction.java index 47bba7a6068..e7572bf710c 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/CompleteYFlowInstallationAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/CompleteYFlowInstallationAction.java @@ -46,10 +46,8 @@ protected void perform(State from, State to, Event event, YFlowCreateContext con FlowStatus flowStatus = transactionManager.doInTransaction(() -> { YFlow yFlow = getYFlow(yFlowId); - boolean hasDownSubFlow = yFlow.getSubFlows().stream().anyMatch(subFlow -> !subFlow.getFlow().isActive()); - FlowStatus status = hasDownSubFlow ? FlowStatus.DEGRADED : FlowStatus.UP; - yFlow.setStatus(status); - return status; + yFlow.recalculateStatus(); + return yFlow.getStatus(); }); dashboardLogger.onYFlowStatusUpdate(yFlowId, flowStatus); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/OnReceivedInstallResponseAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/OnReceivedInstallResponseAction.java index 010c3c409c8..423e230fba8 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/OnReceivedInstallResponseAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/OnReceivedInstallResponseAction.java @@ -68,7 +68,7 @@ public void perform(State from, State to, Event event, YFlowCreateContext contex response.getFailedCommandIds().forEach((uuid, message) -> stateMachine.saveErrorToHistory(FAILED_TO_INSTALL_RULE_ACTION, format("Failed to install the rule: commandId %s, ruleId %s, switch %s. " - + "Error %s. Retrying (attempt %d)", + + "Error: %s. Retrying (attempt %d)", commandId, uuid, response.getSwitchId(), message, attempt))); Set failedUuids = response.getFailedCommandIds().keySet(); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/OnReceivedRemoveResponseAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/OnReceivedRemoveResponseAction.java index d9cd515af85..db0a0d40fa3 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/OnReceivedRemoveResponseAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/create/actions/OnReceivedRemoveResponseAction.java @@ -68,7 +68,7 @@ public void perform(State from, State to, Event event, YFlowCreateContext contex response.getFailedCommandIds().forEach((uuid, message) -> stateMachine.saveErrorToHistory(FAILED_TO_REMOVE_RULE_ACTION, format("Failed to remove the rule: commandId %s, ruleId %s, switch %s. " - + "Error %s. Retrying (attempt %d)", + + "Error: %s. Retrying (attempt %d)", commandId, uuid, response.getSwitchId(), message, attempt))); Set failedUuids = response.getFailedCommandIds().keySet(); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/delete/actions/OnReceivedRemoveResponseAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/delete/actions/OnReceivedRemoveResponseAction.java index b0e074635c2..d4c9e7e2cf7 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/delete/actions/OnReceivedRemoveResponseAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/delete/actions/OnReceivedRemoveResponseAction.java @@ -68,7 +68,7 @@ public void perform(State from, State to, Event event, YFlowDeleteContext contex response.getFailedCommandIds().forEach((uuid, message) -> stateMachine.saveErrorToHistory(FAILED_TO_REMOVE_RULE_ACTION, format("Failed to remove the rule: commandId %s, ruleId %s, switch %s. " - + "Error %s. Retrying (attempt %d)", + + "Error: %s. Retrying (attempt %d)", commandId, uuid, response.getSwitchId(), message, attempt))); Set failedUuids = response.getFailedCommandIds().keySet(); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/CompleteYFlowReroutingAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/CompleteYFlowReroutingAction.java index 9f09cc23c7e..df277739109 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/CompleteYFlowReroutingAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/CompleteYFlowReroutingAction.java @@ -19,7 +19,6 @@ import org.openkilda.model.FlowStatus; import org.openkilda.model.YFlow; -import org.openkilda.model.YSubFlow; import org.openkilda.persistence.PersistenceManager; import org.openkilda.wfm.share.logger.FlowOperationsDashboardLogger; import org.openkilda.wfm.topology.flowhs.fsm.common.actions.YFlowProcessingWithHistorySupportAction; @@ -47,22 +46,9 @@ protected void perform(State from, State to, Event event, String yFlowId = stateMachine.getYFlowId(); FlowStatus flowStatus = transactionManager.doInTransaction(() -> { - YFlow flow = getYFlow(yFlowId); - FlowStatus status = FlowStatus.UP; - boolean allSubFlowsIsInactive = true; - for (YSubFlow subFlow : flow.getSubFlows()) { - if (!subFlow.getFlow().isActive()) { - status = FlowStatus.DEGRADED; - } - allSubFlowsIsInactive &= !subFlow.getFlow().isActive(); - } - - if (allSubFlowsIsInactive) { - status = FlowStatus.DOWN; - } - - flow.setStatus(status); - return status; + YFlow yFlow = getYFlow(yFlowId); + yFlow.recalculateStatus(); + return yFlow.getStatus(); }); dashboardLogger.onYFlowStatusUpdate(yFlowId, flowStatus); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/OnReceivedInstallResponseAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/OnReceivedInstallResponseAction.java index c57d52463c2..a195d24465f 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/OnReceivedInstallResponseAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/OnReceivedInstallResponseAction.java @@ -68,7 +68,7 @@ public void perform(State from, State to, Event event, YFlowRerouteContext conte response.getFailedCommandIds().forEach((uuid, message) -> stateMachine.saveErrorToHistory(FAILED_TO_INSTALL_RULE_ACTION, format("Failed to install the rule: commandId %s, ruleId %s, switch %s. " - + "Error %s. Retrying (attempt %d)", + + "Error: %s. Retrying (attempt %d)", commandId, uuid, response.getSwitchId(), message, attempt))); Set failedUuids = response.getFailedCommandIds().keySet(); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/OnReceivedRemoveResponseAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/OnReceivedRemoveResponseAction.java index 4d4a6cdb006..9126d87fbc3 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/OnReceivedRemoveResponseAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/OnReceivedRemoveResponseAction.java @@ -68,7 +68,7 @@ public void perform(State from, State to, Event event, YFlowRerouteContext conte response.getFailedCommandIds().forEach((uuid, message) -> stateMachine.saveErrorToHistory(FAILED_TO_REMOVE_RULE_ACTION, format("Failed to remove the rule: commandId %s, ruleId %s, switch %s. " - + "Error %s. Retrying (attempt %d)", + + "Error: %s. Retrying (attempt %d)", commandId, uuid, response.getSwitchId(), message, attempt))); Set failedUuids = response.getFailedCommandIds().keySet(); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/YFlowUpdateFsm.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/YFlowUpdateFsm.java index 0c77ae44d47..b9261ce160a 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/YFlowUpdateFsm.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/YFlowUpdateFsm.java @@ -49,7 +49,6 @@ import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.actions.OnSubFlowRevertedAction; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.actions.OnSubFlowUpdatedAction; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.actions.OnTimeoutOperationAction; -import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.actions.ReallocateYFlowResourcesAction; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.actions.RemoveMetersAction; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.actions.RemoveOldMeterAction; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.actions.RevertSubFlowsAction; @@ -87,7 +86,6 @@ public final class YFlowUpdateFsm extends YFlowProcessingFsm subFlows = new HashSet<>(); private final Set updatingSubFlows = new HashSet<>(); @@ -302,8 +300,14 @@ public Factory(@NonNull FlowGenericCarrier carrier, @NonNull PersistenceManager builder.transition() .from(State.OLD_YPOINT_METER_REMOVED) - .to(State.DEALLOCATING_OLD_YFLOW_RESOURCES) - .on(Event.NEXT); + .to(State.YFLOW_INSTALLATION_COMPLETED) + .on(Event.NEXT) + .perform(new CompleteYFlowUpdatingAction(persistenceManager, dashboardLogger)); + builder.transitions() + .from(State.YFLOW_INSTALLATION_COMPLETED) + .toAmong(State.DEALLOCATING_OLD_YFLOW_RESOURCES, State.FINISHED_WITH_ERROR, + State.FINISHED_WITH_ERROR) + .onEach(Event.NEXT, Event.ERROR, Event.TIMEOUT); builder.transition() .from(State.DEALLOCATING_OLD_YFLOW_RESOURCES) @@ -313,29 +317,14 @@ public Factory(@NonNull FlowGenericCarrier carrier, @NonNull PersistenceManager builder.transition() .from(State.OLD_YFLOW_RESOURCES_DEALLOCATED) - .to(State.COMPLETE_YFLOW_INSTALLATION) + .to(State.FINISHED) .on(Event.NEXT); builder.transition() .from(State.OLD_YFLOW_RESOURCES_DEALLOCATED) - .to(State.COMPLETE_YFLOW_INSTALLATION) + .to(State.FINISHED_WITH_ERROR) .on(Event.ERROR) .perform(new HandleNotDeallocatedResourcesAction()); - builder.transition() - .from(State.COMPLETE_YFLOW_INSTALLATION) - .to(State.YFLOW_INSTALLATION_COMPLETED) - .on(Event.NEXT) - .perform(new CompleteYFlowUpdatingAction(persistenceManager, dashboardLogger)); - - builder.transition() - .from(State.YFLOW_INSTALLATION_COMPLETED) - .to(State.FINISHED) - .on(Event.NEXT); - builder.transition() - .from(State.YFLOW_INSTALLATION_COMPLETED) - .to(State.FINISHED_WITH_ERROR) - .on(Event.ERROR); - builder.onEntry(State.ALL_PENDING_OPERATIONS_COMPLETED) .perform(new OnTimeoutOperationAction()); builder.internalTransition() @@ -396,7 +385,6 @@ public Factory(@NonNull FlowGenericCarrier carrier, @NonNull PersistenceManager builder.defineState(State.SUB_FLOW_REVERTING_STARTED) .addEntryAction(new RevertSubFlowsAction(flowUpdateService)); - builder.internalTransition() .within(State.REVERTING_SUB_FLOWS) .on(Event.SUB_FLOW_ALLOCATED) @@ -421,12 +409,17 @@ public Factory(@NonNull FlowGenericCarrier carrier, @NonNull PersistenceManager builder.transition() .from(State.ALL_SUB_FLOWS_REVERTED) - .to(State.YFLOW_RESOURCES_REALLOCATED) + .to(State.REVERTING_YFLOW_UPDATE) + .on(Event.NEXT); + + builder.transition() + .from(State.REVERTING_YFLOW_UPDATE) + .to(State.YFLOW_UPDATE_REVERTED) .on(Event.NEXT) - .perform(new ReallocateYFlowResourcesAction(persistenceManager, pathComputer)); + .perform(new RevertYFlowAction(persistenceManager)); builder.transition() - .from(State.YFLOW_RESOURCES_REALLOCATED) + .from(State.YFLOW_UPDATE_REVERTED) .to(State.INSTALLING_OLD_YPOINT_METER) .on(Event.NEXT) .perform(new InstallReallocatedMetersAction(persistenceManager, ruleManager)); @@ -447,17 +440,6 @@ public Factory(@NonNull FlowGenericCarrier carrier, @NonNull PersistenceManager builder.transition() .from(State.OLD_YPOINT_METER_INSTALLED) - .to(State.REVERTING_YFLOW_UPDATE) - .on(Event.NEXT); - - builder.transition() - .from(State.REVERTING_YFLOW_UPDATE) - .to(State.YFLOW_UPDATE_REVERTED) - .on(Event.NEXT) - .perform(new RevertYFlowAction(persistenceManager)); - - builder.transition() - .from(State.YFLOW_UPDATE_REVERTED) .to(State.YFLOW_REVERTED) .on(Event.NEXT) .perform(new CompleteYFlowUpdatingAction(persistenceManager, dashboardLogger)); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/CompleteYFlowUpdatingAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/CompleteYFlowUpdatingAction.java index c3f586ade79..94db6fdb4ce 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/CompleteYFlowUpdatingAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/CompleteYFlowUpdatingAction.java @@ -46,16 +46,8 @@ protected void perform(State from, State to, Event event, YFlowUpdateContext con FlowStatus flowStatus = transactionManager.doInTransaction(() -> { YFlow yFlow = getYFlow(yFlowId); - boolean allSubFlowsDown = yFlow.getSubFlows().stream() - .noneMatch(subFlow -> subFlow.getFlow().isActive()); - FlowStatus status = FlowStatus.DOWN; - if (!allSubFlowsDown) { - boolean hasDownSubFlow = yFlow.getSubFlows().stream() - .anyMatch(subFlow -> !subFlow.getFlow().isActive()); - status = hasDownSubFlow ? FlowStatus.DEGRADED : FlowStatus.UP; - } - yFlow.setStatus(status); - return status; + yFlow.recalculateStatus(); + return yFlow.getStatus(); }); dashboardLogger.onYFlowStatusUpdate(yFlowId, flowStatus); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnReceivedInstallResponseAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnReceivedInstallResponseAction.java index 75c61fd30de..edb3da1fbca 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnReceivedInstallResponseAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnReceivedInstallResponseAction.java @@ -68,7 +68,7 @@ public void perform(State from, State to, Event event, YFlowUpdateContext contex response.getFailedCommandIds().forEach((uuid, message) -> stateMachine.saveErrorToHistory(FAILED_TO_INSTALL_RULE_ACTION, format("Failed to install the rule: commandId %s, ruleId %s, switch %s. " - + "Error %s. Retrying (attempt %d)", + + "Error: %s. Retrying (attempt %d)", commandId, uuid, response.getSwitchId(), message, attempt))); Set failedUuids = response.getFailedCommandIds().keySet(); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnReceivedRemoveResponseAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnReceivedRemoveResponseAction.java index a569cf909e5..25a57ef78cb 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnReceivedRemoveResponseAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnReceivedRemoveResponseAction.java @@ -68,7 +68,7 @@ public void perform(State from, State to, Event event, YFlowUpdateContext contex response.getFailedCommandIds().forEach((uuid, message) -> stateMachine.saveErrorToHistory(FAILED_TO_REMOVE_RULE_ACTION, format("Failed to remove the rule: commandId %s, ruleId %s, switch %s. " - + "Error %s. Retrying (attempt %d)", + + "Error: %s. Retrying (attempt %d)", commandId, uuid, response.getSwitchId(), message, attempt))); Set failedUuids = response.getFailedCommandIds().keySet(); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/ReallocateYFlowResourcesAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/ReallocateYFlowResourcesAction.java deleted file mode 100644 index 07023c898db..00000000000 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/ReallocateYFlowResourcesAction.java +++ /dev/null @@ -1,145 +0,0 @@ -/* Copyright 2021 Telstra Open Source - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.openkilda.wfm.topology.flowhs.fsm.yflow.update.actions; - -import static java.lang.String.format; - -import org.openkilda.messaging.error.ErrorType; -import org.openkilda.model.Flow; -import org.openkilda.model.FlowPath; -import org.openkilda.model.MeterId; -import org.openkilda.model.SwitchId; -import org.openkilda.model.YFlow; -import org.openkilda.model.YSubFlow; -import org.openkilda.pce.PathComputer; -import org.openkilda.persistence.PersistenceManager; -import org.openkilda.wfm.share.flow.resources.ResourceAllocationException; -import org.openkilda.wfm.topology.flowhs.exception.FlowProcessingException; -import org.openkilda.wfm.topology.flowhs.fsm.common.actions.YFlowProcessingWithHistorySupportAction; -import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateContext; -import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateFsm; -import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateFsm.Event; -import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateFsm.State; -import org.openkilda.wfm.topology.flowhs.model.yflow.YFlowResources; -import org.openkilda.wfm.topology.flowhs.model.yflow.YFlowResources.EndpointResources; - -import lombok.extern.slf4j.Slf4j; - -import java.util.Optional; - -@Slf4j -public class ReallocateYFlowResourcesAction extends - YFlowProcessingWithHistorySupportAction { - private final PathComputer pathComputer; - - public ReallocateYFlowResourcesAction(PersistenceManager persistenceManager, PathComputer pathComputer) { - super(persistenceManager); - this.pathComputer = pathComputer; - } - - @Override - public void perform(State from, State to, Event event, YFlowUpdateContext context, YFlowUpdateFsm stateMachine) { - try { - String yFlowId = stateMachine.getYFlowId(); - YFlowResources reallocatedResources; - // This could be a retry. - if (stateMachine.getReallocatedResources() != null) { - reallocatedResources = stateMachine.getReallocatedResources(); - } else { - reallocatedResources = new YFlowResources(); - stateMachine.setReallocatedResources(reallocatedResources); - } - - YFlow yFlow = getYFlow(yFlowId); - SwitchId sharedSwitchId = yFlow.getSharedEndpoint().getSwitchId(); - YFlowResources oldResources = stateMachine.getOldResources(); - if (reallocatedResources.getMainPathYPointResources() == null) { - FlowPath[] subFlowsForwardPaths = yFlow.getSubFlows().stream() - .map(YSubFlow::getFlow) - .map(Flow::getForwardPath) - .toArray(FlowPath[]::new); - FlowPath[] subFlowsReversePaths = yFlow.getSubFlows().stream() - .map(YSubFlow::getFlow) - .map(Flow::getReversePath) - .toArray(FlowPath[]::new); - - MeterId meterId = Optional.ofNullable(oldResources.getMainPathYPointResources()) - .map(EndpointResources::getMeterId).orElse(null); - EndpointResources endpointResources = allocateYPointResources(yFlowId, sharedSwitchId, - meterId, subFlowsForwardPaths, subFlowsReversePaths); - reallocatedResources.setMainPathYPointResources(endpointResources); - - stateMachine.saveActionToHistory("A new y-point was found for the y-flow", - format("A new y-point %s was found", endpointResources.getEndpoint())); - } - - if (yFlow.isAllocateProtectedPath() && reallocatedResources.getProtectedPathYPointResources() == null) { - FlowPath[] subFlowsForwardPaths = yFlow.getSubFlows().stream() - .map(YSubFlow::getFlow) - .map(Flow::getProtectedForwardPath) - .toArray(FlowPath[]::new); - FlowPath[] subFlowsReversePaths = yFlow.getSubFlows().stream() - .map(YSubFlow::getFlow) - .map(Flow::getProtectedReversePath) - .toArray(FlowPath[]::new); - - MeterId meterId = Optional.ofNullable(oldResources.getProtectedPathYPointResources()) - .map(EndpointResources::getMeterId).orElse(null); - EndpointResources endpointResources = allocateYPointResources(yFlowId, sharedSwitchId, - meterId, subFlowsForwardPaths, subFlowsReversePaths); - reallocatedResources.setProtectedPathYPointResources(endpointResources); - - stateMachine.saveActionToHistory("A new y-point was found for the y-flow", - format("A new y-point %s was found", endpointResources.getEndpoint())); - } - - transactionManager.doInTransaction(() -> { - YFlow flow = getYFlow(yFlowId); - flow.setYPoint(reallocatedResources.getMainPathYPointResources().getEndpoint()); - flow.setMeterId(reallocatedResources.getMainPathYPointResources().getMeterId()); - if (reallocatedResources.getProtectedPathYPointResources() != null) { - flow.setProtectedPathYPoint(reallocatedResources.getProtectedPathYPointResources().getEndpoint()); - flow.setProtectedPathMeterId(reallocatedResources.getProtectedPathYPointResources().getMeterId()); - } - }); - - notifyStats(stateMachine, reallocatedResources); - } catch (ResourceAllocationException ex) { - String errorMessage = format("Failed to allocate y-flow resources. %s", ex.getMessage()); - stateMachine.saveErrorToHistory(errorMessage, ex); - stateMachine.fireError(errorMessage); - } - } - - private EndpointResources allocateYPointResources(String yFlowId, SwitchId sharedEndpoint, MeterId meterId, - FlowPath[] subFlowsForwardPaths, FlowPath[] subFlowsReversePaths) - throws ResourceAllocationException { - SwitchId forwardYPoint = pathComputer.getIntersectionPoint(sharedEndpoint, subFlowsForwardPaths); - SwitchId reverseYPoint = pathComputer.getIntersectionPoint(sharedEndpoint, subFlowsReversePaths); - - if (!forwardYPoint.equals(reverseYPoint)) { - throw new FlowProcessingException(ErrorType.INTERNAL_ERROR, - format("Y-flow %s has different y-points for forward and reverse paths: %s / %s", yFlowId, - forwardYPoint, reverseYPoint)); - } - - return EndpointResources.builder().endpoint(forwardYPoint).meterId(meterId).build(); - } - - private void notifyStats(YFlowUpdateFsm fsm, YFlowResources resources) { - fsm.sendAddOrUpdateStatsNotification(resources); - } -} diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/RevertYFlowAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/RevertYFlowAction.java index 1eba5188795..1977cb5c1c4 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/RevertYFlowAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/RevertYFlowAction.java @@ -41,7 +41,7 @@ public RevertYFlowAction(PersistenceManager persistenceManager) { @Override protected void perform(State from, State to, Event event, YFlowUpdateContext context, YFlowUpdateFsm stateMachine) { YFlowRequest originalFlow = stateMachine.getOriginalFlow(); - YFlowResources resources = stateMachine.getReallocatedResources(); + YFlowResources resources = stateMachine.getOldResources(); FlowStatus flowStatus = transactionManager.doInTransaction(() -> { YFlow yFlow = getYFlow(originalFlow.getYFlowId()); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowReadService.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowReadService.java index b50bdaf2fda..fb12ef76942 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowReadService.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowReadService.java @@ -107,7 +107,7 @@ public YFlowPathsResponse getYFlowPaths(@NonNull String yFlowId) throws FlowNotF for (YSubFlow subFlow : subFlows) { Flow flow = subFlow.getFlow(); mainPaths.add(flow.getForwardPath()); - if (flow.isAllocateProtectedPath()) { + if (flow.isAllocateProtectedPath() && flow.getProtectedForwardPath() != null) { protectedPaths.add(flow.getProtectedForwardPath()); } } @@ -116,7 +116,8 @@ public YFlowPathsResponse getYFlowPaths(@NonNull String yFlowId) throws FlowNotF PathInfoData sharedPath = FlowPathMapper.INSTANCE.map(sharedPathSegments); PathInfoData sharedProtectedPath; - if (protectedPaths.isEmpty()) { + // At least 2 paths required to calculate Y-point. + if (protectedPaths.size() < 2) { sharedProtectedPath = new PathInfoData(); } else { List pathSegments = diff --git a/src-java/kilda-model/src/main/java/org/openkilda/model/YFlow.java b/src-java/kilda-model/src/main/java/org/openkilda/model/YFlow.java index 6ac748b3edc..96f1f9939dd 100644 --- a/src-java/kilda-model/src/main/java/org/openkilda/model/YFlow.java +++ b/src-java/kilda-model/src/main/java/org/openkilda/model/YFlow.java @@ -150,6 +150,27 @@ public int hashCode() { getTimeCreate(), getTimeModify(), getSubFlows()); } + /** + * Recalculate the y-flow status based on sub-flow statuses. + */ + public void recalculateStatus() { + FlowStatus yFlowStatus = null; + for (YSubFlow subFlow : getSubFlows()) { + FlowStatus subFlowStatus = subFlow.getFlow().getStatus(); + if (subFlowStatus == FlowStatus.IN_PROGRESS) { + yFlowStatus = FlowStatus.IN_PROGRESS; + break; + } + if (yFlowStatus == null) { + yFlowStatus = subFlowStatus; + } else if (yFlowStatus == FlowStatus.DOWN && subFlowStatus != FlowStatus.DOWN + || yFlowStatus == FlowStatus.UP && subFlowStatus != FlowStatus.UP) { + yFlowStatus = FlowStatus.DEGRADED; + } + } + setStatus(yFlowStatus); + } + /** * Defines persistable data of the Y-flow. */ diff --git a/src-java/rule-manager/rule-manager-api/src/main/java/org/openkilda/rulemanager/MeterSpeakerData.java b/src-java/rule-manager/rule-manager-api/src/main/java/org/openkilda/rulemanager/MeterSpeakerData.java index 0b86d4e7fbd..fd200f717eb 100644 --- a/src-java/rule-manager/rule-manager-api/src/main/java/org/openkilda/rulemanager/MeterSpeakerData.java +++ b/src-java/rule-manager/rule-manager-api/src/main/java/org/openkilda/rulemanager/MeterSpeakerData.java @@ -86,7 +86,7 @@ public boolean equals(Object o) { && isEqualOrWithinDeviation(burst, that.burst, INACCURATE_BURST_ALLOWED_DEVIATION, INACCURATE_BURST_ALLOWED_RELATIVE_DEVIATION); } else { - return rate == that.rate && burst == that.burst; + return rate == that.rate && inaccurateEquals(burst, that.burst, INACCURATE_BURST_ALLOWED_DEVIATION); } } From 615355b0886a828f03873b57498eceb5be265444 Mon Sep 17 00:00:00 2001 From: Dmitriy Bogun Date: Tue, 11 Jan 2022 14:07:55 +0200 Subject: [PATCH 08/10] Reimplement LAG logical port number allocation Use "pool" approach to allocate logical port number for LAG logical ports. Same as we are doing for meters, transit vlans and other resources. --- .../topology.properties.tmpl | 3 + confd/vars/main.yaml | 1 + docs/design/LAG-for-ports/README.md | 8 +- .../NoPoolResourcesAvailableException.java} | 19 +- .../wfm/share/utils/PoolEntityAdapter.java | 28 ++ .../wfm/share/utils/PoolManager.java | 124 ++++++++ .../resources/topology.properties.example | 5 +- .../utils/InMemorySetPoolEntityAdapter.java | 55 ++++ .../wfm/share/utils/PoolManagerTest.java | 140 +++++++++ .../share/utils/PredictablePoolManager.java | 33 +++ .../org/openkilda/model/LagLogicalPort.java | 5 - .../LagLogicalPortRepository.java | 2 + .../FermaLagLogicalPortRepository.java | 46 +++ .../SwitchManagerTopologyConfig.java | 12 + .../switchmanager/bolt/SwitchManagerHub.java | 18 +- .../switchmanager/fsm/CreateLagPortFsm.java | 36 ++- .../switchmanager/fsm/DeleteLagPortFsm.java | 45 +-- .../service/LagPortOperationConfig.java | 66 +++++ .../service/LagPortOperationService.java | 273 +++++++++++++++++- .../service/LagPortPoolEntityAdapter.java | 69 +++++ .../impl/LagPortOperationServiceImpl.java | 215 -------------- .../fsmhandlers/CreateLagPortServiceImpl.java | 19 +- .../fsmhandlers/DeleteLagPortServiceImpl.java | 18 +- 23 files changed, 933 insertions(+), 307 deletions(-) rename src-java/{kilda-model/src/test/java/org/openkilda/model/LagLogicalPortTest.java => base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/error/NoPoolResourcesAvailableException.java} (54%) create mode 100644 src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/utils/PoolEntityAdapter.java create mode 100644 src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/utils/PoolManager.java create mode 100644 src-java/base-topology/base-storm-topology/src/test/java/org/openkilda/wfm/share/utils/InMemorySetPoolEntityAdapter.java create mode 100644 src-java/base-topology/base-storm-topology/src/test/java/org/openkilda/wfm/share/utils/PoolManagerTest.java create mode 100644 src-java/base-topology/base-storm-topology/src/test/java/org/openkilda/wfm/share/utils/PredictablePoolManager.java create mode 100644 src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortOperationConfig.java create mode 100644 src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortPoolEntityAdapter.java delete mode 100644 src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/impl/LagPortOperationServiceImpl.java diff --git a/confd/templates/base-storm-topology/topology.properties.tmpl b/confd/templates/base-storm-topology/topology.properties.tmpl index 405ec9a9ef1..9b3b86b1aee 100644 --- a/confd/templates/base-storm-topology/topology.properties.tmpl +++ b/confd/templates/base-storm-topology/topology.properties.tmpl @@ -42,6 +42,9 @@ port.up.down.throttling.delay.seconds.cool.down = {{ getv "/kilda_port_up_down_t port.antiflap.stats.dumping.interval.seconds = 60 lag.port.offset = {{ getv "/kilda_lag_port_offset" }} +lag.port.max.number = {{ getv "/kilda_lag_port_max_number" }} +lag.port.pool.chunks.count = 10 +lag.port.pool.cache.size = 128 bfd.port.offset = {{ getv "/kilda_bfd_port_offset" }} bfd.port.max.number = {{ getv "/kilda_bfd_port_max_number" }} diff --git a/confd/vars/main.yaml b/confd/vars/main.yaml index ec0d9dd4def..2348695899d 100644 --- a/confd/vars/main.yaml +++ b/confd/vars/main.yaml @@ -116,6 +116,7 @@ kilda_port_up_down_throttling_delay_seconds_warm_up: 3 kilda_port_up_down_throttling_delay_seconds_cool_down: 7 kilda_lag_port_offset: 2000 +kilda_lag_port_max_number: 2999 kilda_bfd_port_offset: 1000 kilda_bfd_port_max_number: 1999 kilda_bfd_interval_ms: 350 diff --git a/docs/design/LAG-for-ports/README.md b/docs/design/LAG-for-ports/README.md index 3550c101191..45dee465a8c 100644 --- a/docs/design/LAG-for-ports/README.md +++ b/docs/design/LAG-for-ports/README.md @@ -37,7 +37,13 @@ Response example: ## Details All logical port related commands are sent to the switches using gRPC speaker. -Open-kilda calculate logical port number using the following rule: 2000 + min of the physical ports number in the LAG. It is not allowed to have one physical port in two LAGs so this rule will provide unique logical port number for any correct port configuration. LAG logical port configuration should be validated before any create operation to avoid inconsistency. +Kilda configuration defines logical port numbers range and amount of chunks in this range. During LAG create operation +random chunk number will be selected and first unassigned number from this chunk will be used as logical port number. +Port number allocation done on per switch basis, so different switches can have LAG logical ports with same numbers. + +It is not allowed to have one physical port in two LAGs so this rule will provide unique logical port number for any +correct port configuration. LAG logical port configuration should be validated before any create operation to avoid +inconsistency. Currently, open-kilda doesn't have any port related information representation in database. We need to save LAG logical port configuration into database to have ability to restore configuration on the switch. Information about LAGs stored as a separate models in order to provide minimal impact on already existing data structures. diff --git a/src-java/kilda-model/src/test/java/org/openkilda/model/LagLogicalPortTest.java b/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/error/NoPoolResourcesAvailableException.java similarity index 54% rename from src-java/kilda-model/src/test/java/org/openkilda/model/LagLogicalPortTest.java rename to src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/error/NoPoolResourcesAvailableException.java index 054df2b7dd5..7c71e2b68b2 100644 --- a/src-java/kilda-model/src/test/java/org/openkilda/model/LagLogicalPortTest.java +++ b/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/error/NoPoolResourcesAvailableException.java @@ -13,21 +13,10 @@ * limitations under the License. */ -package org.openkilda.model; +package org.openkilda.wfm.error; -import com.google.common.collect.Lists; -import org.junit.Assert; -import org.junit.Test; - -public class LagLogicalPortTest { - public static final int OFFSET = 2000; - public static final int POST_1 = 1; - public static final int POST_2 = 2; - public static final int POST_3 = 3; - - @Test - public void generateLogicalPortNumberTest() { - Assert.assertEquals(POST_1 + OFFSET, - LagLogicalPort.generateLogicalPortNumber(Lists.newArrayList(POST_1, POST_2, POST_3), OFFSET)); +public class NoPoolResourcesAvailableException extends Exception { + public NoPoolResourcesAvailableException() { + super("Unable to allocate pool entity - all entity slots are busy"); } } diff --git a/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/utils/PoolEntityAdapter.java b/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/utils/PoolEntityAdapter.java new file mode 100644 index 00000000000..3127f14608f --- /dev/null +++ b/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/utils/PoolEntityAdapter.java @@ -0,0 +1,28 @@ +/* Copyright 2021 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.wfm.share.utils; + +import java.util.Optional; + +public interface PoolEntityAdapter { + long getNumericSequentialId(T entity); + + Optional allocateSpecificId(long entityId); + + Optional allocateFirstInRange(long idMinimum, long idMaximum); + + String formatResourceNotAvailableMessage(); +} diff --git a/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/utils/PoolManager.java b/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/utils/PoolManager.java new file mode 100644 index 00000000000..67ac910cf14 --- /dev/null +++ b/src-java/base-topology/base-storm-topology/src/main/java/org/openkilda/wfm/share/utils/PoolManager.java @@ -0,0 +1,124 @@ +/* Copyright 2021 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.wfm.share.utils; + +import org.openkilda.wfm.share.flow.resources.ResourceNotAvailableException; + +import com.google.common.base.Preconditions; +import lombok.Value; +import lombok.extern.slf4j.Slf4j; + +import java.util.Optional; +import java.util.Random; + +@Slf4j +public class PoolManager { + protected final PoolConfig config; + private final PoolEntityAdapter entityAdapter; + + private final Random random = new Random(); + private long lastId; + + public PoolManager(PoolConfig config, PoolEntityAdapter entityAdapter) { + this.config = config; + this.entityAdapter = entityAdapter; + + this.lastId = config.idMaximum; + } + + /** + * Allocate one entity. + */ + public T allocate() { + Optional entity = allocateNext(); + if (! entity.isPresent()) { + entity = allocateInChunk(); + } + if (! entity.isPresent()) { + entity = allocateInFullScan(); + } + if (! entity.isPresent()) { + throw new ResourceNotAvailableException(entityAdapter.formatResourceNotAvailableMessage()); + } + + T value = entity.get(); + lastId = entityAdapter.getNumericSequentialId(value); + log.trace("Pool entity have been successfully allocated id=={}", lastId); + return value; + } + + private Optional allocateNext() { + long nextId = lastId + 1; + if (config.idMaximum <= nextId) { + return Optional.empty(); + } + log.trace("Attempt to allocate pool entity by id == {}", nextId); + return entityAdapter.allocateSpecificId(nextId); + } + + private Optional allocateInChunk() { + long chunkNumber = selectChunkNumber(config.chunksCount); + long chunkSize = (config.idMaximum - config.idMinimum) / config.chunksCount; + long first = config.idMinimum + chunkNumber * chunkSize; + + long last; + if (chunkNumber + 1 < config.chunksCount) { + last = Math.min(first + chunkSize - 1, config.idMaximum); + } else { + last = config.idMaximum; + } + + log.trace("Attempt to allocate pool entity in chunk from {} till {}", first, last); + return entityAdapter.allocateFirstInRange(first, last); + } + + private Optional allocateInFullScan() { + log.trace( + "Attempt to allocate pool entity using full scan (idMin=={}, idMax=={})", + config.idMinimum, config.idMaximum); + return entityAdapter.allocateFirstInRange(config.idMinimum, config.idMaximum); + } + + protected long selectChunkNumber(long chunksCount) { + if (chunksCount <= 1) { + return 0; + } + return Math.abs(random.nextInt() % chunksCount); + } + + @Value + public static class PoolConfig { + long idMinimum; + long idMaximum; + long chunksCount; + + public PoolConfig(long idMinimum, long idMaximum, long chunksCount) { + long size = idMaximum - idMinimum; + Preconditions.checkArgument( + 0 < size, String.format( + "Resources pool must have at least one entry (%d(idMaximum) - %d(idMinimum) == %d)", + idMaximum, idMinimum, size)); + Preconditions.checkArgument( + 0 < chunksCount && chunksCount <= size, String.format( + "Invalid pool chunks count, the expression must be correct: 0 < %d <= %d", + chunksCount, size)); + + this.idMinimum = idMinimum; + this.idMaximum = idMaximum; + this.chunksCount = chunksCount; + } + } +} diff --git a/src-java/base-topology/base-storm-topology/src/release/resources/topology.properties.example b/src-java/base-topology/base-storm-topology/src/release/resources/topology.properties.example index a834ae6a459..8aa2dd692c6 100644 --- a/src-java/base-topology/base-storm-topology/src/release/resources/topology.properties.example +++ b/src-java/base-topology/base-storm-topology/src/release/resources/topology.properties.example @@ -41,9 +41,12 @@ port.up.down.throttling.delay.seconds.warm.up = 5 port.up.down.throttling.delay.seconds.cool.down = 10 port.antiflap.stats.dumping.interval.seconds = 60 +lag.port.offset = 2000 +lag.port.max.number = 2999 +lag.port.pool.chunks.count = 10 +lag.port.pool.cache.size = 128 bfd.port.offset = 1000 bfd.port.max.number = 1999 -lag.port.offset = 2000 persistence.implementation.default = orientdb persistence.implementation.area.history = orientdb diff --git a/src-java/base-topology/base-storm-topology/src/test/java/org/openkilda/wfm/share/utils/InMemorySetPoolEntityAdapter.java b/src-java/base-topology/base-storm-topology/src/test/java/org/openkilda/wfm/share/utils/InMemorySetPoolEntityAdapter.java new file mode 100644 index 00000000000..4146367e437 --- /dev/null +++ b/src-java/base-topology/base-storm-topology/src/test/java/org/openkilda/wfm/share/utils/InMemorySetPoolEntityAdapter.java @@ -0,0 +1,55 @@ +/* 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.share.utils; + +import java.util.HashSet; +import java.util.Optional; + +public class InMemorySetPoolEntityAdapter implements PoolEntityAdapter { + private final HashSet allocated = new HashSet<>(); + + public void release(long entity) { + allocated.remove(entity); + } + + @Override + public long getNumericSequentialId(Long entity) { + return entity; + } + + @Override + public Optional allocateSpecificId(long entityId) { + if (allocated.add(entityId)) { + return Optional.of(entityId); + } + return Optional.empty(); + } + + @Override + public Optional allocateFirstInRange(long idMinimum, long idMaximum) { + for (long idx = idMinimum; idx <= idMaximum; idx++) { + if (allocated.add(idx)) { + return Optional.of(idx); + } + } + return Optional.empty(); + } + + @Override + public String formatResourceNotAvailableMessage() { + return "no pool entity available (in memory)"; + } +} diff --git a/src-java/base-topology/base-storm-topology/src/test/java/org/openkilda/wfm/share/utils/PoolManagerTest.java b/src-java/base-topology/base-storm-topology/src/test/java/org/openkilda/wfm/share/utils/PoolManagerTest.java new file mode 100644 index 00000000000..f97c79db30c --- /dev/null +++ b/src-java/base-topology/base-storm-topology/src/test/java/org/openkilda/wfm/share/utils/PoolManagerTest.java @@ -0,0 +1,140 @@ +/* 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.share.utils; + +import org.openkilda.wfm.share.flow.resources.ResourceNotAvailableException; +import org.openkilda.wfm.share.utils.PoolManager.PoolConfig; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class PoolManagerTest { + @Test + public void testSequentialAllocation() { + InMemorySetPoolEntityAdapter adapter = new InMemorySetPoolEntityAdapter(); + + PoolConfig config = newConfig(); + PoolManager poolManager = newPoolManager(config, adapter); + + Assert.assertEquals(0L, (long) poolManager.allocate()); + for (int idx = 1; idx <= config.getIdMaximum(); idx++) { + Assert.assertEquals(idx, (long) poolManager.allocate()); + } + + Assert.assertThrows(ResourceNotAvailableException.class, poolManager::allocate); + } + + @Test + public void testChunkSelectionOnCollision() { + PoolConfig config = newConfig(); + InMemorySetPoolEntityAdapter adapter = new InMemorySetPoolEntityAdapter(); + long chunkSize = (config.getIdMaximum() - config.getIdMinimum()) / config.getChunksCount(); + Assert.assertTrue(1 < config.getChunksCount()); + Assert.assertTrue(1 < chunkSize); + + for (long idx = config.getIdMinimum() + 1; idx < chunkSize + 1; idx++) { + adapter.allocateSpecificId(idx); + } + + PoolManager poolManager = newPoolManager(config, adapter); + Assert.assertEquals(config.getIdMinimum(), (long) poolManager.allocate()); + Assert.assertEquals(chunkSize + 1, (long) poolManager.allocate()); + } + + @Test + public void testAllocateReleasedResources() { + PoolConfig config = newConfig(); + InMemorySetPoolEntityAdapter adapter = new InMemorySetPoolEntityAdapter(); + long chunkSize = (config.getIdMaximum() - config.getIdMinimum()) / config.getChunksCount(); + Assert.assertTrue(1 < config.getChunksCount()); + Assert.assertTrue(1 < chunkSize); + + for (long idx = config.getIdMinimum(); idx <= config.getIdMaximum(); idx++) { + adapter.allocateSpecificId(idx); + } + + PoolManager poolManager = newPoolManager(config, adapter); + Assert.assertThrows(ResourceNotAvailableException.class, poolManager::allocate); + + adapter.release(config.getIdMinimum() + 1); + Assert.assertEquals(config.getIdMinimum() + 1, (long) poolManager.allocate()); + + Assert.assertThrows(ResourceNotAvailableException.class, poolManager::allocate); + } + + @Test + public void testAllChunksAccessibilityUsingFirstEntry() { + PoolConfig config = newConfig(); + InMemorySetPoolEntityAdapter adapter = new InMemorySetPoolEntityAdapter(); + long chunkSize = (config.getIdMaximum() - config.getIdMinimum()) / config.getChunksCount(); + for (long idx = 0; idx <= config.getIdMaximum(); idx++) { + if ((idx % chunkSize) == 0) { + continue; + } + adapter.allocateSpecificId(idx); + } + + PoolManager poolManager = newPoolManager(config, adapter); + for (long idx = 0; idx <= config.getChunksCount(); idx++) { + Long entry = poolManager.allocate(); + Assert.assertEquals(idx * chunkSize, (long) entry); + } + Assert.assertThrows(ResourceNotAvailableException.class, poolManager::allocate); + } + + @Test + public void testAllChunksAccessibilityUsingLastEntry() { + PoolConfig config = newConfig(); + InMemorySetPoolEntityAdapter adapter = new InMemorySetPoolEntityAdapter(); + long chunkSize = (config.getIdMaximum() - config.getIdMinimum()) / config.getChunksCount(); + for (long idx = 0; idx < config.getIdMaximum(); idx++) { + if (idx < chunkSize * (config.getChunksCount() - 1) && (idx % chunkSize) == chunkSize - 1) { + continue; + } + adapter.allocateSpecificId(idx); + } + + PoolManager poolManager = newPoolManager(config, adapter); + for (long idx = 0; idx < config.getChunksCount() - 1; idx++) { + Long entry = poolManager.allocate(); + Assert.assertEquals((idx + 1) * chunkSize - 1, (long) entry); + } + Assert.assertEquals(config.getIdMaximum(), (long) poolManager.allocate()); + Assert.assertThrows(ResourceNotAvailableException.class, poolManager::allocate); + } + + @Test + public void testSmallestChunkSize() { + PoolConfig config = new PoolConfig(0, 5, 5); + InMemorySetPoolEntityAdapter adapter = new InMemorySetPoolEntityAdapter(); + PoolManager poolManager = newPoolManager(config, adapter); + for (int idx = 0; idx <= config.getIdMaximum(); idx++) { + Assert.assertEquals(idx, (long) poolManager.allocate()); + } + Assert.assertThrows(ResourceNotAvailableException.class, poolManager::allocate); + } + + private PoolManager.PoolConfig newConfig() { + return new PoolManager.PoolConfig(0, 20, 4); + } + + private PoolManager newPoolManager(PoolManager.PoolConfig config, PoolEntityAdapter adapter) { + return new PredictablePoolManager<>(config, adapter); + } +} diff --git a/src-java/base-topology/base-storm-topology/src/test/java/org/openkilda/wfm/share/utils/PredictablePoolManager.java b/src-java/base-topology/base-storm-topology/src/test/java/org/openkilda/wfm/share/utils/PredictablePoolManager.java new file mode 100644 index 00000000000..db450a5840c --- /dev/null +++ b/src-java/base-topology/base-storm-topology/src/test/java/org/openkilda/wfm/share/utils/PredictablePoolManager.java @@ -0,0 +1,33 @@ +/* Copyright 2022 Telstra Open Source + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openkilda.wfm.share.utils; + +public class PredictablePoolManager extends PoolManager { + private long lastSelectedChunk = -1; + + public PredictablePoolManager(PoolConfig config, PoolEntityAdapter entityAdapter) { + super(config, entityAdapter); + } + + @Override + protected long selectChunkNumber(long chunksCount) { + lastSelectedChunk += 1; + if (chunksCount <= lastSelectedChunk) { + lastSelectedChunk = 0; + } + return lastSelectedChunk; + } +} diff --git a/src-java/kilda-model/src/main/java/org/openkilda/model/LagLogicalPort.java b/src-java/kilda-model/src/main/java/org/openkilda/model/LagLogicalPort.java index 4a4f03fde15..580b459cad1 100644 --- a/src-java/kilda-model/src/main/java/org/openkilda/model/LagLogicalPort.java +++ b/src-java/kilda-model/src/main/java/org/openkilda/model/LagLogicalPort.java @@ -120,11 +120,6 @@ public int hashCode() { return Objects.hash(getSwitchId(), getLogicalPortNumber()); } - public static int generateLogicalPortNumber(Collection physicalPorts, int lagPortOffset) { - return physicalPorts.stream().min(Integer::compareTo).map(port -> port + lagPortOffset).orElseThrow( - () -> new IllegalArgumentException("No physical ports provided")); - } - /** * Defines persistable data of the LagLogicalPort. */ diff --git a/src-java/kilda-persistence-api/src/main/java/org/openkilda/persistence/repositories/LagLogicalPortRepository.java b/src-java/kilda-persistence-api/src/main/java/org/openkilda/persistence/repositories/LagLogicalPortRepository.java index 186db72a9fd..95607fa9b53 100644 --- a/src-java/kilda-persistence-api/src/main/java/org/openkilda/persistence/repositories/LagLogicalPortRepository.java +++ b/src-java/kilda-persistence-api/src/main/java/org/openkilda/persistence/repositories/LagLogicalPortRepository.java @@ -27,4 +27,6 @@ public interface LagLogicalPortRepository extends Repository { Collection findBySwitchId(SwitchId switchId); Optional findBySwitchIdAndPortNumber(SwitchId switchId, int portNumber); + + Optional findUnassignedPortInRange(SwitchId switchId, int portFirst, int portLast); } diff --git a/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortRepository.java b/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortRepository.java index 026aff5b583..ea6438ec826 100644 --- a/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortRepository.java +++ b/src-java/kilda-persistence-tinkerpop/src/main/java/org/openkilda/persistence/ferma/repositories/FermaLagLogicalPortRepository.java @@ -18,6 +18,7 @@ import org.openkilda.model.LagLogicalPort; import org.openkilda.model.LagLogicalPort.LagLogicalPortData; import org.openkilda.model.SwitchId; +import org.openkilda.persistence.exceptions.PersistenceException; import org.openkilda.persistence.ferma.FermaPersistentImplementation; import org.openkilda.persistence.ferma.frames.KildaBaseVertexFrame; import org.openkilda.persistence.ferma.frames.LagLogicalPortFrame; @@ -27,6 +28,9 @@ import org.openkilda.persistence.repositories.PhysicalPortRepository; import lombok.extern.slf4j.Slf4j; +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; import java.util.Collection; import java.util.List; @@ -81,6 +85,48 @@ public Optional findBySwitchIdAndPortNumber(SwitchId switchId, i : Optional.of(lagLogicalPortFrames.get(0)).map(LagLogicalPort::new); } + @Override + public Optional findUnassignedPortInRange(SwitchId switchId, int portFirst, int portLast) { + String switchIdAsStr = SwitchIdConverter.INSTANCE.toGraphProperty(switchId); + + try (GraphTraversal traversal = framedGraph().traverse(g -> g.V() + .hasLabel(LagLogicalPortFrame.FRAME_LABEL) + .has(LagLogicalPortFrame.SWITCH_ID_PROPERTY, switchIdAsStr) + .has(LagLogicalPortFrame.LOGICAL_PORT_NUMBER_PROPERTY, P.gte(portFirst)) + .has(LagLogicalPortFrame.LOGICAL_PORT_NUMBER_PROPERTY, P.lt(portLast)) + .values(LagLogicalPortFrame.LOGICAL_PORT_NUMBER_PROPERTY) + .order().math("_ + 1").as("a") + .where(__.not(__.V().hasLabel(LagLogicalPortFrame.FRAME_LABEL) + .has(LagLogicalPortFrame.SWITCH_ID_PROPERTY, switchIdAsStr) + .values(LagLogicalPortFrame.LOGICAL_PORT_NUMBER_PROPERTY) + .where(P.eq("a")))) + .select("a") + .limit(1)) + .getRawTraversal()) { + if (traversal.hasNext()) { + return traversal.tryNext() + .map(l -> ((Double) l).intValue()); + } + } catch (Exception e) { + throw new PersistenceException("Failed to traverse", e); + } + + // If there is no one record for specific switch exists + try (GraphTraversal traversal = framedGraph().traverse(g -> g.V() + .hasLabel(LagLogicalPortFrame.FRAME_LABEL) + .has(LagLogicalPortFrame.SWITCH_ID_PROPERTY, switchIdAsStr) + .has(LagLogicalPortFrame.LOGICAL_PORT_NUMBER_PROPERTY, portFirst)) + .getRawTraversal()) { + if (! traversal.hasNext()) { + return Optional.of(portFirst); + } + } catch (Exception e) { + throw new PersistenceException("Failed to traverse", e); + } + + return Optional.empty(); + } + @Override protected LagLogicalPortFrame doAdd(LagLogicalPortData data) { LagLogicalPortFrame frame = KildaBaseVertexFrame.addNewFramedVertex(framedGraph(), diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/SwitchManagerTopologyConfig.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/SwitchManagerTopologyConfig.java index 4c86387dec3..89f87469d01 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/SwitchManagerTopologyConfig.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/SwitchManagerTopologyConfig.java @@ -86,6 +86,18 @@ default String getGrpcResponseTopic() { @Default("2000") int getLagPortOffset(); + @Key("lag.port.max.number") + @Default("2999") + int getLagPortMaxNumber(); + + @Key("lag.port.pool.chunks.count") + @Default("10") + int getLagPortPoolChunksCount(); + + @Key("lag.port.pool.cache.size") + @Default("128") + int getLagPortPoolCacheSize(); + @Key("bfd.port.offset") @Default("1000") int getBfdPortOffset(); diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/SwitchManagerHub.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/SwitchManagerHub.java index 258a6f3d93d..4ca867b7d0e 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/SwitchManagerHub.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/bolt/SwitchManagerHub.java @@ -60,6 +60,7 @@ import org.openkilda.wfm.topology.switchmanager.model.ValidationResult; import org.openkilda.wfm.topology.switchmanager.service.CreateLagPortService; import org.openkilda.wfm.topology.switchmanager.service.DeleteLagPortService; +import org.openkilda.wfm.topology.switchmanager.service.LagPortOperationConfig; import org.openkilda.wfm.topology.switchmanager.service.SwitchManagerCarrier; import org.openkilda.wfm.topology.switchmanager.service.SwitchRuleService; import org.openkilda.wfm.topology.switchmanager.service.SwitchSyncService; @@ -72,11 +73,13 @@ import org.openkilda.wfm.topology.switchmanager.service.impl.fsmhandlers.SwitchValidateServiceImpl; 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 SwitchManagerHub extends HubBolt implements SwitchManagerCarrier { public static final String ID = "switch.manager.hub"; @@ -118,12 +121,15 @@ public void init() { new ValidationServiceImpl(persistenceManager, topologyConfig, flowResourcesConfig)); syncService = new SwitchSyncServiceImpl(this, persistenceManager, flowResourcesConfig); switchRuleService = new SwitchRuleServiceImpl(this, persistenceManager.getRepositoryFactory()); - createLagPortService = new CreateLagPortServiceImpl(this, persistenceManager.getRepositoryFactory(), - persistenceManager.getTransactionManager(), topologyConfig.getBfdPortOffset(), - topologyConfig.getBfdPortMaxNumber(), topologyConfig.getLagPortOffset()); - deleteLagPortService = new DeleteLagPortServiceImpl(this, persistenceManager.getRepositoryFactory(), - persistenceManager.getTransactionManager(), topologyConfig.getBfdPortOffset(), - topologyConfig.getBfdPortMaxNumber(), topologyConfig.getLagPortOffset()); + + LagPortOperationConfig config = new LagPortOperationConfig( + persistenceManager.getRepositoryFactory(), persistenceManager.getTransactionManager(), + topologyConfig.getBfdPortOffset(), topologyConfig.getBfdPortMaxNumber(), + topologyConfig.getLagPortOffset(), topologyConfig.getLagPortMaxNumber(), + topologyConfig.getLagPortPoolChunksCount(), topologyConfig.getLagPortPoolCacheSize()); + log.info("LAG logical ports service config: {}", config); + createLagPortService = new CreateLagPortServiceImpl(this, config); + deleteLagPortService = new DeleteLagPortServiceImpl(this, config); } @Override diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/CreateLagPortFsm.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/CreateLagPortFsm.java index 1fb31911585..f4297812747 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/CreateLagPortFsm.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/CreateLagPortFsm.java @@ -31,7 +31,6 @@ import org.openkilda.messaging.model.grpc.LogicalPort; import org.openkilda.messaging.swmanager.request.CreateLagPortRequest; import org.openkilda.messaging.swmanager.response.LagPortResponse; -import org.openkilda.model.Switch; import org.openkilda.model.SwitchId; import org.openkilda.wfm.topology.switchmanager.error.InconsistentDataException; import org.openkilda.wfm.topology.switchmanager.error.InvalidDataException; @@ -53,6 +52,7 @@ import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine; import java.util.ArrayList; +import java.util.HashSet; @Slf4j public class CreateLagPortFsm extends AbstractStateMachine< @@ -117,13 +117,13 @@ public String getKey() { void createLagInDb(CreateLagState from, CreateLagState to, CreateLagEvent event, CreateLagContext context) { log.info("Creating LAG {} on switch {}. Key={}", request, switchId, key); try { - Switch sw = lagPortOperationService.getSwitch(switchId); - String ipAddress = lagPortOperationService.getSwitchIpAddress(sw); - lagPortOperationService.validatePhysicalPorts(switchId, request.getPortNumbers(), sw.getFeatures()); - lagLogicalPortNumber = lagPortOperationService.createLagPort(switchId, request.getPortNumbers()); + HashSet targetPorts = new HashSet<>(request.getPortNumbers()); + lagLogicalPortNumber = lagPortOperationService.createLagPort(switchId, targetPorts); + + String ipAddress = lagPortOperationService.getSwitchIpAddress(switchId); grpcRequest = new CreateLogicalPortRequest(ipAddress, request.getPortNumbers(), lagLogicalPortNumber, LAG); } catch (InvalidDataException | InconsistentDataException | SwitchNotFoundException e) { - log.error(format("Enable to create LAG port %s in DB. Error: %s", request, e.getMessage()), e); + log.error(format("Unable to handle %s. Error: %s", request, e.getMessage()), e); fire(ERROR, CreateLagContext.builder().error(e).build()); } } @@ -141,26 +141,34 @@ void finishedEnter(CreateLagState from, CreateLagState to, CreateLagEvent event, LagPortResponse response = new LagPortResponse( grpcRequest.getLogicalPortNumber(), new ArrayList<>(grpcRequest.getPortNumbers())); InfoMessage message = new InfoMessage(response, System.currentTimeMillis(), key); - - carrier.cancelTimeoutCallback(key); carrier.response(key, message); } protected void finishedWithErrorEnter(CreateLagState from, CreateLagState to, CreateLagEvent event, CreateLagContext context) { - if (lagLogicalPortNumber != null) { - // remove created LAG port - log.info("Removing form DB created LAG port {} on switch {}. Key={}", lagLogicalPortNumber, switchId, key); - lagPortOperationService.removeLagPort(switchId, lagLogicalPortNumber); - } + removePortEntryIgnoreMissing(); SwitchManagerException error = context.getError(); log.error(format("Unable to create LAG %s on switch %s. Key: %s. Error: %s", request, switchId, key, error.getMessage()), error); - carrier.cancelTimeoutCallback(key); carrier.errorResponse(key, error.getError(), "Error during LAG create", error.getMessage()); } + private void removePortEntryIgnoreMissing() { + if (lagLogicalPortNumber == null) { + return; + } + + log.info("Removing form DB created LAG port {} on switch {}. Key={}", lagLogicalPortNumber, switchId, key); + try { + lagPortOperationService.removeLagPort(switchId, lagLogicalPortNumber); + } catch (SwitchManagerException e) { + log.debug( + "Ignoring error on LAG logical port #{} on {} remove from DB during rollback: {}", + lagLogicalPortNumber, switchId, e.getMessage()); + } + } + @Override protected void afterTransitionCausedException(CreateLagState fromState, CreateLagState toState, CreateLagEvent event, CreateLagContext context) { diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/DeleteLagPortFsm.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/DeleteLagPortFsm.java index 233272e66d9..3e1ad93d7cf 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/DeleteLagPortFsm.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/DeleteLagPortFsm.java @@ -32,7 +32,6 @@ import org.openkilda.messaging.swmanager.response.LagPortResponse; import org.openkilda.model.LagLogicalPort; import org.openkilda.model.PhysicalPort; -import org.openkilda.model.Switch; import org.openkilda.model.SwitchId; import org.openkilda.wfm.topology.switchmanager.error.InconsistentDataException; import org.openkilda.wfm.topology.switchmanager.error.InvalidDataException; @@ -54,8 +53,7 @@ import org.squirrelframework.foundation.fsm.StateMachineStatus; import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine; -import java.util.ArrayList; -import java.util.Optional; +import java.util.Collections; import java.util.stream.Collectors; @Slf4j @@ -124,14 +122,14 @@ public String getKey() { void createGrpcRequest(DeleteLagState from, DeleteLagState to, DeleteLagEvent event, DeleteLagContext context) { log.info("Removing LAG {} on switch {}. Key={}", request, switchId, key); try { - Switch sw = lagPortOperationService.getSwitch(switchId); - String ipAddress = lagPortOperationService.getSwitchIpAddress(sw); - lagPortOperationService.validateLagBeforeDelete(switchId, request.getLogicalPortNumber()); + lagPortOperationService.ensureDeleteIsPossible(switchId, request.getLogicalPortNumber()); + + String ipAddress = lagPortOperationService.getSwitchIpAddress(switchId); grpcRequest = new DeleteLogicalPortRequest(ipAddress, request.getLogicalPortNumber()); } catch (LagPortNotFoundException | InvalidDataException | SwitchNotFoundException | InconsistentDataException e) { - log.error(format("Enable to delete LAG port %s from switch %s. Error: %s", request.getLogicalPortNumber(), - switchId, e.getMessage()), e); + log.error(format("Unable to delete LAG logical port %d on switch %s. Error: %s", + request.getLogicalPortNumber(), switchId, e.getMessage()), e); fire(ERROR, DeleteLagContext.builder().error(e).build()); } } @@ -143,22 +141,29 @@ void sendGrpcRequest(DeleteLagState from, DeleteLagState to, DeleteLagEvent even void removeDbLag(DeleteLagState from, DeleteLagState to, DeleteLagEvent event, DeleteLagContext context) { log.info("Removing LAG port {} from database. Switch {}. Key={}", context.deletedLogicalPort, switchId, key); - Optional lagPort = lagPortOperationService.removeLagPort( - switchId, request.getLogicalPortNumber()); - - if (lagPort.isPresent()) { - log.info("Successfully removed LAG {} from DB", lagPort.get()); - removedLagPort = lagPort.get(); - } else { - log.warn("Can't remove LAG {} from DB because LAG is not found", lagPort); - removedLagPort = new LagLogicalPort(switchId, request.getLogicalPortNumber(), new ArrayList()); + Integer portNumber = request.getLogicalPortNumber(); + try { + removedLagPort = lagPortOperationService.removeLagPort(switchId, portNumber); + log.info("Successfully removed LAG logical port {} on switch {} from DB", portNumber, switchId); + } catch (LagPortNotFoundException | InvalidDataException e) { + log.error( + "Can't remove LAG logical port {} on switch {} from DB: {}", portNumber, switchId, e.getMessage()); } } void finishedEnter(DeleteLagState from, DeleteLagState to, DeleteLagEvent event, DeleteLagContext context) { - LagPortResponse response = new LagPortResponse( - removedLagPort.getLogicalPortNumber(), removedLagPort.getPhysicalPorts().stream() - .map(PhysicalPort::getPortNumber).collect(Collectors.toList())); + LagPortResponse response; + if (removedLagPort != null) { + response = new LagPortResponse( + removedLagPort.getLogicalPortNumber(), removedLagPort.getPhysicalPorts().stream() + .map(PhysicalPort::getPortNumber).collect(Collectors.toList())); + + } else { + // dummy response entity + // TODO(surabujin): weird behaviour, can we be more correct? + response = new LagPortResponse(request.getLogicalPortNumber(), Collections.emptyList()); + } + InfoMessage message = new InfoMessage(response, System.currentTimeMillis(), key); carrier.cancelTimeoutCallback(key); diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortOperationConfig.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortOperationConfig.java new file mode 100644 index 00000000000..a1382f6f6bf --- /dev/null +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortOperationConfig.java @@ -0,0 +1,66 @@ +/* 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.switchmanager.service; + +import org.openkilda.persistence.repositories.RepositoryFactory; +import org.openkilda.persistence.tx.TransactionManager; +import org.openkilda.wfm.share.utils.PoolManager; + +import com.google.common.base.Preconditions; +import lombok.Builder; +import lombok.NonNull; +import lombok.Value; + +@Value +public class LagPortOperationConfig { + @NonNull + RepositoryFactory repositoryFactory; + + @NonNull + TransactionManager transactionManager; + + int bfdPortOffset; + int bfdPortMaxNumber; + + int poolCacheSize; + + PoolManager.PoolConfig poolConfig; + + @Builder + public LagPortOperationConfig( + @NonNull RepositoryFactory repositoryFactory, @NonNull TransactionManager transactionManager, + int bfdPortOffset, int bfdPortMaxNumber, + int portNumberFirst, int portNumberLast, int poolChunksCount, int poolCacheSize) { + this.repositoryFactory = repositoryFactory; + this.transactionManager = transactionManager; + + Preconditions.checkArgument(0 < bfdPortOffset, String.format( + "BFD logical port offset bfdPortOffset==%d must be greater than 0", bfdPortOffset)); + this.bfdPortOffset = bfdPortOffset; + + Preconditions.checkArgument(bfdPortOffset <= bfdPortMaxNumber, String.format( + "BFD logical port maximum value bfdPortMaxNumber==%d must be greater than bfdPortOffset==%d", + bfdPortMaxNumber, bfdPortOffset)); + this.bfdPortMaxNumber = bfdPortMaxNumber; + + Preconditions.checkArgument(0 < poolCacheSize, String.format( + "LAG port number pool managers cache size poolCacheSize==%d must be greater than 0", + poolCacheSize)); + this.poolCacheSize = poolCacheSize; + + poolConfig = new PoolManager.PoolConfig(portNumberFirst, portNumberLast, poolChunksCount); + } +} diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortOperationService.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortOperationService.java index bdc1f7d448f..9ccd9606f4e 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortOperationService.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortOperationService.java @@ -1,4 +1,4 @@ -/* Copyright 2021 Telstra Open Source +/* 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. @@ -15,31 +15,284 @@ package org.openkilda.wfm.topology.switchmanager.service; +import static java.lang.String.format; +import static org.openkilda.model.SwitchFeature.BFD; + +import org.openkilda.model.Flow; +import org.openkilda.model.FlowMirrorPath; +import org.openkilda.model.IpSocketAddress; +import org.openkilda.model.Isl; import org.openkilda.model.LagLogicalPort; +import org.openkilda.model.PathId; +import org.openkilda.model.PhysicalPort; import org.openkilda.model.Switch; import org.openkilda.model.SwitchFeature; import org.openkilda.model.SwitchId; +import org.openkilda.model.SwitchProperties; +import org.openkilda.persistence.exceptions.ConstraintViolationException; +import org.openkilda.persistence.repositories.FlowMirrorPathRepository; +import org.openkilda.persistence.repositories.FlowRepository; +import org.openkilda.persistence.repositories.IslRepository; +import org.openkilda.persistence.repositories.LagLogicalPortRepository; +import org.openkilda.persistence.repositories.PhysicalPortRepository; +import org.openkilda.persistence.repositories.RepositoryFactory; +import org.openkilda.persistence.repositories.SwitchPropertiesRepository; +import org.openkilda.persistence.repositories.SwitchRepository; +import org.openkilda.persistence.tx.TransactionManager; +import org.openkilda.wfm.share.utils.PoolManager; import org.openkilda.wfm.topology.switchmanager.error.InconsistentDataException; import org.openkilda.wfm.topology.switchmanager.error.InvalidDataException; import org.openkilda.wfm.topology.switchmanager.error.LagPortNotFoundException; import org.openkilda.wfm.topology.switchmanager.error.SwitchNotFoundException; +import com.google.common.collect.Sets; +import com.google.common.collect.Sets.SetView; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import net.jodah.failsafe.RetryPolicy; +import org.apache.commons.collections4.map.LRUMap; + +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.Objects; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +public class LagPortOperationService { + private final TransactionManager transactionManager; + private final SwitchRepository switchRepository; + private final SwitchPropertiesRepository switchPropertiesRepository; + private final LagLogicalPortRepository lagLogicalPortRepository; + private final PhysicalPortRepository physicalPortRepository; + private final IslRepository islRepository; + private final FlowRepository flowRepository; + private final FlowMirrorPathRepository flowMirrorPathRepository; + + private final LagPortOperationConfig config; + + private final LRUMap> portNumberPool; + + public LagPortOperationService(LagPortOperationConfig config) { + this.transactionManager = config.getTransactionManager(); + + @NonNull RepositoryFactory repositoryFactory = config.getRepositoryFactory(); + this.switchRepository = repositoryFactory.createSwitchRepository(); + this.switchPropertiesRepository = repositoryFactory.createSwitchPropertiesRepository(); + this.lagLogicalPortRepository = repositoryFactory.createLagLogicalPortRepository(); + this.physicalPortRepository = repositoryFactory.createPhysicalPortRepository(); + this.islRepository = repositoryFactory.createIslRepository(); + this.flowRepository = repositoryFactory.createFlowRepository(); + this.flowMirrorPathRepository = repositoryFactory.createFlowMirrorPathRepository(); + + this.config = config; + + portNumberPool = new LRUMap<>(config.getPoolCacheSize()); + } + + /** + * Create LAG logical port. + */ + public int createLagPort(SwitchId switchId, Set physicalPortNumbers) { + if (physicalPortNumbers == null || physicalPortNumbers.isEmpty()) { + throw new InvalidDataException("Physical ports list is empty"); + } + + LagLogicalPort port = transactionManager.doInTransaction( + newCreateRetryPolicy(switchId), () -> createTransaction(switchId, physicalPortNumbers)); + return port.getLogicalPortNumber(); + } + + /** + * Delete LAG logical port. + */ + public LagLogicalPort removeLagPort(SwitchId switchId, int logicalPortNumber) { + return transactionManager.doInTransaction( + newDeleteRetryPolicy(switchId), () -> deleteTransaction(switchId, logicalPortNumber)); + } + + /** + * Verify that LAG logical port can be removed (it exists and do not in use by any flow). + */ + public LagLogicalPort ensureDeleteIsPossible(SwitchId switchId, int logicalPortNumber) { + Switch sw = querySwitch(switchId); // locate switch first to produce correct error if switch is missing + Optional port = lagLogicalPortRepository.findBySwitchIdAndPortNumber( + switchId, logicalPortNumber); + if (!port.isPresent()) { + throw new LagPortNotFoundException(switchId, logicalPortNumber); + } + + List occupiedBy = flowRepository.findByEndpoint(switchId, logicalPortNumber).stream() + .map(Flow::getFlowId) + .collect(Collectors.toList()); + if (!occupiedBy.isEmpty()) { + throw new InvalidDataException(format("Couldn't delete LAG port '%d' from switch %s because flows '%s' " + + "use it as endpoint", logicalPortNumber, switchId, occupiedBy)); + } + + if (!isSwitchLagCapable(sw)) { + log.error( + "Processing request for remove existing LAG logical port #{} on switch {} without LAG support", + logicalPortNumber, switchId); + } + + return port.get(); + } + + private void validatePhysicalPort(SwitchId switchId, Set features, Integer portNumber) + throws InvalidDataException { + if (portNumber == null || portNumber <= 0) { + throw new InvalidDataException(format("Invalid physical port number %s. It can't be null or negative.", + portNumber)); + } + + int bfdPortOffset = config.getBfdPortOffset(); + int bfdPortMaxNumber = config.getBfdPortMaxNumber(); + if (features.contains(BFD) && portNumber >= bfdPortOffset && portNumber <= bfdPortMaxNumber) { + throw new InvalidDataException( + format("Physical port number %d intersects with BFD port range [%d, %d]", portNumber, + bfdPortOffset, bfdPortMaxNumber)); + } + + long lagPortOffset = config.getPoolConfig().getIdMinimum(); + if (portNumber >= lagPortOffset) { + throw new InvalidDataException( + format("Physical port number %d can't be greater than LAG port offset %d.", + portNumber, lagPortOffset)); + } + + Collection isls = islRepository.findByEndpoint(switchId, portNumber); + if (!isls.isEmpty()) { + throw new InvalidDataException( + format("Physical port number %d intersects with existing ISLs %s.", portNumber, isls)); + } + + Optional properties = switchPropertiesRepository.findBySwitchId(switchId); + if (properties.isPresent() && Objects.equals(properties.get().getServer42Port(), portNumber)) { + throw new InvalidDataException( + format("Physical port number %d on switch %s is server42 port.", portNumber, switchId)); + } + + Set flowIds = flowRepository.findByEndpoint(switchId, portNumber).stream() + .map(Flow::getFlowId).collect(Collectors.toSet()); + if (!flowIds.isEmpty()) { + throw new InvalidDataException(format("Physical port %d already used by following flows: %s. You must " + + "remove these flows to be able to use the port in LAG.", portNumber, flowIds)); + } + + Collection mirrorPaths = flowMirrorPathRepository + .findByEgressSwitchIdAndPort(switchId, portNumber); + if (!mirrorPaths.isEmpty()) { + Map> mirrorPathByFLowIdMap = new HashMap<>(); + for (FlowMirrorPath path : mirrorPaths) { + String flowId = path.getFlowMirrorPoints().getFlowPath().getFlowId(); + mirrorPathByFLowIdMap.computeIfAbsent(flowId, ignore -> new ArrayList<>()); + mirrorPathByFLowIdMap.get(flowId).add(path.getPathId()); + } + + String message = mirrorPathByFLowIdMap.entrySet().stream() + .map(entry -> format("flow '%s': %s", entry.getKey(), entry.getValue())) + .collect(Collectors.joining(", ")); + throw new InvalidDataException(format("Physical port %d already used as sink by following mirror points %s", + portNumber, message)); + } + } + + /** + * Fetch and decode switch IP address from DB. + */ + public String getSwitchIpAddress(SwitchId switchId) throws InvalidDataException, InconsistentDataException { + Switch sw = querySwitch(switchId); + return Optional.ofNullable(sw.getSocketAddress()).map(IpSocketAddress::getAddress).orElseThrow( + () -> new InconsistentDataException( + format("Switch %s has invalid IP address %s", sw, sw.getSocketAddress()))); + } + + private LagLogicalPort createTransaction(SwitchId switchId, Set targetPorts) { + Switch sw = querySwitch(switchId); // locate switch first to produce correct error if switch is missing + if (! isSwitchLagCapable(sw)) { + throw new InvalidDataException(format("Switch %s doesn't support LAG.", sw.getSwitchId())); + } + + Set features = sw.getFeatures(); + for (Integer portNumber : targetPorts) { + validatePhysicalPort(switchId, features, portNumber); + } + + ensureNoLagCollisions(switchId, targetPorts); + + LagLogicalPort port = queryPoolManager(switchId).allocate(); + port.setPhysicalPorts( + targetPorts.stream() + .map(portNumber -> new PhysicalPort(switchId, portNumber, port)) + .collect(Collectors.toList())); + + log.info("Adding new LAG logical port entry into DB: {}", port); + lagLogicalPortRepository.add(port); + return port; + } + + private LagLogicalPort deleteTransaction(SwitchId switchId, int logicalPortNumber) { + LagLogicalPort port = ensureDeleteIsPossible(switchId, logicalPortNumber); + log.info("Removing LAG logical port entry into DB: {}", port); + lagLogicalPortRepository.remove(port); + return port; + } + + private boolean isSwitchLagCapable(Switch sw) { + return sw.getFeatures().contains(SwitchFeature.LAG); + } + + private void ensureNoLagCollisions(SwitchId switchId, Set targetPorts) { + Set occupiedPorts = physicalPortRepository.findPortNumbersBySwitchId(switchId); + // FIXME(surabujin): we are unreasonably supposing that all physical port objects in DB related to LAGs + SetView intersection = Sets.intersection(occupiedPorts, new HashSet<>(targetPorts)); + + if (! intersection.isEmpty()) { + throw new InvalidDataException( + format("Physical ports [%s] on switch %s already occupied by other LAG group(s).", + intersection.stream().sorted() + .map(Object::toString) + .collect(Collectors.joining(", ")), switchId)); + } + } -public interface LagPortOperationService { - int createLagPort(SwitchId switchId, List physicalPortNumbers); + private PoolManager queryPoolManager(SwitchId switchId) { + return portNumberPool.computeIfAbsent(switchId, this::newPoolManager); + } - Optional removeLagPort(SwitchId switchId, int logicalPortNumber); + private Switch querySwitch(SwitchId switchId) throws SwitchNotFoundException { + return switchRepository.findById(switchId).orElseThrow(() -> new SwitchNotFoundException(switchId)); + } - void validatePhysicalPorts(SwitchId switchId, List physicalPortNumbers, Set features) - throws InvalidDataException; + private PoolManager newPoolManager(SwitchId switchId) { + LagPortPoolEntityAdapter adapter = new LagPortPoolEntityAdapter(config, lagLogicalPortRepository, switchId); + return new PoolManager<>(config.getPoolConfig(), adapter); + } - String getSwitchIpAddress(Switch sw) throws InvalidDataException, InconsistentDataException; + private RetryPolicy newCreateRetryPolicy(SwitchId switchId) { + return newRetryPolicy(switchId, "create"); + } - Switch getSwitch(SwitchId switchId) throws SwitchNotFoundException; + private RetryPolicy newDeleteRetryPolicy(SwitchId switchId) { + return newRetryPolicy(switchId, "delete"); + } - void validateLagBeforeDelete(SwitchId switchId, int logicalPortNumber) - throws LagPortNotFoundException, InvalidDataException; + private RetryPolicy newRetryPolicy(SwitchId switchId, String action) { + return transactionManager.getDefaultRetryPolicy() + .handle(ConstraintViolationException.class) + .onRetry( + e -> log.warn( + "Unable to {} LAG logical port DB record for switch {}: {}. Retrying #{}...", + action, switchId, e.getLastFailure().getMessage(), e.getAttemptCount())) + .onRetriesExceeded( + e -> log.error( + "Failed to {} LAG logical port DB record for switch {}: {}", + action, switchId, e.getFailure().getMessage(), e.getFailure())); + } } diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortPoolEntityAdapter.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortPoolEntityAdapter.java new file mode 100644 index 00000000000..a60cf1cb34a --- /dev/null +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/LagPortPoolEntityAdapter.java @@ -0,0 +1,69 @@ +/* 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.switchmanager.service; + +import org.openkilda.model.LagLogicalPort; +import org.openkilda.model.SwitchId; +import org.openkilda.persistence.repositories.LagLogicalPortRepository; +import org.openkilda.wfm.share.utils.PoolEntityAdapter; + +import java.util.Collections; +import java.util.Optional; + +public class LagPortPoolEntityAdapter implements PoolEntityAdapter { + private final LagPortOperationConfig config; + private final LagLogicalPortRepository repository; + + private final SwitchId switchId; + + public LagPortPoolEntityAdapter( + LagPortOperationConfig config, LagLogicalPortRepository repository, SwitchId switchId) { + this.config = config; + this.repository = repository; + this.switchId = switchId; + } + + @Override + public long getNumericSequentialId(LagLogicalPort entity) { + return entity.getLogicalPortNumber(); + } + + @Override + public Optional allocateSpecificId(long entityId) { + Optional existing = repository.findBySwitchIdAndPortNumber(switchId, (int) entityId); + if (existing.isPresent()) { + return Optional.empty(); + } + return Optional.of(newEntity(entityId)); + } + + @Override + public Optional allocateFirstInRange(long idMinimum, long idMaximum) { + return repository.findUnassignedPortInRange(switchId, (int) idMinimum, (int) idMaximum) + .map(this::newEntity); + } + + @Override + public String formatResourceNotAvailableMessage() { + return String.format( + "Unable to find any unassigned LAG logical port number for switch %s in range from %d to %d", + switchId, config.getPoolConfig().getIdMinimum(), config.getPoolConfig().getIdMaximum()); + } + + private LagLogicalPort newEntity(long portNumber) { + return new LagLogicalPort(switchId, (int) portNumber, Collections.emptyList()); + } +} diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/impl/LagPortOperationServiceImpl.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/impl/LagPortOperationServiceImpl.java deleted file mode 100644 index b96479428f3..00000000000 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/impl/LagPortOperationServiceImpl.java +++ /dev/null @@ -1,215 +0,0 @@ -/* Copyright 2021 Telstra Open Source - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.openkilda.wfm.topology.switchmanager.service.impl; - -import static java.lang.String.format; -import static org.openkilda.model.SwitchFeature.BFD; -import static org.openkilda.model.SwitchFeature.LAG; - -import org.openkilda.model.Flow; -import org.openkilda.model.FlowMirrorPath; -import org.openkilda.model.IpSocketAddress; -import org.openkilda.model.Isl; -import org.openkilda.model.LagLogicalPort; -import org.openkilda.model.PathId; -import org.openkilda.model.Switch; -import org.openkilda.model.SwitchFeature; -import org.openkilda.model.SwitchId; -import org.openkilda.model.SwitchProperties; -import org.openkilda.persistence.repositories.FlowMirrorPathRepository; -import org.openkilda.persistence.repositories.FlowRepository; -import org.openkilda.persistence.repositories.IslRepository; -import org.openkilda.persistence.repositories.LagLogicalPortRepository; -import org.openkilda.persistence.repositories.PhysicalPortRepository; -import org.openkilda.persistence.repositories.RepositoryFactory; -import org.openkilda.persistence.repositories.SwitchPropertiesRepository; -import org.openkilda.persistence.repositories.SwitchRepository; -import org.openkilda.persistence.tx.TransactionManager; -import org.openkilda.wfm.topology.switchmanager.error.InconsistentDataException; -import org.openkilda.wfm.topology.switchmanager.error.InvalidDataException; -import org.openkilda.wfm.topology.switchmanager.error.LagPortNotFoundException; -import org.openkilda.wfm.topology.switchmanager.error.SwitchNotFoundException; -import org.openkilda.wfm.topology.switchmanager.service.LagPortOperationService; - -import com.google.common.collect.Sets; -import com.google.common.collect.Sets.SetView; - -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.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -public class LagPortOperationServiceImpl implements LagPortOperationService { - private final TransactionManager transactionManager; - private final SwitchRepository switchRepository; - private final SwitchPropertiesRepository switchPropertiesRepository; - private final LagLogicalPortRepository lagLogicalPortRepository; - private final PhysicalPortRepository physicalPortRepository; - private final IslRepository islRepository; - private final FlowRepository flowRepository; - private final FlowMirrorPathRepository flowMirrorPathRepository; - private final int bfdPortOffset; - private final int bfdPortMaxNumber; - private final int lagPortOffset; - - public LagPortOperationServiceImpl(RepositoryFactory repositoryFactory, TransactionManager transactionManager, - int bfdPortOffset, int bfdPortMaxNumber, int lagPortOffset) { - this.transactionManager = transactionManager; - this.switchRepository = repositoryFactory.createSwitchRepository(); - this.switchPropertiesRepository = repositoryFactory.createSwitchPropertiesRepository(); - this.lagLogicalPortRepository = repositoryFactory.createLagLogicalPortRepository(); - this.physicalPortRepository = repositoryFactory.createPhysicalPortRepository(); - this.islRepository = repositoryFactory.createIslRepository(); - this.flowRepository = repositoryFactory.createFlowRepository(); - this.flowMirrorPathRepository = repositoryFactory.createFlowMirrorPathRepository(); - this.bfdPortOffset = bfdPortOffset; - this.bfdPortMaxNumber = bfdPortMaxNumber; - this.lagPortOffset = lagPortOffset; - } - - @Override - public int createLagPort(SwitchId switchId, List physicalPortNumbers) { - int lagLogicalPortNumber = LagLogicalPort.generateLogicalPortNumber(physicalPortNumbers, lagPortOffset); - LagLogicalPort lagLogicalPort = new LagLogicalPort(switchId, lagLogicalPortNumber, physicalPortNumbers); - - lagLogicalPortRepository.add(lagLogicalPort); - return lagLogicalPortNumber; - } - - @Override - public Optional removeLagPort(SwitchId switchId, int logicalPortNumber) { - return transactionManager.doInTransaction(() -> - lagLogicalPortRepository.findBySwitchIdAndPortNumber(switchId, logicalPortNumber) - .map(port -> { - // physical ports also will be removed by cascade delete operation - lagLogicalPortRepository.remove(port); - return port; - })); - } - - @Override - public void validatePhysicalPorts(SwitchId switchId, List physicalPortNumbers, - Set features) throws InvalidDataException { - if (physicalPortNumbers == null || physicalPortNumbers.isEmpty()) { - throw new InvalidDataException("Physical ports list is empty"); - } - - for (Integer portNumber : physicalPortNumbers) { - validatePhysicalPort(switchId, features, portNumber); - } - - Set existingPhysicalPorts = physicalPortRepository.findPortNumbersBySwitchId(switchId); - SetView portsIntersection = Sets.intersection( - existingPhysicalPorts, new HashSet<>(physicalPortNumbers)); - - if (!portsIntersection.isEmpty()) { - throw new InvalidDataException( - format("Physical ports %s on switch %s already occupied by other LAG group(s).", - portsIntersection, switchId)); - } - } - - private void validatePhysicalPort(SwitchId switchId, Set features, Integer portNumber) - throws InvalidDataException { - if (portNumber == null || portNumber <= 0) { - throw new InvalidDataException(format("Invalid physical port number %s. It can't be null or negative.", - portNumber)); - } - if (features.contains(BFD) && portNumber >= bfdPortOffset && portNumber <= bfdPortMaxNumber) { - throw new InvalidDataException( - format("Physical port number %d intersects with BFD port range [%d, %d]", portNumber, - bfdPortOffset, bfdPortMaxNumber)); - } - if (portNumber >= lagPortOffset) { - throw new InvalidDataException( - format("Physical port number %d can't be greater than LAG port offset %d.", - portNumber, lagPortOffset)); - } - - Collection isls = islRepository.findByEndpoint(switchId, portNumber); - if (!isls.isEmpty()) { - throw new InvalidDataException( - format("Physical port number %d intersects with existing ISLs %s.", portNumber, isls)); - } - - Optional properties = switchPropertiesRepository.findBySwitchId(switchId); - if (properties.isPresent() && Objects.equals(properties.get().getServer42Port(), portNumber)) { - throw new InvalidDataException( - format("Physical port number %d on switch %s is server42 port.", portNumber, switchId)); - } - - Set flowIds = flowRepository.findByEndpoint(switchId, portNumber).stream() - .map(Flow::getFlowId).collect(Collectors.toSet()); - if (!flowIds.isEmpty()) { - throw new InvalidDataException(format("Physical port %d already used by following flows: %s. You must " - + "remove these flows to be able to use the port in LAG.", portNumber, flowIds)); - } - - Collection mirrorPaths = flowMirrorPathRepository - .findByEgressSwitchIdAndPort(switchId, portNumber); - if (!mirrorPaths.isEmpty()) { - Map> mirrorPathByFLowIdMap = new HashMap<>(); - for (FlowMirrorPath path : mirrorPaths) { - String flowId = path.getFlowMirrorPoints().getFlowPath().getFlowId(); - mirrorPathByFLowIdMap.computeIfAbsent(flowId, ignore -> new ArrayList<>()); - mirrorPathByFLowIdMap.get(flowId).add(path.getPathId()); - } - - String message = mirrorPathByFLowIdMap.entrySet().stream() - .map(entry -> format("flow '%s': %s", entry.getKey(), entry.getValue())) - .collect(Collectors.joining(", ")); - throw new InvalidDataException(format("Physical port %d already used as sink by following mirror points %s", - portNumber, message)); - } - } - - @Override - public String getSwitchIpAddress(Switch sw) throws InvalidDataException, InconsistentDataException { - if (!sw.getFeatures().contains(LAG)) { - throw new InvalidDataException(format("Switch %s doesn't support LAG.", sw.getSwitchId())); - } - - return Optional.ofNullable(sw.getSocketAddress()).map(IpSocketAddress::getAddress).orElseThrow( - () -> new InconsistentDataException( - format("Switch %s has invalid IP address %s", sw, sw.getSocketAddress()))); - } - - @Override - public Switch getSwitch(SwitchId switchId) throws SwitchNotFoundException { - return switchRepository.findById(switchId).orElseThrow(() -> new SwitchNotFoundException(switchId)); - } - - @Override - public void validateLagBeforeDelete(SwitchId switchId, int logicalPortNumber) - throws LagPortNotFoundException, InvalidDataException { - if (!lagLogicalPortRepository.findBySwitchIdAndPortNumber(switchId, logicalPortNumber).isPresent()) { - throw new LagPortNotFoundException(switchId, logicalPortNumber); - } - - List flowIds = flowRepository.findByEndpoint(switchId, logicalPortNumber) - .stream().map(Flow::getFlowId).collect(Collectors.toList()); - if (!flowIds.isEmpty()) { - throw new InvalidDataException(format("Couldn't delete LAG port '%d' from switch %s because flows '%s' " - + "use it as endpoint", logicalPortNumber, switchId, flowIds)); - } - } -} diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/impl/fsmhandlers/CreateLagPortServiceImpl.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/impl/fsmhandlers/CreateLagPortServiceImpl.java index 69448906dd1..c895a4d4c0d 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/impl/fsmhandlers/CreateLagPortServiceImpl.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/impl/fsmhandlers/CreateLagPortServiceImpl.java @@ -20,8 +20,6 @@ import org.openkilda.messaging.error.ErrorMessage; import org.openkilda.messaging.info.grpc.CreateLogicalPortResponse; import org.openkilda.messaging.swmanager.request.CreateLagPortRequest; -import org.openkilda.persistence.repositories.RepositoryFactory; -import org.openkilda.persistence.tx.TransactionManager; import org.openkilda.wfm.share.utils.FsmExecutor; import org.openkilda.wfm.topology.switchmanager.error.OperationTimeoutException; import org.openkilda.wfm.topology.switchmanager.error.SpeakerFailureException; @@ -30,9 +28,9 @@ import org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagEvent; import org.openkilda.wfm.topology.switchmanager.fsm.CreateLagPortFsm.CreateLagState; import org.openkilda.wfm.topology.switchmanager.service.CreateLagPortService; +import org.openkilda.wfm.topology.switchmanager.service.LagPortOperationConfig; import org.openkilda.wfm.topology.switchmanager.service.LagPortOperationService; import org.openkilda.wfm.topology.switchmanager.service.SwitchManagerCarrier; -import org.openkilda.wfm.topology.switchmanager.service.impl.LagPortOperationServiceImpl; import lombok.extern.slf4j.Slf4j; import org.squirrelframework.foundation.fsm.StateMachineBuilder; @@ -51,11 +49,8 @@ public class CreateLagPortServiceImpl implements CreateLagPortService { private boolean active = true; - public CreateLagPortServiceImpl(SwitchManagerCarrier carrier, RepositoryFactory repositoryFactory, - TransactionManager transactionManager, int bfdPortOffset, int bfdPortMaxNumber, - int lagPortOffset) { - this.lagPortOperationService = new LagPortOperationServiceImpl(repositoryFactory, transactionManager, - bfdPortOffset, bfdPortMaxNumber, lagPortOffset); + public CreateLagPortServiceImpl(SwitchManagerCarrier carrier, LagPortOperationConfig config) { + this.lagPortOperationService = new LagPortOperationService(config); this.builder = CreateLagPortFsm.builder(); this.fsmExecutor = new FsmExecutor<>(CreateLagEvent.NEXT); this.carrier = carrier; @@ -144,8 +139,12 @@ private void fireFsmEvent(CreateLagPortFsm fsm, CreateLagEvent event, CreateLagC private void removeIfCompleted(CreateLagPortFsm fsm) { if (fsm.isTerminated()) { - log.info("Create LAG {} FSM have reached termination state (key={})", fsm.getRequest(), fsm.getKey()); - fsms.remove(fsm.getKey()); + String requestKey = fsm.getKey(); + log.info("Create LAG {} FSM have reached termination state (key={})", fsm.getRequest(), requestKey); + + fsms.remove(requestKey); + carrier.cancelTimeoutCallback(requestKey); + if (isAllOperationsCompleted() && !active) { carrier.sendInactive(); } diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/impl/fsmhandlers/DeleteLagPortServiceImpl.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/impl/fsmhandlers/DeleteLagPortServiceImpl.java index d517a2b99ec..06209682340 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/impl/fsmhandlers/DeleteLagPortServiceImpl.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/impl/fsmhandlers/DeleteLagPortServiceImpl.java @@ -20,8 +20,6 @@ import org.openkilda.messaging.error.ErrorMessage; import org.openkilda.messaging.info.grpc.DeleteLogicalPortResponse; import org.openkilda.messaging.swmanager.request.DeleteLagPortRequest; -import org.openkilda.persistence.repositories.RepositoryFactory; -import org.openkilda.persistence.tx.TransactionManager; import org.openkilda.wfm.share.utils.FsmExecutor; import org.openkilda.wfm.topology.switchmanager.error.OperationTimeoutException; import org.openkilda.wfm.topology.switchmanager.error.SpeakerFailureException; @@ -30,9 +28,9 @@ import org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagEvent; import org.openkilda.wfm.topology.switchmanager.fsm.DeleteLagPortFsm.DeleteLagState; import org.openkilda.wfm.topology.switchmanager.service.DeleteLagPortService; +import org.openkilda.wfm.topology.switchmanager.service.LagPortOperationConfig; import org.openkilda.wfm.topology.switchmanager.service.LagPortOperationService; import org.openkilda.wfm.topology.switchmanager.service.SwitchManagerCarrier; -import org.openkilda.wfm.topology.switchmanager.service.impl.LagPortOperationServiceImpl; import lombok.extern.slf4j.Slf4j; import org.squirrelframework.foundation.fsm.StateMachineBuilder; @@ -51,11 +49,8 @@ public class DeleteLagPortServiceImpl implements DeleteLagPortService { private boolean active = true; - public DeleteLagPortServiceImpl(SwitchManagerCarrier carrier, RepositoryFactory repositoryFactory, - TransactionManager transactionManager, int bfdPortOffset, int bfdPortMaxNumber, - int lagPortOffset) { - this.lagOperationService = new LagPortOperationServiceImpl(repositoryFactory, transactionManager, bfdPortOffset, - bfdPortMaxNumber, lagPortOffset); + public DeleteLagPortServiceImpl(SwitchManagerCarrier carrier, LagPortOperationConfig config) { + this.lagOperationService = new LagPortOperationService(config); this.builder = DeleteLagPortFsm.builder(); this.fsmExecutor = new FsmExecutor<>(DeleteLagEvent.NEXT); this.carrier = carrier; @@ -144,8 +139,11 @@ private void fireFsmEvent(DeleteLagPortFsm fsm, DeleteLagEvent event, DeleteLagC private void removeIfCompleted(DeleteLagPortFsm fsm) { if (fsm.isTerminated()) { - log.info("Delete LAG {} FSM have reached termination state (key={})", fsm.getRequest(), fsm.getKey()); - fsms.remove(fsm.getKey()); + String requestKey = fsm.getKey(); + log.info("Delete LAG {} FSM have reached termination state (key={})", fsm.getRequest(), requestKey); + fsms.remove(requestKey); + carrier.cancelTimeoutCallback(requestKey); + if (isAllOperationsCompleted() && !active) { carrier.sendInactive(); } From 26a4d7a793493cf7a1cae3a2048f87b91b11b9a3 Mon Sep 17 00:00:00 2001 From: Dmitry Poltavets Date: Mon, 24 Jan 2022 17:34:12 +0400 Subject: [PATCH 09/10] Fixed PCE for protected YFlows. --- .../fsm/yflow/reroute/YFlowRerouteFsm.java | 4 +-- .../actions/OnSubFlowAllocatedAction.java | 23 ++---------- .../actions/OnSubFlowReroutedAction.java | 36 +++++++++++++++---- .../actions/RerouteSubFlowsAction.java | 4 +-- .../fsm/yflow/update/YFlowUpdateFsm.java | 10 +++--- .../OnRevertSubFlowAllocatedAction.java | 21 ++--------- .../actions/OnSubFlowAllocatedAction.java | 21 ++--------- .../actions/OnSubFlowRevertedAction.java | 24 ++++++++++++- .../actions/OnSubFlowUpdatedAction.java | 24 ++++++++++++- .../update/actions/UpdateSubFlowsAction.java | 26 +++++++------- .../yflow/YFlowRerouteServiceTest.java | 4 +-- .../service/yflow/YFlowUpdateServiceTest.java | 2 +- .../pce/AvailableNetworkFactory.java | 6 +++- 13 files changed, 112 insertions(+), 93 deletions(-) diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/YFlowRerouteFsm.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/YFlowRerouteFsm.java index f13bf7c075f..14e8c209a0d 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/YFlowRerouteFsm.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/YFlowRerouteFsm.java @@ -188,11 +188,11 @@ public Factory(@NonNull YFlowRerouteHubCarrier carrier, @NonNull PersistenceMana builder.internalTransition() .within(State.REROUTING_SUB_FLOWS) .on(Event.SUB_FLOW_ALLOCATED) - .perform(new OnSubFlowAllocatedAction(flowRerouteService, persistenceManager)); + .perform(new OnSubFlowAllocatedAction(persistenceManager)); builder.internalTransition() .within(State.REROUTING_SUB_FLOWS) .on(Event.SUB_FLOW_REROUTED) - .perform(new OnSubFlowReroutedAction()); + .perform(new OnSubFlowReroutedAction(flowRerouteService)); builder.internalTransition() .within(State.REROUTING_SUB_FLOWS) .on(Event.SUB_FLOW_FAILED) 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 cb2f6f8eeba..48202563974 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 @@ -1,4 +1,4 @@ -/* Copyright 2021 Telstra Open Source +/* 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. @@ -41,7 +41,6 @@ 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.service.FlowRerouteService; import lombok.extern.slf4j.Slf4j; @@ -54,12 +53,10 @@ @Slf4j public class OnSubFlowAllocatedAction extends NbTrackableWithHistorySupportAction { - private final FlowRerouteService flowRerouteService; private final YFlowRepository yFlowRepository; - public OnSubFlowAllocatedAction(FlowRerouteService flowRerouteService, PersistenceManager persistenceManager) { + public OnSubFlowAllocatedAction(PersistenceManager persistenceManager) { super(persistenceManager); - this.flowRerouteService = flowRerouteService; RepositoryFactory repositoryFactory = persistenceManager.getRepositoryFactory(); this.yFlowRepository = repositoryFactory.createYFlowRepository(); } @@ -78,22 +75,6 @@ protected Optional performWithResponse(State from, State to, Event even stateMachine.addAllocatedSubFlow(subFlowId); - if (subFlowId.equals(stateMachine.getMainAffinityFlowId())) { - stateMachine.getRerouteRequests().forEach(rerouteRequest -> { - String requestedFlowId = rerouteRequest.getFlowId(); - if (!requestedFlowId.equals(subFlowId)) { - // clear to avoid the 'path has no affected ISLs' exception - rerouteRequest.getAffectedIsl().clear(); - stateMachine.addSubFlow(requestedFlowId); - stateMachine.addReroutingSubFlow(requestedFlowId); - stateMachine.notifyEventListeners(listener -> - listener.onSubFlowProcessingStart(yFlowId, requestedFlowId)); - CommandContext flowContext = stateMachine.getCommandContext().fork(requestedFlowId); - flowRerouteService.startFlowRerouting(rerouteRequest, flowContext, yFlowId); - } - }); - } - if (stateMachine.getAllocatedSubFlows().size() == stateMachine.getSubFlows().size()) { return Optional.of(buildRerouteResponseMessage(stateMachine)); } diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/OnSubFlowReroutedAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/OnSubFlowReroutedAction.java index d2a6092ad84..84b5dac3d08 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/OnSubFlowReroutedAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/OnSubFlowReroutedAction.java @@ -1,4 +1,4 @@ -/* Copyright 2021 Telstra Open Source +/* 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. @@ -17,29 +17,53 @@ import static java.lang.String.format; +import org.openkilda.wfm.CommandContext; import org.openkilda.wfm.topology.flowhs.fsm.common.actions.HistoryRecordingAction; 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.service.FlowRerouteService; import lombok.extern.slf4j.Slf4j; @Slf4j public class OnSubFlowReroutedAction extends HistoryRecordingAction { + private final FlowRerouteService flowRerouteService; + + public OnSubFlowReroutedAction(FlowRerouteService flowRerouteService) { + this.flowRerouteService = flowRerouteService; + } + @Override protected void perform(State from, State to, Event event, YFlowRerouteContext context, YFlowRerouteFsm stateMachine) { - String flowId = context.getSubFlowId(); - if (!stateMachine.isReroutingSubFlow(flowId)) { - throw new IllegalStateException("Received an event for non-pending sub-flow " + flowId); + String subFlowId = context.getSubFlowId(); + if (!stateMachine.isReroutingSubFlow(subFlowId)) { + throw new IllegalStateException("Received an event for non-pending sub-flow " + subFlowId); } stateMachine.saveActionToHistory("Rerouted a sub-flow", - format("Rerouted sub-flow %s of y-flow %s", flowId, stateMachine.getYFlowId())); + format("Rerouted sub-flow %s of y-flow %s", subFlowId, stateMachine.getYFlowId())); - stateMachine.removeReroutingSubFlow(flowId); + stateMachine.removeReroutingSubFlow(subFlowId); + + String yFlowId = stateMachine.getYFlowId(); + if (subFlowId.equals(stateMachine.getMainAffinityFlowId())) { + stateMachine.getRerouteRequests().forEach(rerouteRequest -> { + String requestedFlowId = rerouteRequest.getFlowId(); + if (!requestedFlowId.equals(subFlowId)) { + // clear to avoid the 'path has no affected ISLs' exception + rerouteRequest.getAffectedIsl().clear(); + stateMachine.addReroutingSubFlow(requestedFlowId); + stateMachine.notifyEventListeners(listener -> + listener.onSubFlowProcessingStart(yFlowId, requestedFlowId)); + CommandContext flowContext = stateMachine.getCommandContext().fork(requestedFlowId); + flowRerouteService.startFlowRerouting(rerouteRequest, flowContext, yFlowId); + } + }); + } if (stateMachine.getReroutingSubFlows().isEmpty()) { if (stateMachine.getFailedSubFlows().isEmpty()) { diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/RerouteSubFlowsAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/RerouteSubFlowsAction.java index 722cd33f43f..b44c7e41f96 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/RerouteSubFlowsAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/reroute/actions/RerouteSubFlowsAction.java @@ -1,4 +1,4 @@ -/* Copyright 2021 Telstra Open Source +/* 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. @@ -47,6 +47,7 @@ public void perform(State from, State to, Event event, YFlowRerouteContext conte boolean isMainAffinityFlowRequestSent = false; String yFlowId = stateMachine.getYFlowId(); for (String subFlowId : targetSubFlowIds) { + stateMachine.addSubFlow(subFlowId); FlowRerouteRequest rerouteRequest = new FlowRerouteRequest(subFlowId, stateMachine.isForceReroute(), false, stateMachine.isIgnoreBandwidth(), stateMachine.getAffectedIsls(), stateMachine.getRerouteReason(), false); @@ -67,7 +68,6 @@ public void perform(State from, State to, Event event, YFlowRerouteContext conte private void sendRerouteRequest(YFlowRerouteFsm stateMachine, FlowRerouteRequest rerouteRequest, String yFlowId) { String subFlowId = rerouteRequest.getFlowId(); - stateMachine.addSubFlow(subFlowId); stateMachine.addReroutingSubFlow(subFlowId); stateMachine.notifyEventListeners(listener -> listener.onSubFlowProcessingStart(yFlowId, subFlowId)); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/YFlowUpdateFsm.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/YFlowUpdateFsm.java index 0ef391af0ca..743c31ad965 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/YFlowUpdateFsm.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/YFlowUpdateFsm.java @@ -1,4 +1,4 @@ -/* Copyright 2021 Telstra Open Source +/* 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. @@ -197,11 +197,11 @@ public Factory(@NonNull FlowGenericCarrier carrier, @NonNull PersistenceManager builder.internalTransition() .within(State.UPDATING_SUB_FLOWS) .on(Event.SUB_FLOW_ALLOCATED) - .perform(new OnSubFlowAllocatedAction(flowUpdateService, persistenceManager)); + .perform(new OnSubFlowAllocatedAction(persistenceManager)); builder.internalTransition() .within(State.UPDATING_SUB_FLOWS) .on(Event.SUB_FLOW_UPDATED) - .perform(new OnSubFlowUpdatedAction()); + .perform(new OnSubFlowUpdatedAction(flowUpdateService)); builder.internalTransition() .within(State.UPDATING_SUB_FLOWS) .on(Event.SUB_FLOW_FAILED) @@ -390,11 +390,11 @@ public Factory(@NonNull FlowGenericCarrier carrier, @NonNull PersistenceManager builder.internalTransition() .within(State.REVERTING_SUB_FLOWS) .on(Event.SUB_FLOW_ALLOCATED) - .perform(new OnRevertSubFlowAllocatedAction(flowUpdateService, persistenceManager)); + .perform(new OnRevertSubFlowAllocatedAction(persistenceManager)); builder.internalTransition() .within(State.REVERTING_SUB_FLOWS) .on(Event.SUB_FLOW_UPDATED) - .perform(new OnSubFlowRevertedAction()); + .perform(new OnSubFlowRevertedAction(flowUpdateService)); builder.internalTransition() .within(State.REVERTING_SUB_FLOWS) .on(Event.SUB_FLOW_FAILED) diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnRevertSubFlowAllocatedAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnRevertSubFlowAllocatedAction.java index d7b51103392..c966ace9ca7 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnRevertSubFlowAllocatedAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnRevertSubFlowAllocatedAction.java @@ -1,4 +1,4 @@ -/* Copyright 2021 Telstra Open Source +/* 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. @@ -29,27 +29,23 @@ import org.openkilda.persistence.repositories.RepositoryFactory; import org.openkilda.persistence.repositories.YFlowRepository; import org.openkilda.persistence.tx.TransactionManager; -import org.openkilda.wfm.CommandContext; import org.openkilda.wfm.topology.flowhs.exception.FlowProcessingException; import org.openkilda.wfm.topology.flowhs.fsm.common.actions.HistoryRecordingAction; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateContext; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateFsm; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateFsm.Event; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateFsm.State; -import org.openkilda.wfm.topology.flowhs.service.FlowUpdateService; import lombok.extern.slf4j.Slf4j; @Slf4j public class OnRevertSubFlowAllocatedAction extends HistoryRecordingAction { - private final FlowUpdateService flowUpdateService; private final TransactionManager transactionManager; private final YFlowRepository yFlowRepository; protected final FlowRepository flowRepository; - public OnRevertSubFlowAllocatedAction(FlowUpdateService flowUpdateService, PersistenceManager persistenceManager) { - this.flowUpdateService = flowUpdateService; + public OnRevertSubFlowAllocatedAction(PersistenceManager persistenceManager) { this.transactionManager = persistenceManager.getTransactionManager(); RepositoryFactory repositoryFactory = persistenceManager.getRepositoryFactory(); this.yFlowRepository = repositoryFactory.createYFlowRepository(); @@ -100,18 +96,5 @@ protected void perform(State from, State to, Event event, YFlowUpdateContext con yFlow.updateSubFlow(subFlow); return yFlow; }); - - if (subFlowId.equals(stateMachine.getMainAffinityFlowId())) { - stateMachine.getRequestedFlows().forEach(originalFlow -> { - String requestedFlowId = originalFlow.getFlowId(); - if (!requestedFlowId.equals(subFlowId)) { - CommandContext flowContext = stateMachine.getCommandContext().fork(requestedFlowId); - stateMachine.addUpdatingSubFlow(requestedFlowId); - stateMachine.notifyEventListeners(listener -> - listener.onSubFlowProcessingStart(yFlowId, requestedFlowId)); - flowUpdateService.startFlowUpdating(flowContext, originalFlow, yFlowId); - } - }); - } } } diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnSubFlowAllocatedAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnSubFlowAllocatedAction.java index d7f995f162a..cf05528014d 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnSubFlowAllocatedAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnSubFlowAllocatedAction.java @@ -1,4 +1,4 @@ -/* Copyright 2021 Telstra Open Source +/* 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. @@ -39,7 +39,6 @@ import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateFsm.Event; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateFsm.State; import org.openkilda.wfm.topology.flowhs.mapper.YFlowMapper; -import org.openkilda.wfm.topology.flowhs.service.FlowUpdateService; import lombok.extern.slf4j.Slf4j; @@ -52,12 +51,10 @@ @Slf4j public class OnSubFlowAllocatedAction extends NbTrackableWithHistorySupportAction { - private final FlowUpdateService flowUpdateService; private final YFlowRepository yFlowRepository; - public OnSubFlowAllocatedAction(FlowUpdateService flowUpdateService, PersistenceManager persistenceManager) { + public OnSubFlowAllocatedAction(PersistenceManager persistenceManager) { super(persistenceManager); - this.flowUpdateService = flowUpdateService; RepositoryFactory repositoryFactory = persistenceManager.getRepositoryFactory(); this.yFlowRepository = repositoryFactory.createYFlowRepository(); } @@ -108,20 +105,6 @@ protected Optional performWithResponse(State from, State to, Event even return yFlow; }); - if (subFlowId.equals(stateMachine.getMainAffinityFlowId())) { - stateMachine.getRequestedFlows().forEach(requestedFlow -> { - String requestedFlowId = requestedFlow.getFlowId(); - if (!requestedFlowId.equals(subFlowId)) { - stateMachine.addSubFlow(requestedFlowId); - stateMachine.addUpdatingSubFlow(requestedFlowId); - stateMachine.notifyEventListeners(listener -> - listener.onSubFlowProcessingStart(yFlowId, requestedFlowId)); - CommandContext flowContext = stateMachine.getCommandContext().fork(requestedFlowId); - flowUpdateService.startFlowUpdating(flowContext, requestedFlow, yFlowId); - } - }); - } - if (stateMachine.getAllocatedSubFlows().size() == stateMachine.getSubFlows().size()) { return Optional.of(buildResponseMessage(result, stateMachine.getCommandContext())); } else { diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnSubFlowRevertedAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnSubFlowRevertedAction.java index 358fb2c7196..6b552d8465a 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnSubFlowRevertedAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnSubFlowRevertedAction.java @@ -1,4 +1,4 @@ -/* Copyright 2021 Telstra Open Source +/* 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. @@ -17,17 +17,25 @@ import static java.lang.String.format; +import org.openkilda.wfm.CommandContext; import org.openkilda.wfm.topology.flowhs.fsm.common.actions.HistoryRecordingAction; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateContext; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateFsm; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateFsm.Event; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateFsm.State; +import org.openkilda.wfm.topology.flowhs.service.FlowUpdateService; import lombok.extern.slf4j.Slf4j; @Slf4j public class OnSubFlowRevertedAction extends HistoryRecordingAction { + private final FlowUpdateService flowUpdateService; + + public OnSubFlowRevertedAction(FlowUpdateService flowUpdateService) { + this.flowUpdateService = flowUpdateService; + } + @Override protected void perform(State from, State to, Event event, YFlowUpdateContext context, YFlowUpdateFsm stateMachine) { String subFlowId = context.getSubFlowId(); @@ -42,6 +50,20 @@ protected void perform(State from, State to, Event event, YFlowUpdateContext con stateMachine.notifyEventListeners(listener -> listener.onSubFlowProcessingFinished(stateMachine.getYFlowId(), subFlowId)); + String yFlowId = stateMachine.getYFlowId(); + if (subFlowId.equals(stateMachine.getMainAffinityFlowId())) { + stateMachine.getRequestedFlows().forEach(originalFlow -> { + String requestedFlowId = originalFlow.getFlowId(); + if (!requestedFlowId.equals(subFlowId)) { + CommandContext flowContext = stateMachine.getCommandContext().fork(requestedFlowId); + stateMachine.addUpdatingSubFlow(requestedFlowId); + stateMachine.notifyEventListeners(listener -> + listener.onSubFlowProcessingStart(yFlowId, requestedFlowId)); + flowUpdateService.startFlowUpdating(flowContext, originalFlow, yFlowId); + } + }); + } + if (stateMachine.getUpdatingSubFlows().isEmpty()) { stateMachine.fire(Event.ALL_SUB_FLOWS_REVERTED); } diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnSubFlowUpdatedAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnSubFlowUpdatedAction.java index 6837931b944..75437cd0ad9 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnSubFlowUpdatedAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/OnSubFlowUpdatedAction.java @@ -1,4 +1,4 @@ -/* Copyright 2021 Telstra Open Source +/* 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. @@ -17,17 +17,25 @@ import static java.lang.String.format; +import org.openkilda.wfm.CommandContext; import org.openkilda.wfm.topology.flowhs.fsm.common.actions.HistoryRecordingAction; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateContext; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateFsm; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateFsm.Event; import org.openkilda.wfm.topology.flowhs.fsm.yflow.update.YFlowUpdateFsm.State; +import org.openkilda.wfm.topology.flowhs.service.FlowUpdateService; import lombok.extern.slf4j.Slf4j; @Slf4j public class OnSubFlowUpdatedAction extends HistoryRecordingAction { + private final FlowUpdateService flowUpdateService; + + public OnSubFlowUpdatedAction(FlowUpdateService flowUpdateService) { + this.flowUpdateService = flowUpdateService; + } + @Override protected void perform(State from, State to, Event event, YFlowUpdateContext context, YFlowUpdateFsm stateMachine) { String subFlowId = context.getSubFlowId(); @@ -42,6 +50,20 @@ protected void perform(State from, State to, Event event, YFlowUpdateContext con stateMachine.notifyEventListeners(listener -> listener.onSubFlowProcessingFinished(stateMachine.getYFlowId(), subFlowId)); + String yFlowId = stateMachine.getYFlowId(); + if (subFlowId.equals(stateMachine.getMainAffinityFlowId())) { + stateMachine.getRequestedFlows().forEach(requestedFlow -> { + String requestedFlowId = requestedFlow.getFlowId(); + if (!requestedFlowId.equals(subFlowId)) { + stateMachine.addUpdatingSubFlow(requestedFlowId); + stateMachine.notifyEventListeners(listener -> + listener.onSubFlowProcessingStart(yFlowId, requestedFlowId)); + CommandContext flowContext = stateMachine.getCommandContext().fork(requestedFlowId); + flowUpdateService.startFlowUpdating(flowContext, requestedFlow, yFlowId); + } + }); + } + if (stateMachine.getUpdatingSubFlows().isEmpty()) { if (stateMachine.getFailedSubFlows().isEmpty()) { stateMachine.fire(Event.ALL_SUB_FLOWS_UPDATED); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/UpdateSubFlowsAction.java b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/UpdateSubFlowsAction.java index ce17d53d65a..a3152e8ef18 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/UpdateSubFlowsAction.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/main/java/org/openkilda/wfm/topology/flowhs/fsm/yflow/update/actions/UpdateSubFlowsAction.java @@ -1,4 +1,4 @@ -/* Copyright 2021 Telstra Open Source +/* 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. @@ -46,17 +46,17 @@ public void perform(State from, State to, Event event, YFlowUpdateContext contex stateMachine.clearUpdatingSubFlows(); String yFlowId = stateMachine.getYFlowId(); - requestedFlows.stream() - .filter(requestedFlow -> requestedFlow.getFlowId().equals(stateMachine.getMainAffinityFlowId())) - .forEach(requestedFlow -> { - String subFlowId = requestedFlow.getFlowId(); - stateMachine.addSubFlow(subFlowId); - stateMachine.addUpdatingSubFlow(subFlowId); - stateMachine.notifyEventListeners(listener -> - listener.onSubFlowProcessingStart(yFlowId, subFlowId)); - CommandContext flowContext = stateMachine.getCommandContext().fork(subFlowId); - requestedFlow.setDiverseFlowId(stateMachine.getDiverseFlowId()); - flowUpdateService.startFlowUpdating(flowContext, requestedFlow, yFlowId); - }); + requestedFlows.forEach(requestedFlow -> { + String subFlowId = requestedFlow.getFlowId(); + stateMachine.addSubFlow(subFlowId); + if (requestedFlow.getFlowId().equals(stateMachine.getMainAffinityFlowId())) { + stateMachine.addUpdatingSubFlow(subFlowId); + stateMachine.notifyEventListeners(listener -> + listener.onSubFlowProcessingStart(yFlowId, subFlowId)); + CommandContext flowContext = stateMachine.getCommandContext().fork(subFlowId); + requestedFlow.setDiverseFlowId(stateMachine.getDiverseFlowId()); + flowUpdateService.startFlowUpdating(flowContext, requestedFlow, yFlowId); + } + }); } } diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/test/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowRerouteServiceTest.java b/src-java/flowhs-topology/flowhs-storm-topology/src/test/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowRerouteServiceTest.java index 2377c7fb493..4803b02d93e 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/test/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowRerouteServiceTest.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/test/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowRerouteServiceTest.java @@ -180,7 +180,7 @@ public void shouldFailIfNoPathAvailableForSecondSubFlow() // when processRerouteRequestAndSpeakerCommands(request, - FlowStatus.IN_PROGRESS, FlowStatus.IN_PROGRESS, FlowStatus.DOWN); + FlowStatus.IN_PROGRESS, FlowStatus.IN_PROGRESS, FlowStatus.UP); verifyNorthboundErrorResponse(yFlowRerouteHubCarrier, ErrorType.NOT_FOUND); verifyYFlowStatus(request.getYFlowId(), FlowStatus.DEGRADED, FlowStatus.UP, FlowStatus.DOWN); @@ -293,7 +293,7 @@ public void shouldFailOnUnsuccessfulMeterInstallation() // when service.handleRequest(request.getYFlowId(), new CommandContext(), request); - verifyYFlowAndSubFlowStatus(request.getYFlowId(), FlowStatus.IN_PROGRESS); + verifyYFlowStatus(request.getYFlowId(), FlowStatus.IN_PROGRESS, FlowStatus.IN_PROGRESS, FlowStatus.UP); // and handleSpeakerCommandsAndFailInstall(service, request.getYFlowId(), "test_successful_yflow"); diff --git a/src-java/flowhs-topology/flowhs-storm-topology/src/test/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowUpdateServiceTest.java b/src-java/flowhs-topology/flowhs-storm-topology/src/test/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowUpdateServiceTest.java index 8ac4b3a6c09..4d6489d4aab 100644 --- a/src-java/flowhs-topology/flowhs-storm-topology/src/test/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowUpdateServiceTest.java +++ b/src-java/flowhs-topology/flowhs-storm-topology/src/test/java/org/openkilda/wfm/topology/flowhs/service/yflow/YFlowUpdateServiceTest.java @@ -267,7 +267,7 @@ public void shouldFailOnUnsuccessfulMeterInstallation() // when service.handleRequest(request.getYFlowId(), new CommandContext(), request); - verifyYFlowAndSubFlowStatus(request.getYFlowId(), FlowStatus.IN_PROGRESS); + verifyYFlowStatus(request.getYFlowId(), FlowStatus.IN_PROGRESS, FlowStatus.IN_PROGRESS, FlowStatus.UP); // and handleSpeakerCommandsAndFailInstall(service, request.getYFlowId(), "test_successful_yflow"); diff --git a/src-java/kilda-pce/src/main/java/org/openkilda/pce/AvailableNetworkFactory.java b/src-java/kilda-pce/src/main/java/org/openkilda/pce/AvailableNetworkFactory.java index aac0c62b391..55bcba6c54d 100644 --- a/src-java/kilda-pce/src/main/java/org/openkilda/pce/AvailableNetworkFactory.java +++ b/src-java/kilda-pce/src/main/java/org/openkilda/pce/AvailableNetworkFactory.java @@ -1,4 +1,4 @@ -/* Copyright 2021 Telstra Open Source +/* 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. @@ -92,8 +92,12 @@ public AvailableNetwork getAvailableNetwork(Flow flow, Collection reuseP .collect(Collectors.toList()); } + Collection affinityPathIds = + flowPathRepository.findPathIdsByFlowAffinityGroupId(flow.getAffinityGroupId()); flowPaths.forEach(pathId -> flowPathRepository.findById(pathId) + .filter(flowPath -> !affinityPathIds.contains(flowPath.getPathId()) + || flowPath.getFlowId().equals(flow.getFlowId())) .ifPresent(flowPath -> { network.processDiversitySegments(flowPath.getSegments(), flow); network.processDiversitySegmentsWithPop(flowPath.getSegments()); From 44d5488307d5f03d6a0cf547338d8fc5ace1d2cf Mon Sep 17 00:00:00 2001 From: Dmitry Poltavets Date: Thu, 27 Jan 2022 17:01:20 +0400 Subject: [PATCH 10/10] Updated changelog --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1689c2ab64..edb08e475a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## v1.114.0 (31/01/2022) + +### Features: +- [#4675](https://github.com/telstra/open-kilda/pull/4675) Y Flow Ping: Periodic pings (Issue: [#4589](https://github.com/telstra/open-kilda/issues/4589)) [**storm-topologies**] +- [#4676](https://github.com/telstra/open-kilda/pull/4676) [test] use yFlowPing in func-tests [**tests**] +- [#4616](https://github.com/telstra/open-kilda/pull/4616) [yflow] tests for a subFlow [**tests**] +- [#4655](https://github.com/telstra/open-kilda/pull/4655) Add diversity to YFlow [**docs**][**northbound**][**storm-topologies**] + +### Bug Fixes: +- [#4674](https://github.com/telstra/open-kilda/pull/4674) Fix Y-flow update - handle allocateProtectedPath changes +- [#4677](https://github.com/telstra/open-kilda/pull/4677) Fix PCE for protected YFlows. +- [#4668](https://github.com/telstra/open-kilda/pull/4668) Fix switch connections transaction retry policy [**storm-topologies**] +- [#4671](https://github.com/telstra/open-kilda/pull/4671) Fix Y-flow API - add allocateProtectedPath to returned entities [**northbound**] + +### Improvements: +- [#4654](https://github.com/telstra/open-kilda/pull/4654) Reimplement LAG logical port number allocation [**storm-topologies**] + +For the complete list of changes, check out [the commit log](https://github.com/telstra/open-kilda/compare/v1.113.0...v1.114.0). + +### Affected Components: +ping, nbworker, network, nb, swmanager, flow-hs + +--- + ## v1.113.0 (25/01/2022) ### Features: