diff --git a/src-gui/ui/src/app/common/constants/constants.ts b/src-gui/ui/src/app/common/constants/constants.ts
index 614a080582b..d2206f23eaf 100644
--- a/src-gui/ui/src/app/common/constants/constants.ts
+++ b/src-gui/ui/src/app/common/constants/constants.ts
@@ -36,6 +36,8 @@ export const MessageObj = {
bfd_flag_updated: 'BFD flag updated successfully!',
flows_evacuated: 'All flows are evacuated successfully!',
error_flows_evacuated: 'Error in evacuating flows!',
+ info_cannot_evacuate_flows_from_switch: 'Can not evacuate flows while switch is not under maintenance.',
+ info_cannot_evacuate_flows_from_isl: 'Can not evacuate flows while ISL is not under maintenance.',
reverse_graph_no_data: 'Backward graph API did not return data.',
forward_graph_no_data: 'Forward graph API did not return data.',
updating_isl_bandwidth: 'Updating ISL max bandwidth',
diff --git a/src-gui/ui/src/app/modules/isl/isl-detail/isl-detail.component.html b/src-gui/ui/src/app/modules/isl/isl-detail/isl-detail.component.html
index 9c89bf50452..86f7d6fb121 100644
--- a/src-gui/ui/src/app/modules/isl/isl-detail/isl-detail.component.html
+++ b/src-gui/ui/src/app/modules/isl/isl-detail/isl-detail.component.html
@@ -242,18 +242,13 @@
ISL DETAILS
-
Evacuate All Flows:
-
+
Evacuate All Flows:
-
-
-
-
-
-
-
+
Evacuate
+
diff --git a/src-gui/ui/src/app/modules/isl/isl-detail/isl-detail.component.ts b/src-gui/ui/src/app/modules/isl/isl-detail/isl-detail.component.ts
index 4a42520b028..ac81ea4115f 100644
--- a/src-gui/ui/src/app/modules/isl/isl-detail/isl-detail.component.ts
+++ b/src-gui/ui/src/app/modules/isl/isl-detail/isl-detail.component.ts
@@ -42,7 +42,6 @@ export class IslDetailComponent implements OnInit, AfterViewInit, OnDestroy {
state = '';
bfd_session_status = '';
enable_bfd = false;
- evacuate = false;
under_maintenance = false;
loadingData = true;
isBFDEdit: any = false;
@@ -187,7 +186,6 @@ export class IslDetailComponent implements OnInit, AfterViewInit, OnDestroy {
this.bfd_session_status = retrievedObject.bfd_session_status;
this.available_bandwidth = retrievedObject.available_bandwidth;
this.under_maintenance = retrievedObject.under_maintenance;
- this.evacuate = retrievedObject.evacuate;
this.enable_bfd = retrievedObject.enable_bfd;
this.clipBoardItems = Object.assign(this.clipBoardItems, {
sourceSwitchName: retrievedObject.source_switch_name,
@@ -413,14 +411,13 @@ export class IslDetailComponent implements OnInit, AfterViewInit, OnDestroy {
}
evacuateIsl(e) {
+ if (!this.under_maintenance) {
+ this.toastr.info(MessageObj.info_cannot_evacuate_flows_from_isl, 'Can not evacuate');
+ return;
+ }
const modalRef = this.modalService.open(ModalconfirmationComponent);
modalRef.componentInstance.title = 'Confirmation';
- this.evacuate = e.target.checked;
- if (this.evacuate) {
- modalRef.componentInstance.content = 'Are you sure you want to evacuate all flows?';
- } else {
- modalRef.componentInstance.content = 'Are you sure ?';
- }
+ modalRef.componentInstance.content = 'Are you sure you want to evacuate all flows?';
modalRef.result.then((response) => {
if (response && response == true) {
const data = {
@@ -429,7 +426,7 @@ export class IslDetailComponent implements OnInit, AfterViewInit, OnDestroy {
dst_switch: this.dst_switch,
dst_port: this.dst_port,
under_maintenance: this.under_maintenance,
- evacuate: e.target.checked
+ evacuate: true
};
this.islListService.islUnderMaintenance(data).subscribe(response => {
this.toastr.success(MessageObj.flows_evacuated, 'Success');
@@ -437,11 +434,7 @@ export class IslDetailComponent implements OnInit, AfterViewInit, OnDestroy {
}, error => {
this.toastr.error(MessageObj.error_flows_evacuated, 'Error');
});
- } else {
- this.evacuate = false;
}
- }, error => {
- this.evacuate = false;
});
}
diff --git a/src-gui/ui/src/app/modules/switches/switch-detail/switch-detail.component.html b/src-gui/ui/src/app/modules/switches/switch-detail/switch-detail.component.html
index 96af9adcb39..24e78b6f0a9 100644
--- a/src-gui/ui/src/app/modules/switches/switch-detail/switch-detail.component.html
+++ b/src-gui/ui/src/app/modules/switches/switch-detail/switch-detail.component.html
@@ -108,12 +108,10 @@ SWITCH DETAILS
Evacuate:
diff --git a/src-gui/ui/src/app/modules/switches/switch-detail/switch-detail.component.ts b/src-gui/ui/src/app/modules/switches/switch-detail/switch-detail.component.ts
index 33c86bd03dd..ca8c7c40b1c 100644
--- a/src-gui/ui/src/app/modules/switches/switch-detail/switch-detail.component.ts
+++ b/src-gui/ui/src/app/modules/switches/switch-detail/switch-detail.component.ts
@@ -442,31 +442,24 @@ export class SwitchDetailComponent implements OnInit, AfterViewInit, OnDestroy {
}
evacuateSwitch(e) {
+ if (!this.underMaintenance) {
+ this.toastr.info(MessageObj.info_cannot_evacuate_flows_from_switch, 'Can not evacuate');
+ return;
+ }
const modalRef = this.modalService.open(ModalconfirmationComponent);
modalRef.componentInstance.title = 'Confirmation';
- this.evacuate = e.target.checked;
- if (this.evacuate) {
- modalRef.componentInstance.content = 'Are you sure you want to evacuate all flows?';
- } else {
- modalRef.componentInstance.content = 'Are you sure ?';
- }
+ modalRef.componentInstance.content = 'Are you sure you want to evacuate all flows?';
modalRef.result.then((response) => {
if (response && response == true) {
- const data = {'under_maintenance': this.underMaintenance, 'evacuate': e.target.checked};
+ const data = {'under_maintenance': this.underMaintenance, 'evacuate': true};
this.switchService.switchMaintenance(data, this.switchId).subscribe((serverResponse) => {
this.toastr.success(MessageObj.flows_evacuated, 'Success');
location.reload();
}, error => {
this.toastr.error(MessageObj.error_flows_evacuated, 'Error');
});
- } else {
- this.evacuate = false;
}
- }, error => {
- this.evacuate = false;
});
-
-
}
ngOnDestroy() {
diff --git a/src-java/kilda-model/src/main/java/org/openkilda/model/FlowPath.java b/src-java/kilda-model/src/main/java/org/openkilda/model/FlowPath.java
index 52cda6ef062..78a97356822 100644
--- a/src-java/kilda-model/src/main/java/org/openkilda/model/FlowPath.java
+++ b/src-java/kilda-model/src/main/java/org/openkilda/model/FlowPath.java
@@ -88,13 +88,14 @@ public FlowPath(@NonNull PathId pathId, @NonNull Switch srcSwitch, @NonNull Swit
long latency, long bandwidth,
boolean ignoreBandwidth, FlowPathStatus status, List segments,
Set applications,
- String sharedBandwidthGroupId, HaFlowPath haFlowPath) {
+ String sharedBandwidthGroupId, HaFlowPath haFlowPath, Flow flow) {
data = FlowPathDataImpl.builder().pathId(pathId).srcSwitch(srcSwitch).destSwitch(destSwitch)
.cookie(cookie).meterId(meterId).ingressMirrorGroupId(ingressMirrorGroupId)
.latency(latency).bandwidth(bandwidth)
.ignoreBandwidth(ignoreBandwidth).status(status)
.applications(applications)
.sharedBandwidthGroupId(sharedBandwidthGroupId).haFlowPath(haFlowPath)
+ .flow(flow)
.build();
// The reference is used to link path segments back to the path. See {@link #setSegments(List)}.
((FlowPathDataImpl) data).flowPath = this;
diff --git a/src-java/nbworker-topology/nbworker-storm-topology/src/main/java/org/openkilda/wfm/topology/nbworker/bolts/MessageEncoder.java b/src-java/nbworker-topology/nbworker-storm-topology/src/main/java/org/openkilda/wfm/topology/nbworker/bolts/MessageEncoder.java
index 8f8f5a17d05..e5a0ae08c22 100644
--- a/src-java/nbworker-topology/nbworker-storm-topology/src/main/java/org/openkilda/wfm/topology/nbworker/bolts/MessageEncoder.java
+++ b/src-java/nbworker-topology/nbworker-storm-topology/src/main/java/org/openkilda/wfm/topology/nbworker/bolts/MessageEncoder.java
@@ -17,7 +17,7 @@
import org.openkilda.messaging.Message;
import org.openkilda.messaging.MessageData;
-import org.openkilda.messaging.command.flow.FlowRerouteRequest;
+import org.openkilda.messaging.command.BaseRerouteRequest;
import org.openkilda.messaging.command.switches.SwitchValidateRequest;
import org.openkilda.messaging.error.ErrorData;
import org.openkilda.wfm.CommandContext;
@@ -38,7 +38,7 @@ protected void handleInput(Tuple input) throws Exception {
CommandContext commandContext = pullContext(input);
Message message = wrap(commandContext, payload);
- if (payload instanceof FlowRerouteRequest) {
+ if (payload instanceof BaseRerouteRequest) {
getOutput().emit(input.getSourceStreamId(), input, new Values(message));
} else if (payload instanceof SwitchValidateRequest) {
getOutput().emit(input.getSourceStreamId(), input,
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 8895b7b7224..66b1b5d52c4 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
@@ -24,6 +24,7 @@
import org.openkilda.messaging.command.flow.FlowRequest;
import org.openkilda.messaging.command.flow.FlowRerouteRequest;
import org.openkilda.messaging.command.haflow.HaFlowRerouteRequest;
+import org.openkilda.messaging.command.yflow.YFlowRerouteRequest;
import org.openkilda.messaging.error.ErrorType;
import org.openkilda.messaging.error.InvalidFlowException;
import org.openkilda.messaging.error.MessageException;
@@ -83,6 +84,7 @@
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.jodah.failsafe.RetryPolicy;
+import org.apache.commons.lang3.StringUtils;
import java.time.Duration;
import java.time.Instant;
@@ -713,10 +715,20 @@ public List makeRerouteRequests(
}
}
} else {
- if (processed.add(flow.getFlowId())) {
- FlowRerouteRequest request = new FlowRerouteRequest(
- flow.getFlowId(), false, false, affectedIslEndpoints, reason, false);
- results.add(request);
+ if (StringUtils.isNotBlank(flow.getYFlowId())) {
+ if (yFlowRepository.exists(flow.getYFlowId())) {
+ if (processed.add(flow.getYFlowId())) {
+ YFlowRerouteRequest req = new YFlowRerouteRequest(flow.getYFlowId(), affectedIslEndpoints,
+ reason, false);
+ results.add(req);
+ }
+ }
+ } else {
+ if (processed.add(flow.getFlowId())) {
+ FlowRerouteRequest request = new FlowRerouteRequest(
+ flow.getFlowId(), false, false, affectedIslEndpoints, reason, false);
+ results.add(request);
+ }
}
}
}
diff --git a/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/topology/nbworker/services/FlowOperationsServiceTest.java b/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/topology/nbworker/services/FlowOperationsServiceTest.java
index 3bd37ee9719..d09a9c4ea08 100644
--- a/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/topology/nbworker/services/FlowOperationsServiceTest.java
+++ b/src-java/nbworker-topology/nbworker-storm-topology/src/test/java/org/openkilda/wfm/topology/nbworker/services/FlowOperationsServiceTest.java
@@ -23,7 +23,10 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.openkilda.messaging.command.BaseRerouteRequest;
import org.openkilda.messaging.command.flow.FlowRequest;
+import org.openkilda.messaging.command.flow.FlowRerouteRequest;
+import org.openkilda.messaging.command.yflow.YFlowRerouteRequest;
import org.openkilda.messaging.error.InvalidFlowException;
import org.openkilda.messaging.info.InfoData;
import org.openkilda.messaging.model.FlowPatch;
@@ -39,12 +42,15 @@
import org.openkilda.model.Switch;
import org.openkilda.model.SwitchId;
import org.openkilda.model.SwitchStatus;
+import org.openkilda.model.YFlow;
+import org.openkilda.model.YFlow.SharedEndpoint;
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.PathSegmentRepository;
import org.openkilda.persistence.repositories.SwitchRepository;
+import org.openkilda.persistence.repositories.YFlowRepository;
import org.openkilda.wfm.error.FlowNotFoundException;
import org.openkilda.wfm.error.SwitchNotFoundException;
import org.openkilda.wfm.share.flow.TestFlowBuilder;
@@ -71,6 +77,7 @@ public class FlowOperationsServiceTest extends InMemoryGraphBasedTest {
private static final String FLOW_ID_1 = "flow_1";
private static final String FLOW_ID_2 = "flow_2";
private static final String FLOW_ID_3 = "flow_3";
+ private static final String Y_FLOW_ID_1 = "y_flow_1";
private static final PathId FORWARD_PATH_1 = new PathId("forward_path_1");
private static final PathId FORWARD_PATH_2 = new PathId("forward_path_2");
private static final PathId FORWARD_PATH_3 = new PathId("forward_path_3");
@@ -91,6 +98,7 @@ public class FlowOperationsServiceTest extends InMemoryGraphBasedTest {
private static FlowOperationsService flowOperationsService;
private static FlowRepository flowRepository;
+ private static YFlowRepository yFlowRepository;
private static FlowPathRepository flowPathRepository;
private static PathSegmentRepository pathSegmentRepository;
private static SwitchRepository switchRepository;
@@ -103,6 +111,7 @@ public class FlowOperationsServiceTest extends InMemoryGraphBasedTest {
@BeforeAll
public static void setUpOnce() {
flowRepository = persistenceManager.getRepositoryFactory().createFlowRepository();
+ yFlowRepository = persistenceManager.getRepositoryFactory().createYFlowRepository();
flowPathRepository = persistenceManager.getRepositoryFactory().createFlowPathRepository();
pathSegmentRepository = persistenceManager.getRepositoryFactory().createPathSegmentRepository();
switchRepository = persistenceManager.getRepositoryFactory().createSwitchRepository();
@@ -789,7 +798,7 @@ public void whenFlowWithMaxLatency_patchFlowWithLatencyTier2OnlyTest()
}
@Test
- void whenPartialUpdate_dumpBeforeAndDumpAfterIsSaved() throws FlowNotFoundException, InvalidFlowException {
+ public void whenPartialUpdate_dumpBeforeAndDumpAfterIsSaved() throws FlowNotFoundException, InvalidFlowException {
Flow createdFlow = createFlow(FLOW_ID_1, switchA, 1, switchC, 2,
FORWARD_PATH_1, REVERSE_PATH_1, switchB, false,
100_500L, 0L);
@@ -815,7 +824,7 @@ void whenPartialUpdate_dumpBeforeAndDumpAfterIsSaved() throws FlowNotFoundExcept
}
@Test
- void whenFullUpdateIsRequired_historyActionIsSaved() throws FlowNotFoundException, InvalidFlowException {
+ public void whenFullUpdateIsRequired_historyActionIsSaved() throws FlowNotFoundException, InvalidFlowException {
Flow createdFlow = createFlow(FLOW_ID_1, switchA, 1, switchC, 2,
FORWARD_PATH_1, REVERSE_PATH_1, switchB, false,
100_500L, 0L);
@@ -839,6 +848,91 @@ void whenFullUpdateIsRequired_historyActionIsSaved() throws FlowNotFoundExceptio
assertEquals(action, carrier.getHistoryHolderList().get(2).getFlowHistoryData().getAction());
}
+ @Test
+ public void makeRerouteRequests() {
+ YFlow yFlow = buildYFlow(Y_FLOW_ID_1, switchA, 1, switchD);
+ yFlowRepository.add(yFlow);
+
+ Flow ySubflow1 = buildFlow(null, Y_FLOW_ID_1, switchA, 1, 10,
+ switchB, 2, 11, "subFlow1", yFlow);
+ FlowPath yFlowForwardPath1 = FlowPath.builder()
+ .pathId(new PathId("subPath1"))
+ .srcSwitch(switchA)
+ .destSwitch(switchB)
+ .flow(ySubflow1)
+ .build();
+
+ Flow ySubflow2 = buildFlow(null, Y_FLOW_ID_1, switchA, 1, 20,
+ switchC, 2, 22, "subFlow2", yFlow);
+ FlowPath yFlowForwardPath2 = FlowPath.builder()
+ .pathId(new PathId("subPath2"))
+ .srcSwitch(switchA)
+ .destSwitch(switchB)
+ .flow(ySubflow2)
+ .build();
+
+ Flow flow1 = buildFlow(FLOW_ID_1, null, switchA, 1, 100, switchB,
+ 2, 111, "regularFlow", null);
+ FlowPath flowPath1 = FlowPath.builder()
+ .pathId(new PathId("path1"))
+ .srcSwitch(switchA)
+ .destSwitch(switchB)
+ .flow(flow1)
+ .build();
+
+ List flowPaths = Arrays.asList(yFlowForwardPath1, flowPath1, yFlowForwardPath2);
+
+ // 3 flow path: 1 for regular flow and 2 for y-flow
+ List actualResult =
+ flowOperationsService.makeRerouteRequests(flowPaths, new HashSet<>(), "Great reason to reroute");
+
+ Assertions.assertEquals(2, actualResult.size());
+ Assertions.assertInstanceOf(YFlowRerouteRequest.class, actualResult.get(0));
+ Assertions.assertEquals(Y_FLOW_ID_1, actualResult.get(0).getFlowId());
+ Assertions.assertInstanceOf(FlowRerouteRequest.class, actualResult.get(1));
+ Assertions.assertEquals(FLOW_ID_1, actualResult.get(1).getFlowId());
+
+ // y-flow does not exist in the repository
+ transactionManager.doInTransaction(() -> yFlowRepository.remove(yFlow));
+ actualResult = flowOperationsService.makeRerouteRequests(flowPaths, new HashSet<>(), "Great reason to reroute");
+ Assertions.assertEquals(1, actualResult.size());
+ Assertions.assertInstanceOf(FlowRerouteRequest.class, actualResult.get(0));
+ Assertions.assertEquals(FLOW_ID_1, actualResult.get(0).getFlowId());
+ }
+
+ private YFlow buildYFlow(String yFlowId, Switch sharedEndpoint, int portNumber, Switch yPoint) {
+ return YFlow.builder()
+ .yFlowId(yFlowId)
+ .sharedEndpoint(new SharedEndpoint(sharedEndpoint.getSwitchId(), portNumber))
+ .yPoint(yPoint.getSwitchId())
+ .status(FlowStatus.UP)
+ .build();
+ }
+
+ private Flow buildFlow(String flowId, String yflowId, Switch srcSwitch, int srcPort, int srcVlan, Switch destSwitch,
+ int destPort, int destVlan, String desc, YFlow yflow) {
+ TestFlowBuilder builder = new TestFlowBuilder()
+ .yFlow(yflow)
+ .srcSwitch(srcSwitch)
+ .srcPort(srcPort)
+ .srcVlan(srcVlan)
+ .destSwitch(destSwitch)
+ .destPort(destPort)
+ .destVlan(destVlan)
+ .encapsulationType(FlowEncapsulationType.TRANSIT_VLAN)
+ .pathComputationStrategy(PathComputationStrategy.COST)
+ .description(desc)
+ .status(FlowStatus.UP);
+
+ if (flowId != null) {
+ builder.flowId(flowId);
+ }
+ if (yflowId != null) {
+ builder.yFlowId(yflowId);
+ }
+ return builder.build();
+ }
+
private void assertFlows(Collection actualFlows, String... expectedFlowIds) {
assertEquals(expectedFlowIds.length, actualFlows.size());
assertEquals(new HashSet<>(Arrays.asList(expectedFlowIds)),