From 365b96cc388ceb9d3af15c2bbdf66d609911305c Mon Sep 17 00:00:00 2001 From: pkazlenka Date: Wed, 25 Oct 2023 12:41:00 +0200 Subject: [PATCH 1/5] #5390: [TEST] Use helper methods to create flow Implements #5390 * Replaced direct Northbound calls to create flows with helper method (for safety) where possible * Method FlowHelperV2.addFlow() now accepts expected flow state as an optional parameter * Added several FlowHelper methods to retrieve history entries easier --- .../functionaltests/helpers/FlowHelper.groovy | 28 ++++++++++ .../helpers/FlowHelperV2.groovy | 29 +++++++--- .../configuration/ConfigurationSpec.groovy | 4 +- .../spec/flows/AutoRerouteSpec.groovy | 56 +++++++++---------- .../spec/flows/BandwidthSpec.groovy | 5 +- .../spec/flows/FlowAffinitySpec.groovy | 16 +++--- .../spec/flows/FlowCrudSpec.groovy | 31 +++++----- .../spec/flows/FlowCrudV1Spec.groovy | 10 ++-- .../spec/flows/FlowDiversitySpec.groovy | 10 ++-- .../spec/flows/FlowHistorySpec.groovy | 2 +- .../spec/flows/FlowLoopSpec.groovy | 4 +- .../spec/flows/FlowMonitoringSpec.groovy | 4 +- .../spec/flows/MaxLatencySpec.groovy | 36 +++--------- .../spec/flows/MirrorEndpointsSpec.groovy | 7 +-- .../spec/flows/ProtectedPathSpec.groovy | 6 +- .../spec/flows/ProtectedPathV1Spec.groovy | 4 +- .../spec/flows/QinQFlowSpec.groovy | 8 +-- .../spec/flows/SwapEndpointSpec.groovy | 4 +- .../spec/flows/ThrottlingRerouteSpec.groovy | 10 ++-- .../spec/flows/VxlanFlowSpec.groovy | 39 +++++++------ .../spec/flows/yflows/SubFlowSpec.groovy | 2 +- .../spec/flows/yflows/YFlowCreateSpec.groovy | 8 +-- .../flows/yflows/YFlowDiversitySpec.groovy | 6 +- .../spec/flows/yflows/YFlowRerouteSpec.groovy | 4 +- .../spec/switches/SwitchFailuresSpec.groovy | 10 ++-- .../spec/switches/SwitchesSpec.groovy | 4 +- .../spec/toggles/FeatureTogglesSpec.groovy | 2 +- .../spec/toggles/FeatureTogglesV2Spec.groovy | 10 ++-- .../spec/xresilience/ChaosSpec.groovy | 2 +- .../FloodlightKafkaConnectionSpec.groovy | 2 +- .../spec/xresilience/RetriesSpec.groovy | 18 +++--- 31 files changed, 198 insertions(+), 183 deletions(-) diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/FlowHelper.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/FlowHelper.groovy index eca6465089e..6dbc7ad8399 100644 --- a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/FlowHelper.groovy +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/FlowHelper.groovy @@ -1,6 +1,7 @@ package org.openkilda.functionaltests.helpers import org.openkilda.messaging.payload.flow.PathNodePayload +import org.openkilda.messaging.payload.history.FlowHistoryEntry import org.openkilda.model.SwitchId import static groovyx.gpars.GParsPool.withPool @@ -169,6 +170,33 @@ class FlowHelper { return response } + /** + * Returns last (the freshest) flow history entry + * @param flowId + * @return + */ + FlowHistoryEntry getLatestHistoryEntry(String flowId) { + return northbound.getFlowHistory(flowId).last() + } + + /** + * Returns the number of entries in flow history + * @param flowId + * @return + */ + int getHistorySize(String flowId) { + return northbound.getFlowHistory(flowId).size() + } + + //TODO: Switch to enum for action + List getHistoryEntriesByAction(String flowId, String action) { + return northbound.getFlowHistory(flowId).findAll {it.getAction() == action} + } + + FlowHistoryEntry getEarliestHistoryEntryByAction(String flowId, String action) { + return getHistoryEntriesByAction(flowId, action).first() + } + /** * Check whether given potential flow is conflicting with any of flows in the given list. * Usually used to ensure that some new flow is by accident is not conflicting with any of existing flows. diff --git a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/FlowHelperV2.groovy b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/FlowHelperV2.groovy index 12bb223101d..9c152f370d5 100644 --- a/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/FlowHelperV2.groovy +++ b/src-java/testing/functional-tests/src/main/groovy/org/openkilda/functionaltests/helpers/FlowHelperV2.groovy @@ -6,6 +6,8 @@ import static org.openkilda.functionaltests.helpers.FlowHistoryConstants.CREATE_ import static org.openkilda.functionaltests.helpers.FlowHistoryConstants.CREATE_SUCCESS import static org.openkilda.functionaltests.helpers.FlowHistoryConstants.DELETE_SUCCESS import static org.openkilda.functionaltests.helpers.FlowHistoryConstants.PARTIAL_UPDATE_ONLY_IN_DB +import static org.openkilda.messaging.payload.flow.FlowState.IN_PROGRESS +import static org.openkilda.messaging.payload.flow.FlowState.UP import static org.openkilda.testing.Constants.FLOW_CRUD_TIMEOUT import static org.openkilda.testing.Constants.WAIT_OFFSET import static org.springframework.beans.factory.config.ConfigurableBeanFactory.SCOPE_PROTOTYPE @@ -153,23 +155,32 @@ class FlowHelperV2 { } /** - * Adds flow and waits for it to become UP + * Adds flow and waits for it to become in expected state ('Up' by default) */ - FlowResponseV2 addFlow(FlowRequestV2 flow) { + FlowResponseV2 addFlow(FlowRequestV2 flow, FlowState expectedFlowState = UP) { log.debug("Adding flow '${flow.flowId}'") def response = northboundV2.addFlow(flow) Wrappers.wait(FLOW_CRUD_TIMEOUT) { - assert northboundV2.getFlowStatus(flow.flowId).status == FlowState.UP - assert northbound.getFlowHistory(flow.flowId).last().payload.last().action == CREATE_SUCCESS + assert northboundV2.getFlowStatus(flow.flowId).status == expectedFlowState + if (expectedFlowState != IN_PROGRESS) { + assert northbound.getFlowHistory(flow.flowId).last().payload.last().action == CREATE_SUCCESS + } } return response } /** - * Adds flow and waits for it to become UP + * Adds flow and waits for it to become in expected state ('Up' by default) + */ + FlowResponseV2 addFlow(FlowCreatePayload flow, FlowState expectedFlowState = UP) { + return addFlow(toV2(flow), expectedFlowState); + } + + /** + * Sends flow create request but doesn't wait for flow to go up. */ - FlowResponseV2 addFlow(FlowCreatePayload flow) { - return addFlow(toV2(flow)); + FlowResponseV2 attemptToAddFlow(FlowRequestV2 flow) { + return northboundV2.addFlow(flow) } /** @@ -193,7 +204,7 @@ class FlowHelperV2 { log.debug("Updating flow '${flowId}'") def response = northboundV2.updateFlow(flowId, flow) Wrappers.wait(FLOW_CRUD_TIMEOUT) { - assert northboundV2.getFlowStatus(flowId).status == FlowState.UP + assert northboundV2.getFlowStatus(flowId).status == UP assert northbound.getFlowHistory(flowId).last().payload.last().action == UPDATE_SUCCESS } return response @@ -211,7 +222,7 @@ class FlowHelperV2 { String action = isUpdateConsecutive ? UPDATE_SUCCESS : PARTIAL_UPDATE_ONLY_IN_DB def response = northboundV2.partialUpdate(flowId, flow) Wrappers.wait(FLOW_CRUD_TIMEOUT) { - assert northboundV2.getFlowStatus(flowId).status == FlowState.UP + assert northboundV2.getFlowStatus(flowId).status == UP assert northbound.getFlowHistory(flowId).last().payload.last().action == action } return response diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/configuration/ConfigurationSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/configuration/ConfigurationSpec.groovy index 847b8f4c4db..77609c00b74 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/configuration/ConfigurationSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/configuration/ConfigurationSpec.groovy @@ -43,7 +43,7 @@ class ConfigurationSpec extends HealthCheckSpecification { assumeTrue(switchPair != null, "Unable to find required switch pair in topology") def flow1 = flowHelperV2.randomFlow(switchPair) flow1.encapsulationType = null - northboundV2.addFlow(flow1) + flowHelperV2.addFlow(flow1) then: "Flow is created with current default encapsulation type(transit_vlan)" northboundV2.getFlow(flow1.flowId).encapsulationType == defaultEncapsulationType.toString().toLowerCase() @@ -62,7 +62,7 @@ class ConfigurationSpec extends HealthCheckSpecification { when: "Create a flow without encapsulation type" def flow2 = flowHelperV2.randomFlow(switchPair, false, [flow1]) flow2.encapsulationType = null - northboundV2.addFlow(flow2) + flowHelperV2.addFlow(flow2) then: "Flow is created with new default encapsulation type(vxlan)" northboundV2.getFlow(flow2.flowId).encapsulationType == newFlowEncapsulationType.toString().toLowerCase() diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/AutoRerouteSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/AutoRerouteSpec.groovy index 40f5f2ba1c2..adf0e959b00 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/AutoRerouteSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/AutoRerouteSpec.groovy @@ -11,6 +11,7 @@ import static org.openkilda.functionaltests.helpers.FlowHistoryConstants.REROUTE import static org.openkilda.functionaltests.helpers.FlowHistoryConstants.REROUTE_COMPLETE import static org.openkilda.functionaltests.helpers.FlowHistoryConstants.REROUTE_FAIL import static org.openkilda.functionaltests.helpers.FlowHistoryConstants.REROUTE_SUCCESS +import static org.openkilda.functionaltests.helpers.Wrappers.retry import static org.openkilda.functionaltests.helpers.Wrappers.timedLoop import static org.openkilda.functionaltests.helpers.Wrappers.wait import static org.openkilda.messaging.info.event.IslChangeType.FAILED @@ -22,7 +23,6 @@ import org.openkilda.functionaltests.HealthCheckSpecification import org.openkilda.functionaltests.extension.tags.IterationTag import org.openkilda.functionaltests.extension.tags.Tags import org.openkilda.functionaltests.helpers.PathHelper -import org.openkilda.functionaltests.helpers.Wrappers import org.openkilda.functionaltests.helpers.model.SwitchPair import org.openkilda.messaging.info.event.IslChangeType import org.openkilda.messaging.info.event.PathNode @@ -276,7 +276,7 @@ class AutoRerouteSpec extends HealthCheckSpecification { then: "The flow becomes 'Down'" wait(rerouteDelay + WAIT_OFFSET * 2) { assert northboundV2.getFlowStatus(flow.flowId).status == FlowState.DOWN - def reroutes = northbound.getFlowHistory(flow.flowId).findAll { it.action == REROUTE_ACTION } + def reroutes = flowHelper.getHistoryEntriesByAction(flow.flowId, REROUTE_ACTION) assert reroutes.size() == reroutesCount assert reroutes.last().payload.last().action == REROUTE_FAIL } @@ -290,7 +290,7 @@ class AutoRerouteSpec extends HealthCheckSpecification { then: "The flow becomes 'Up'" wait(rerouteDelay + WAIT_OFFSET) { assert northboundV2.getFlowStatus(flow.flowId).status == FlowState.UP - assert northbound.getFlowHistory(flow.flowId).last().payload.find { + assert flowHelper.getLatestHistoryEntry(flow.flowId).payload.find { it.action == "The flow status was reverted to UP" || it.action == REROUTE_SUCCESS } } @@ -330,12 +330,12 @@ class AutoRerouteSpec extends HealthCheckSpecification { then: "The flow goes to 'Down' status" wait(rerouteDelay + WAIT_OFFSET) { assert northboundV2.getFlowStatus(flow.flowId).status == FlowState.DOWN - assert northbound.getFlowHistory(flow.flowId).last().payload.find { it.action == REROUTE_FAIL } + assert flowHelper.getLatestHistoryEntry(flow.flowId).payload.find { it.action == REROUTE_FAIL } } wait(WAIT_OFFSET) { def prevHistorySize = northbound.getFlowHistory(flow.flowId) .findAll { !(it.details =~ /Reason: ISL .* status become ACTIVE/) }.size() - Wrappers.timedLoop(4) { + timedLoop(4) { //history size should no longer change for the flow, all retries should give up def newHistorySize = northbound.getFlowHistory(flow.flowId) .findAll { !(it.details =~ /Reason: ISL .* status become ACTIVE/) }.size() @@ -526,10 +526,10 @@ class AutoRerouteSpec extends HealthCheckSpecification { isSwDeactivated = false then: "System doesn't try to reroute the flow on the switchUp event because flow is already in UP state" - Wrappers.timedLoop(rerouteDelay + WAIT_OFFSET / 2) { - assert northbound.getFlowHistory(flow.flowId).findAll { - !(it.details =~ /Reason: ISL .* status become ACTIVE/) && //exclude ISL up reasons from parallel streams - it.action == REROUTE_ACTION }.empty + timedLoop(rerouteDelay + WAIT_OFFSET / 2) { + assert flowHelper.getHistoryEntriesByAction(flow.flowId, REROUTE_ACTION).findAll { + !(it.details =~ /Reason: ISL .* status become ACTIVE/) + }.isEmpty() } cleanup: @@ -577,10 +577,10 @@ class AutoRerouteSpec extends HealthCheckSpecification { def blockData = switchHelper.knockoutSwitch(switchToManipulate, RW) def isSwitchActivated = false wait(WAIT_OFFSET) { - def prevHistorySize = northbound.getFlowHistory(flow.flowId).size() - Wrappers.timedLoop(4) { + def prevHistorySize = flowHelper.getHistorySize(flow.flowId) + timedLoop(4) { //history size should no longer change for the flow, all retries should give up - def newHistorySize = northbound.getFlowHistory(flow.flowId).size() + def newHistorySize = flowHelper.getHistorySize(flow.flowId) assert newHistorySize == prevHistorySize assert northbound.getFlowStatus(flow.flowId).status == FlowState.DOWN sleep(500) @@ -666,18 +666,17 @@ triggering one more reroute of the current path" lockKeeper.shapeSwitchesTraffic([swPair.dst], new TrafficControlData(1000)) //break the second ISL when the first reroute has started and is in progress wait(WAIT_OFFSET) { - assert northbound.getFlowHistory(flow.flowId).findAll { it.action == REROUTE_ACTION }.size() == 1 + assert flowHelper.getHistoryEntriesByAction(flow.flowId, REROUTE_ACTION).size() == 1 } antiflap.portDown(commonIsl.srcSwitch.dpId, commonIsl.srcPort) TimeUnit.SECONDS.sleep(rerouteDelay) //first reroute should not be finished at this point, otherwise increase the latency to switches assert ![REROUTE_SUCCESS, REROUTE_FAIL].contains( - northbound.getFlowHistory(flow.flowId).find { it.action == REROUTE_ACTION }.payload.last().action) + flowHelper.getEarliestHistoryEntryByAction(flow.flowId, REROUTE_ACTION).payload.last().action) then: "System reroutes the flow twice and flow ends up in UP state" wait(PATH_INSTALLATION_TIME * 2) { - def history = northbound.getFlowHistory(flow.flowId) - def reroutes = history.findAll { it.action == REROUTE_ACTION } + def reroutes = flowHelper.getHistoryEntriesByAction(flow.getFlowId(), REROUTE_ACTION) assert reroutes.size() == 2 //reroute queue, second reroute starts right after first is finished reroutes.each { assert it.payload.last().action == REROUTE_SUCCESS } assert northboundV2.getFlowStatus(flow.flowId).status == FlowState.UP @@ -690,7 +689,7 @@ triggering one more reroute of the current path" } and: "Flow is pingable" - Wrappers.retry(3, 0) { //Was unstable on Jenkins builds. Fresh env problem? + retry(3, 0) { //Was unstable on Jenkins builds. Fresh env problem? with(northbound.pingFlow(flow.flowId, new PingInput())) { it.forward.pingSuccess it.reverse.pingSuccess @@ -828,11 +827,11 @@ class AutoRerouteIsolatedSpec extends HealthCheckSpecification { then: "System tries to reroute a flow with transit switch" def flowPathMap = [(firstFlow.flowId): firstFlowMainPath, (secondFlow.flowId): secondFlowPath] wait(WAIT_OFFSET * 2) { - def firstFlowHistory = northbound.getFlowHistory(firstFlow.flowId).findAll { it.action == REROUTE_ACTION } + def firstFlowHistory = flowHelper.getHistoryEntriesByAction(firstFlow.flowId, REROUTE_ACTION) assert firstFlowHistory.last().payload.find { it.action == REROUTE_FAIL } //check that system doesn't retry to reroute the firstFlow (its src is down, no need to retry) assert !firstFlowHistory.find { it.taskId =~ /.+ : retry #1/ } - def secondFlowHistory = northbound.getFlowHistory(secondFlow.flowId).findAll { it.action == REROUTE_ACTION } + def secondFlowHistory = flowHelper.getHistoryEntriesByAction(secondFlow.flowId, REROUTE_ACTION) /*there should be original reroute + 3 retries or original reroute + 2 retries (sometimes the system does not try to retry reroute for linkDown event, because the system gets 'ISL timeout' event for other ISLs) @@ -858,10 +857,10 @@ class AutoRerouteIsolatedSpec extends HealthCheckSpecification { nonRtIsls.forEach { assert islUtils.getIslInfo(allLinks, it).get().state == FAILED } } wait(WAIT_OFFSET) { - def prevHistorySizes = [firstFlow.flowId, secondFlow.flowId].collect { northbound.getFlowHistory(it).size() } - Wrappers.timedLoop(4) { + def prevHistorySizes = [firstFlow.flowId, secondFlow.flowId].collect { flowHelper.getHistorySize(it) } + timedLoop(4) { //history size should no longer change for both flows, all retries should give up - def newHistorySizes = [firstFlow.flowId, secondFlow.flowId].collect { northbound.getFlowHistory(it).size() } + def newHistorySizes = [firstFlow.flowId, secondFlow.flowId].collect { flowHelper.getHistorySize(it) } assert newHistorySizes == prevHistorySizes withPool { [firstFlow.flowId, secondFlow.flowId].eachParallel { String flowId -> @@ -887,9 +886,8 @@ Failed to find path with requested bandwidth= ignored" and don't check that flow is UP */ wait(WAIT_OFFSET) { [firstFlow, secondFlow].each { - assert northbound.getFlowHistory(it.flowId).find { - it.action == REROUTE_ACTION && - it.details == "Reason: Switch '$switchPair1.src.dpId' online" + assert flowHelper.getHistoryEntriesByAction(it.flowId, REROUTE_ACTION).find { + it.details == "Reason: Switch '$switchPair1.src.dpId' online" } } } @@ -973,8 +971,8 @@ Failed to find path with requested bandwidth= ignored" and: "Flow remains DEGRADED and on the same path" wait(rerouteDelay + WAIT_OFFSET) { - assert northbound.getFlowHistory(flow.flowId).findAll { - it.action == REROUTE_ACTION && it.details == "Reason: initiated via Northbound" + assert flowHelper.getHistoryEntriesByAction(flow.flowId, REROUTE_ACTION).findAll { + it.details == "Reason: initiated via Northbound" }.size() == 2 //reroute + retry assert northboundV2.getFlowStatus(flow.flowId).status == FlowState.DEGRADED } @@ -992,8 +990,8 @@ Failed to find path with requested bandwidth= ignored" then: "System tries to reroute the DEGRADED flow" and: "Flow remains DEGRADED and on the same path" wait(rerouteDelay + WAIT_OFFSET) { - assert northbound.getFlowHistory(flow.flowId).findAll { - it.action == REROUTE_ACTION && it.details.contains("status become ACTIVE") + assert flowHelper.getHistoryEntriesByAction(flow.flowId, REROUTE_ACTION).findAll { + it.details.contains("status become ACTIVE") }.size() == 2 //reroute + retry assert northboundV2.getFlowStatus(flow.flowId).status == FlowState.DEGRADED } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/BandwidthSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/BandwidthSpec.groovy index 7bd112e40c7..9e4fc0c07e9 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/BandwidthSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/BandwidthSpec.groovy @@ -298,9 +298,8 @@ class BandwidthSpec extends HealthCheckSpecification { } } - def flow = flowHelper.randomFlow(switchPair) - flow.maximumBandwidth = involvedBandwidths.max() + 1 - northbound.addFlow(flow) + def flow = flowHelper.randomFlow(switchPair).tap {it.maximumBandwidth = involvedBandwidths.max() + 1} + flowHelper.addFlow(flow) then: "The flow is not created because flow path should not be found" def exc = thrown(HttpClientErrorException) diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowAffinitySpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowAffinitySpec.groovy index b2a3fc7050b..72783fd1ce9 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowAffinitySpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowAffinitySpec.groovy @@ -40,7 +40,7 @@ class FlowAffinitySpec extends HealthCheckSpecification { and: "Flows' histories contain 'affinityGroupId' information" [flow2, flow3].each {//flow1 had no affinity at the time of creation - assert northbound.getFlowHistory(it.flowId).find { it.action == CREATE_ACTION }.dumps + assert flowHelper.getEarliestHistoryEntryByAction(it.flowId, CREATE_ACTION).dumps .find { it.type == "stateAfter" }?.affinityGroupId == flow1.flowId } @@ -49,14 +49,14 @@ class FlowAffinitySpec extends HealthCheckSpecification { def flowsAreDeleted = true then: "Flow1 history contains 'affinityGroupId' information in 'delete' operation" - verifyAll(northbound.getFlowHistory(flow1.flowId).find { it.action == DELETE_ACTION }.dumps) { + verifyAll(flowHelper.getEarliestHistoryEntryByAction(flow1.flowId, DELETE_ACTION).dumps) { it.find { it.type == "stateBefore" }?.affinityGroupId !it.find { it.type == "stateAfter" }?.affinityGroupId } and: "Flow2 and flow3 histories does not have 'affinityGroupId' in 'delete' because it's gone after deletion of 'main' flow1" [ flow2, flow3].each { - verifyAll(northbound.getFlowHistory(it.flowId).find { it.action == DELETE_ACTION }.dumps) { + verifyAll(flowHelper.getEarliestHistoryEntryByAction(it.flowId, DELETE_ACTION).dumps) { !it.find { it.type == "stateBefore" }?.affinityGroupId !it.find { it.type == "stateAfter" }?.affinityGroupId } @@ -197,14 +197,14 @@ class FlowAffinitySpec extends HealthCheckSpecification { when: "Create affinity flow on the same switch pair" def affinityFlow2 = flowHelperV2.randomFlow(swPair, false, [flow1, affinityFlow]).tap { affinityFlowId = flow1.flowId; diverseFlowId = affinityFlow.flowId } - northboundV2.addFlow(affinityFlow2) + flowHelperV2.addFlow(affinityFlow2) then: "Error is returned" def e = thrown(HttpClientErrorException) expectedError.matches(e) when: "Create affinity flow on the same switch pair" def affinityFlow3 = flowHelperV2.randomFlow(swPair, false, [flow1, affinityFlow]).tap { affinityFlowId = affinityFlow.flowId; diverseFlowId = flow1.flowId } - northboundV2.addFlow(affinityFlow3) + flowHelperV2.addFlow(affinityFlow3) then: "Error is returned" def e2 = thrown(HttpClientErrorException) @@ -230,7 +230,7 @@ class FlowAffinitySpec extends HealthCheckSpecification { when: "Create an affinity flow that targets the diversity flow" def affinityFlow2 = flowHelperV2.randomFlow(swPair, false, [flow1, diverseFlow]).tap { affinityFlowId = flow1.flowId; diverseFlowId = flow2.flowId } - northboundV2.addFlow(affinityFlow2) + flowHelperV2.addFlow(affinityFlow2) then: "Error is returned" def e = thrown(HttpClientErrorException) @@ -261,7 +261,7 @@ class FlowAffinitySpec extends HealthCheckSpecification { } and: "Affinity flow history contain 'affinityGroupId' information" - assert northbound.getFlowHistory(affinityFlow.flowId).find { it.action == CREATE_ACTION }.dumps + assert flowHelper.getEarliestHistoryEntryByAction(affinityFlow.flowId, CREATE_ACTION).dumps .find { it.type == "stateAfter" }?.affinityGroupId == oneSwitchFlow.flowId cleanup: @@ -273,7 +273,7 @@ class FlowAffinitySpec extends HealthCheckSpecification { when: "Create an affinity flow that targets non-existing flow" def swPair = topologyHelper.getSwitchPairs()[0] def flow = flowHelperV2.randomFlow(swPair).tap { diverseFlowId = NON_EXISTENT_FLOW_ID } - northboundV2.addFlow(flow) + flowHelperV2.addFlow(flow) then: "Error is returned" def e = thrown(HttpClientErrorException) diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowCrudSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowCrudSpec.groovy index c8a857ce46c..b4554c04336 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowCrudSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowCrudSpec.groovy @@ -25,6 +25,8 @@ import static org.openkilda.functionaltests.helpers.Wrappers.timedLoop import static org.openkilda.messaging.info.event.IslChangeType.DISCOVERED import static org.openkilda.messaging.info.event.IslChangeType.FAILED import static org.openkilda.messaging.info.event.IslChangeType.MOVED +import static org.openkilda.messaging.payload.flow.FlowState.IN_PROGRESS +import static org.openkilda.messaging.payload.flow.FlowState.UP import static org.openkilda.testing.Constants.PATH_INSTALLATION_TIME import static org.openkilda.testing.Constants.RULES_DELETION_TIME import static org.openkilda.testing.Constants.RULES_INSTALLATION_TIME @@ -41,7 +43,6 @@ import org.openkilda.messaging.info.event.PathNode import org.openkilda.messaging.payload.flow.DetectConnectedDevicesPayload import org.openkilda.messaging.payload.flow.FlowEndpointPayload import org.openkilda.messaging.payload.flow.FlowPayload -import org.openkilda.messaging.payload.flow.FlowState import org.openkilda.model.FlowEncapsulationType import org.openkilda.model.SwitchId import org.openkilda.model.cookie.Cookie @@ -480,7 +481,7 @@ class FlowCrudSpec extends HealthCheckSpecification { } when: "Try building a flow using the isolated switch" - northboundV2.addFlow(flow) + flowHelperV2.addFlow(flow) then: "Error is returned, stating that there is no path found for such flow" def error = thrown(HttpClientErrorException) @@ -528,7 +529,7 @@ class FlowCrudSpec extends HealthCheckSpecification { def switches = pathHelper.getInvolvedSwitches(paths.min { pathHelper.getCost(it) }) when: "Init creation of a new flow" - northboundV2.addFlow(flow) + flowHelperV2.addFlow(flow, IN_PROGRESS) and: "Immediately remove the flow" northboundV2.deleteFlow(flow.flowId) @@ -541,7 +542,7 @@ class FlowCrudSpec extends HealthCheckSpecification { and: "Flow eventually gets into UP state" wait(WAIT_OFFSET) { - assert northboundV2.getFlowStatus(flow.flowId).status == FlowState.UP + assert northboundV2.getFlowStatus(flow.flowId).status == UP } @@ -562,7 +563,7 @@ class FlowCrudSpec extends HealthCheckSpecification { } cleanup: "Remove the flow" - Wrappers.silent{flow && flowHelperV2.deleteFlow(flow.flowId)} + flow && flowHelperV2.deleteFlow(flow.flowId) } def "Unable to create a flow with #problem"() { @@ -762,7 +763,7 @@ Failed to find path with requested bandwidth=${IMPOSSIBLY_HIGH_BANDWIDTH}/) when: "Try to create a one-switch flow on a deactivated switch" def flow = flowHelperV2.singleSwitchFlow(sw) - northboundV2.addFlow(flow) + flowHelperV2.addFlow(flow) then: "Human readable error is returned" def exc = thrown(HttpClientErrorException) @@ -916,12 +917,12 @@ types .* or update switch properties and add needed encapsulation type./).matche def longPath = swPair.paths.max { it.size() } swPair.paths.findAll { it != longPath }.each { pathHelper.makePathMorePreferable(longPath, it) } def flow = flowHelperV2.randomFlow(swPair) - northboundV2.addFlow(flow) + flowHelperV2.addFlow(flow, IN_PROGRESS) then: "Flow status is changed to UP only when all rules are actually installed" - northboundV2.getFlowStatus(flow.flowId).status == FlowState.IN_PROGRESS + northboundV2.getFlowStatus(flow.flowId).status == IN_PROGRESS wait(PATH_INSTALLATION_TIME) { - assert northboundV2.getFlowStatus(flow.flowId).status == FlowState.UP + assert northboundV2.getFlowStatus(flow.flowId).status == UP } def flowInfo = database.getFlow(flow.flowId) def flowCookies = [flowInfo.forwardPath.cookie.value, flowInfo.reversePath.cookie.value] @@ -982,12 +983,12 @@ types .* or update switch properties and add needed encapsulation type./).matche } and: "Flow history shows actual info into stateBefore and stateAfter sections" - def flowHistory = northbound.getFlowHistory(flow.flowId) - with(flowHistory.last().dumps.find { it.type == "stateBefore" }) { + def flowHistoryEntry = flowHelper.getLatestHistoryEntry(flow.flowId) + with(flowHistoryEntry.dumps.find { it.type == "stateBefore" }) { it.sourcePort == flow.source.portNumber it.sourceVlan == flow.source.vlanId } - with(flowHistory.last().dumps.find { it.type == "stateAfter" }) { + with(flowHistoryEntry.dumps.find { it.type == "stateAfter" }) { it.sourcePort == updatedFlow.source.portNumber it.sourceVlan == updatedFlow.source.vlanId } @@ -1036,11 +1037,11 @@ types .* or update switch properties and add needed encapsulation type./).matche } and: "Flow history shows actual info into stateBefore and stateAfter sections" - def flowHistory2 = northbound.getFlowHistory(flow.flowId) - with(flowHistory2.last().dumps.find { it.type == "stateBefore" }) { + def flowHistory2 = flowHelper.getLatestHistoryEntry(flow.flowId) + with(flowHistory2.dumps.find { it.type == "stateBefore" }) { it.destinationSwitch == dstSwitch.dpId.toString() } - with(flowHistory2.last().dumps.find { it.type == "stateAfter" }) { + with(flowHistory2.dumps.find { it.type == "stateAfter" }) { it.destinationSwitch == newDstSwitch.dpId.toString() } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowCrudV1Spec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowCrudV1Spec.groovy index c4db60fe58b..549c9b16ade 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowCrudV1Spec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowCrudV1Spec.groovy @@ -378,7 +378,7 @@ class FlowCrudV1Spec extends HealthCheckSpecification { flow.destination.vlanId = flow.source.vlanId when: "Try creating such flow" - northbound.addFlow(flow) + flowHelper.addFlow(flow) then: "Error is returned, stating a readable reason" def error = thrown(HttpClientErrorException) @@ -403,7 +403,7 @@ class FlowCrudV1Spec extends HealthCheckSpecification { flowHelper.addFlow(flow) and: "Try creating the second flow which conflicts" - northbound.addFlow(conflictingFlow) + flowHelper.addFlow(conflictingFlow) then: "Error is returned, stating a readable reason of conflict" def error = thrown(HttpClientErrorException) @@ -508,7 +508,7 @@ class FlowCrudV1Spec extends HealthCheckSpecification { } when: "Try building a flow using the isolated switch" - northbound.addFlow(flow) + flowHelper.addFlow(flow) then: "Error is returned, stating that there is no path found for such flow" def error = thrown(HttpClientErrorException) @@ -789,7 +789,7 @@ Failed to find path with requested bandwidth=$flow.maximumBandwidth/).matches(er when: "Create a flow" def flow = flowHelper.singleSwitchFlow(sw) - northbound.addFlow(flow) + flowHelper.addFlow(flow) then: "Human readable error is returned" def exc = thrown(HttpClientErrorException) @@ -832,7 +832,7 @@ are not connected to the controller/).matches(exc) def amountOfFlows = 5 amountOfFlows.times { def flow = flowHelper.singleSwitchFlow(sw) - northbound.addFlow(flow) + flowHelper.addFlow(flow) flows << flow.id } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowDiversitySpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowDiversitySpec.groovy index 6300a9321cf..162e954812d 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowDiversitySpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowDiversitySpec.groovy @@ -75,7 +75,7 @@ class FlowDiversitySpec extends HealthCheckSpecification { and: "Flows' histories contain 'diverseGroupId' information" [flow2, flow3].each {//flow1 had no diversity at the time of creation - assert northbound.getFlowHistory(it.flowId).find { it.action == CREATE_ACTION }.dumps + assert flowHelper.getEarliestHistoryEntryByAction(it.flowId, CREATE_ACTION).dumps .find { it.type == "stateAfter" }?.diverseGroupId } @@ -85,13 +85,13 @@ class FlowDiversitySpec extends HealthCheckSpecification { then: "Flows' histories contain 'diverseGroupId' information in 'delete' operation" [flow1, flow2].each { - verifyAll(northbound.getFlowHistory(it.flowId).find { it.action == DELETE_ACTION }.dumps) { + verifyAll(flowHelper.getEarliestHistoryEntryByAction(it.flowId, DELETE_ACTION).dumps) { it.find { it.type == "stateBefore" }?.diverseGroupId !it.find { it.type == "stateAfter" }?.diverseGroupId } } //except flow3, because after deletion of flow1/flow2 flow3 is no longer in the diversity group - verifyAll(northbound.getFlowHistory(flow3.flowId).find { it.action == DELETE_ACTION }.dumps) { + verifyAll(flowHelper.getEarliestHistoryEntryByAction(flow3.flowId, DELETE_ACTION).dumps) { !it.find { it.type == "stateBefore" }?.diverseGroupId !it.find { it.type == "stateAfter" }?.diverseGroupId } @@ -120,7 +120,7 @@ class FlowDiversitySpec extends HealthCheckSpecification { flow2.tap { it.diverseFlowId = flow1.flowId }) and: "Second flow's history contains 'groupId' information" - verifyAll(northbound.getFlowHistory(flow2.flowId).find { it.action == UPDATE_ACTION }.dumps) { + verifyAll(flowHelper.getEarliestHistoryEntryByAction(flow2.flowId, UPDATE_ACTION).dumps) { !it.find { it.type == "stateBefore" }?.diverseGroupId it.find { it.type == "stateAfter" }?.diverseGroupId } @@ -186,7 +186,7 @@ class FlowDiversitySpec extends HealthCheckSpecification { !northboundV2.getFlow(flow2.flowId).diverseWith and: "The flow's history reflects the change of 'groupId' field" - verifyAll(northbound.getFlowHistory(flow2.flowId).find { it.action == UPDATE_ACTION }.dumps) { + verifyAll(flowHelper.getEarliestHistoryEntryByAction(flow2.flowId, UPDATE_ACTION).dumps) { //https://github.com/telstra/open-kilda/issues/3807 // it.find { it.type == "stateBefore" }.groupId !it.find { it.type == "stateAfter" }.diverseGroupId diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowHistorySpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowHistorySpec.groovy index 3d882d44ea1..3fa92757e4d 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowHistorySpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowHistorySpec.groovy @@ -371,7 +371,7 @@ class FlowHistorySpec extends HealthCheckSpecification { and: "The root cause('Switch is not active') is registered in flow history" Wrappers.wait(WAIT_OFFSET) { - def flowHistory = northbound.getFlowHistory(flow.flowId).find { it.action == REROUTE_ACTION } + def flowHistory = flowHelper.getEarliestHistoryEntryByAction(flow.flowId, REROUTE_ACTION) assert flowHistory.payload[0].action == "Started flow validation" assert flowHistory.payload[1].action == "ValidateFlowAction failed: Flow's $flow.flowId src switch is not active" assert flowHistory.payload[2].action == REROUTE_FAIL diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowLoopSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowLoopSpec.groovy index 2384d0fd44f..10eef1c139d 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowLoopSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowLoopSpec.groovy @@ -261,7 +261,7 @@ class FlowLoopSpec extends HealthCheckSpecification { and: "Flow history contains info about flowLoop" Wrappers.wait(WAIT_OFFSET / 2) { - def flowHistory = northbound.getFlowHistory(flow.flowId).last() + def flowHistory = flowHelper.getLatestHistoryEntry(flow.flowId) assert !flowHistory.dumps.find { it.type == "stateBefore" }.loopSwitchId assert flowHistory.dumps.find { it.type == "stateAfter" }.loopSwitchId == switchPair.dst.dpId } @@ -463,7 +463,7 @@ class FlowLoopSpec extends HealthCheckSpecification { northboundV2.createFlowLoop(flow.flowId, new FlowLoopPayload(switchPair.src.dpId)) Wrappers.wait(WAIT_OFFSET) { assert northboundV2.getFlowStatus(flow.flowId).status == FlowState.UP - assert northbound.getFlowHistory(flow.flowId).last().payload.last().action == UPDATE_SUCCESS + assert flowHelper.getLatestHistoryEntry(flow.flowId).payload.last().action == UPDATE_SUCCESS } when: "Break ISL on the main path (bring port down) to init auto swap" diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowMonitoringSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowMonitoringSpec.groovy index 22c49429728..b3586ca5182 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowMonitoringSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/FlowMonitoringSpec.groovy @@ -122,7 +122,7 @@ class FlowMonitoringSpec extends HealthCheckSpecification { * and then reroute the flow. */ wait(flowSlaCheckIntervalSeconds * 2 + flowLatencySlaTimeoutSeconds + WAIT_OFFSET) { - def history = northbound.getFlowHistory(flow.flowId).last() + def history = flowHelper.getLatestHistoryEntry(flow.flowId) // Flow sync or flow reroute with reason "Flow latency become unhealthy" assert history.getAction() == "Flow paths sync" || (history.details.contains("healthy") && @@ -177,7 +177,7 @@ and flowLatencyMonitoringReactions is disabled in featureToggle"() { then: "Flow is not rerouted because flowLatencyMonitoringReactions is disabled in featureToggle" sleep((flowSlaCheckIntervalSeconds * 2 + flowLatencySlaTimeoutSeconds) * 1000) - northbound.getFlowHistory(flow.flowId).findAll { it.action == REROUTE_ACTION }.empty + flowHelper.getHistoryEntriesByAction(flow.flowId, REROUTE_ACTION).isEmpty() and: "Flow path is not changed" pathHelper.convert(northbound.getFlowPath(flow.flowId)) == mainPath diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/MaxLatencySpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/MaxLatencySpec.groovy index fa265219506..9ee6f361e68 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/MaxLatencySpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/MaxLatencySpec.groovy @@ -20,6 +20,7 @@ import static org.openkilda.functionaltests.extension.tags.Tag.LOW_PRIORITY import static org.openkilda.functionaltests.helpers.FlowHistoryConstants.REROUTE_SUCCESS import static org.openkilda.functionaltests.helpers.Wrappers.wait import static org.openkilda.messaging.info.event.IslChangeType.DISCOVERED +import static org.openkilda.messaging.payload.flow.FlowState.DEGRADED import static org.openkilda.testing.Constants.WAIT_OFFSET @See(["https://github.com/telstra/open-kilda/blob/develop/docs/design/pce/design.md", @@ -128,17 +129,10 @@ class MaxLatencySpec extends HealthCheckSpecification { maxLatencyTier2 = 16 // maxLatency < pathLatency < maxLatencyTier2 pathComputationStrategy = PathComputationStrategy.MAX_LATENCY.toString() } - northboundV2.addFlow(flow) + flowHelperV2.addFlow(flow, DEGRADED) then: "Flow is created, main path is the 10 latency path, protected is 15 latency" and: "Flow goes to DEGRADED state" - wait(WAIT_OFFSET) { - def flowInfo = northboundV2.getFlow(flow.flowId) - assert flowInfo.status == FlowState.DEGRADED.toString() - assert flowInfo.statusDetails.mainPath == "Up" - assert flowInfo.statusDetails.protectedPath == "degraded" - assert flowInfo.statusInfo == StatusInfo.BACK_UP_STRATEGY_USED - } def path = northbound.getFlowPath(flow.flowId) pathHelper.convert(path) == mainPath pathHelper.convert(path.protectedPath) == alternativePath @@ -159,15 +153,9 @@ class MaxLatencySpec extends HealthCheckSpecification { maxLatencyTier2 = 16 pathComputationStrategy = PathComputationStrategy.MAX_LATENCY.toString() } - northboundV2.addFlow(flow) + flowHelperV2.addFlow(flow, DEGRADED) then: "Flow is created, flow path is the 15 latency path" - wait(WAIT_OFFSET) { - def flowInfo = northboundV2.getFlow(flow.flowId) - assert flowInfo.status == FlowState.DEGRADED.toString() - assert flowInfo.statusInfo == StatusInfo.BACK_UP_STRATEGY_USED - assert northboundV2.getFlowHistoryStatuses(flow.flowId).historyStatuses*.statusBecome == ["DEGRADED"] - } pathHelper.convert(northbound.getFlowPath(flow.flowId)) == alternativePath cleanup: @@ -198,7 +186,7 @@ class MaxLatencySpec extends HealthCheckSpecification { wait(WAIT_OFFSET) { def flowInfo = northboundV2.getFlow(flow.flowId) assert flowInfo.maxLatency == newMaxLatency - assert flowInfo.status == FlowState.DEGRADED.toString() + assert flowInfo.status == DEGRADED.toString() assert flowInfo.statusInfo == StatusInfo.BACK_UP_STRATEGY_USED /*[0..1] - can be more than two statuses due to running this test in a parallel mode. for example: reroute can be triggered by blinking/activating any isl (not involved in flow path)*/ @@ -230,12 +218,12 @@ class MaxLatencySpec extends HealthCheckSpecification { then: "Flow is rerouted and goes to the DEGRADED state" wait(rerouteDelay + WAIT_OFFSET) { - def flowHistory = northbound.getFlowHistory(flow.flowId).last() + def flowHistory = flowHelper.getLatestHistoryEntry(flow.flowId) flowHistory.payload.last().action == REROUTE_SUCCESS // https://github.com/telstra/open-kilda/issues/4049 flowHistory.payload.last().details == "Flow reroute completed with status DEGRADED and error: The primary path status is DEGRADED" def flowInfo = northboundV2.getFlow(flow.flowId) - assert flowInfo.status == FlowState.DEGRADED.toString() + assert flowInfo.status == DEGRADED.toString() assert flowInfo.statusInfo == StatusInfo.BACK_UP_STRATEGY_USED } pathHelper.convert(northbound.getFlowPath(flow.flowId)) == alternativePath @@ -260,15 +248,10 @@ class MaxLatencySpec extends HealthCheckSpecification { maxLatencyTier2 = 12 pathComputationStrategy = PathComputationStrategy.LATENCY.toString() } - northboundV2.addFlow(flow) + flowHelperV2.addFlow(flow, DEGRADED) then: "Flow is created in DEGRADED state because flowPath doesn't satisfy max_latency value \ but satisfies max_latency_tier2" - wait(WAIT_OFFSET) { - def flowInfo = northboundV2.getFlow(flow.flowId) - assert flowInfo.status == FlowState.DEGRADED.toString() - assert flowInfo.statusInfo == StatusInfo.BACK_UP_STRATEGY_USED - } def path = northbound.getFlowPath(flow.flowId) pathHelper.convert(path) == mainPath @@ -288,10 +271,9 @@ but satisfies max_latency_tier2" maxLatencyTier2 = 12 pathComputationStrategy = PathComputationStrategy.LATENCY.toString() } - northboundV2.addFlow(flow) + flowHelperV2.addFlow(flow) then: "Flow is created in UP" - wait(WAIT_OFFSET) { assert northboundV2.getFlowStatus(flow.flowId).status == FlowState.UP } def path = northbound.getFlowPath(flow.flowId) pathHelper.convert(path) == mainPath @@ -311,7 +293,7 @@ but satisfies max_latency_tier2" maxLatencyTier2 = 11 pathComputationStrategy = PathComputationStrategy.LATENCY.toString() } - northboundV2.addFlow(flow) + flowHelperV2.addFlow(flow) then: "Flow is not created, human readable error is returned" def e = thrown(HttpClientErrorException) diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/MirrorEndpointsSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/MirrorEndpointsSpec.groovy index a3ae8fdfcc3..2a468869287 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/MirrorEndpointsSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/MirrorEndpointsSpec.groovy @@ -8,7 +8,6 @@ import static org.openkilda.functionaltests.extension.tags.Tag.LOW_PRIORITY import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE_SWITCHES import static org.openkilda.functionaltests.extension.tags.Tag.TOPOLOGY_DEPENDENT -import static org.openkilda.functionaltests.model.stats.Direction.FORWARD import static org.openkilda.functionaltests.model.stats.FlowStatsMetric.FLOW_RAW_BYTES import static org.openkilda.testing.Constants.WAIT_OFFSET import static spock.util.matcher.HamcrestSupport.expect @@ -102,7 +101,7 @@ class MirrorEndpointsSpec extends HealthCheckSpecification { and: "Flow history reports a successful mirror creation" Wrappers.wait(WAIT_OFFSET) { - with(northbound.getFlowHistory(flow.flowId).last()) { + with(flowHelper.getLatestHistoryEntry(flow.getFlowId())) { action == FlowHistoryConstants.CREATE_MIRROR_ACTION it.payload.last().action == FlowHistoryConstants.CREATE_MIRROR_SUCCESS } @@ -183,7 +182,7 @@ class MirrorEndpointsSpec extends HealthCheckSpecification { then: "'Mirror point delete' operation is present in flow history" Wrappers.wait(WAIT_OFFSET) { - with(northbound.getFlowHistory(flow.flowId).last()) { + with(flowHelper.getLatestHistoryEntry(flow.flowId)) { action == FlowHistoryConstants.DELETE_MIRROR_ACTION payload.last().action == FlowHistoryConstants.DELETE_MIRROR_SUCCESS } @@ -873,7 +872,7 @@ with these parameters./) it.destination.portNumber = mirrorPoint.sinkEndpoint.portNumber it.destination.vlanId = mirrorPoint.sinkEndpoint.vlanId } - northboundV2.addFlow(otherFlow) + flowHelperV2.addFlow(otherFlow) then: "Error is returned, cannot create flow that conflicts with mirror point" def error = thrown(HttpClientErrorException) diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/ProtectedPathSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/ProtectedPathSpec.groovy index 815f1b40087..b1288f251dd 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/ProtectedPathSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/ProtectedPathSpec.groovy @@ -940,7 +940,7 @@ doesn't have links with enough bandwidth, Failed to find path with requested ban status == FlowState.DEGRADED.toString() statusInfo == StatusInfo.OVERLAPPING_PROTECTED_PATH } - assert northbound.getFlowHistory(flow.flowId).last().payload.find { it.action == REROUTE_FAIL } + assert flowHelper.getLatestHistoryEntry(flow.flowId).payload.find { it.action == REROUTE_FAIL } assert northboundV2.getFlowHistoryStatuses(flow.flowId, 1).historyStatuses*.statusBecome == ["DEGRADED"] } @@ -1323,8 +1323,8 @@ doesn't have links with enough bandwidth, Failed to find path with requested ban then: "Flow state is changed to DOWN" Wrappers.wait(WAIT_OFFSET) { assert northboundV2.getFlowStatus(flow.flowId).status == FlowState.DOWN - assert northbound.getFlowHistory(flow.flowId).find { - it.action == REROUTE_ACTION && it.taskId =~ (/.+ : retry #1 ignore_bw true/) + assert flowHelper.getHistoryEntriesByAction(flow.flowId, REROUTE_ACTION).find{ + it.taskId =~ (/.+ : retry #1 ignore_bw true/) }?.payload?.last()?.action == REROUTE_FAIL } verifyAll(northboundV2.getFlow(flow.flowId).statusDetails) { diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/ProtectedPathV1Spec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/ProtectedPathV1Spec.groovy index b682cfb22f2..05eecfb30c4 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/ProtectedPathV1Spec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/ProtectedPathV1Spec.groovy @@ -514,8 +514,8 @@ class ProtectedPathV1Spec extends HealthCheckSpecification { then: "Flow state is changed to DOWN" Wrappers.wait(WAIT_OFFSET) { assert northbound.getFlowStatus(flow.id).status == FlowState.DOWN - assert northbound.getFlowHistory(flow.id).find { - it.action == REROUTE_ACTION && it.taskId =~ (/.+ : retry #1 ignore_bw true/) + assert flowHelper.getHistoryEntriesByAction(flow.id, REROUTE_ACTION).find { + it.taskId =~ (/.+ : retry #1 ignore_bw true/) }?.payload?.last()?.action == REROUTE_FAIL } verifyAll(northbound.getFlow(flow.id).flowStatusDetails) { diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/QinQFlowSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/QinQFlowSpec.groovy index da5c04ef8da..4ccb3e086cd 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/QinQFlowSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/QinQFlowSpec.groovy @@ -180,14 +180,14 @@ class QinQFlowSpec extends HealthCheckSpecification { } and: "Flow history shows actual info into stateBefore and stateAfter sections" - def flowHistory = northbound.getFlowHistory(qinqFlow.flowId) - with(flowHistory.last().dumps.find { it.type == "stateBefore" }){ + def flowHistoryEntry = flowHelper.getLatestHistoryEntry(qinqFlow.flowId) + with(flowHistoryEntry.dumps.find { it.type == "stateBefore" }){ it.sourceVlan == srcVlanId it.sourceInnerVlan == srcInnerVlanId it.destinationVlan == dstVlanId it.destinationInnerVlan == dstInnerVlanId } - with(flowHistory.last().dumps.find { it.type == "stateAfter" }){ + with(flowHistoryEntry.dumps.find { it.type == "stateAfter" }){ it.sourceVlan == vlanFlow.source.vlanId it.sourceInnerVlan == vlanFlow.destination.vlanId it.destinationVlan == vlanFlow.destination.vlanId @@ -327,7 +327,7 @@ class QinQFlowSpec extends HealthCheckSpecification { def flow = flowHelperV2.randomFlow(swP) flow.source.innerVlanId = srcInnerVlanId flow.destination.innerVlanId = dstInnerVlanId - northboundV2.addFlow(flow) + flowHelperV2.addFlow(flow) then: "Human readable error is returned" def exc = thrown(HttpClientErrorException) diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/SwapEndpointSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/SwapEndpointSpec.groovy index 5aa3bab50b2..f55a59d2798 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/SwapEndpointSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/SwapEndpointSpec.groovy @@ -1060,8 +1060,8 @@ switches"() { it.state == IslChangeType.FAILED }.size() == broughtDownPorts.size() * 2 assert northbound.getFlowStatus(flow1.id).status == FlowState.DOWN - assert northbound.getFlowHistory(flow1.id).find { - it.action == REROUTE_ACTION && it.taskId =~ (/.+ : retry #1 ignore_bw true/) + assert flowHelper.getHistoryEntriesByAction(flow1.id, REROUTE_ACTION).find { + it.taskId =~ (/.+ : retry #1 ignore_bw true/) }?.payload?.last()?.action == REROUTE_FAIL } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/ThrottlingRerouteSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/ThrottlingRerouteSpec.groovy index 2c9c439ee32..e3f3f9b7a59 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/ThrottlingRerouteSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/ThrottlingRerouteSpec.groovy @@ -69,15 +69,15 @@ class ThrottlingRerouteSpec extends HealthCheckSpecification { then: "The oldest broken flow is still not rerouted before rerouteDelay run out" sleep(untilReroutesBegin() - (long) (rerouteDelay * 1000 * 0.5)) //check after 50% of rerouteDelay has passed - northbound.getFlowHistory(flows.first().flowId).last().action == "Flow creating" //reroute didn't start yet + flowHelper.getLatestHistoryEntry(flows.first().flowId).action == "Flow creating" //reroute didn't start yet and: "The oldest broken flow is rerouted when the rerouteDelay runs out" def waitTime = untilReroutesBegin() / 1000.0 + PATH_INSTALLATION_TIME * 2 Wrappers.wait(waitTime) { //Flow should go DOWN or change path on reroute. In our case it doesn't matter which of these happen. assert (northboundV2.getFlowStatus(flows.first().flowId).status == FlowState.DOWN && - northbound.getFlowHistory(flows.first().flowId).find { - it.action == REROUTE_ACTION && it.taskId =~ (/.+ : retry #1/) })|| + flowHelper.getHistoryEntriesByAction(flows.first().flowId, REROUTE_ACTION).find { + it.taskId =~ (/.+ : retry #1/) })|| northbound.getFlowPath(flows.first().flowId) != flowPaths.first() } @@ -85,8 +85,8 @@ class ThrottlingRerouteSpec extends HealthCheckSpecification { Wrappers.wait(rerouteDelay + WAIT_OFFSET) { flowPaths[1..-1].each { flowPath -> assert (northboundV2.getFlowStatus(flowPath.id).status == FlowState.DOWN && - northbound.getFlowHistory(flowPath.id).find { - it.action == REROUTE_ACTION && it.taskId =~ (/.+ : retry #1/) + flowHelper.getHistoryEntriesByAction(flowPath.id, REROUTE_ACTION).find { + it.taskId =~ (/.+ : retry #1/) }) || (northbound.getFlowPath(flowPath.id) != flowPath && northboundV2.getFlowStatus(flowPath.id).status == FlowState.UP) diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/VxlanFlowSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/VxlanFlowSpec.groovy index 3a573e60055..4b03903570f 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/VxlanFlowSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/VxlanFlowSpec.groovy @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue import static org.openkilda.functionaltests.extension.tags.Tag.LOW_PRIORITY import static org.openkilda.functionaltests.extension.tags.Tag.SMOKE_SWITCHES import static org.openkilda.functionaltests.extension.tags.Tag.TOPOLOGY_DEPENDENT +import static org.openkilda.model.FlowEncapsulationType.VXLAN import static org.openkilda.testing.Constants.PATH_INSTALLATION_TIME import static org.openkilda.testing.Constants.RULES_DELETION_TIME import static org.openkilda.testing.Constants.RULES_INSTALLATION_TIME @@ -68,7 +69,7 @@ class VxlanFlowSpec extends HealthCheckSpecification { flowInfo.encapsulationType == data.encapsulationCreate.toString().toLowerCase() and: "Correct rules are installed" - def vxlanRule = (flowInfo.encapsulationType == FlowEncapsulationType.VXLAN.toString().toLowerCase()) + def vxlanRule = (flowInfo.encapsulationType == VXLAN.toString().toLowerCase()) def flowInfoFromDb = database.getFlow(flow.flowId) // ingressRule should contain "pushVxlan" // egressRule should contain "tunnel-id" @@ -191,10 +192,10 @@ class VxlanFlowSpec extends HealthCheckSpecification { [ [ encapsulationCreate: FlowEncapsulationType.TRANSIT_VLAN, - encapsulationUpdate: FlowEncapsulationType.VXLAN + encapsulationUpdate: VXLAN ], [ - encapsulationCreate: FlowEncapsulationType.VXLAN, + encapsulationCreate: VXLAN, encapsulationUpdate: FlowEncapsulationType.TRANSIT_VLAN ] ], getUniqueVxlanSwitchPairs() @@ -209,7 +210,7 @@ class VxlanFlowSpec extends HealthCheckSpecification { assumeTrue(switchPair as boolean, "Unable to find required switches in topology") def flow = flowHelperV2.randomFlow(switchPair) - flow.encapsulationType = FlowEncapsulationType.VXLAN + flow.encapsulationType = VXLAN flow.pinned = true flowHelperV2.addFlow(flow) @@ -248,7 +249,7 @@ class VxlanFlowSpec extends HealthCheckSpecification { when: "Create a flow with protected path" def flow = flowHelperV2.randomFlow(switchPair) flow.allocateProtectedPath = true - flow.encapsulationType = FlowEncapsulationType.VXLAN + flow.encapsulationType = VXLAN flowHelperV2.addFlow(flow) then: "Flow is created with protected path" @@ -365,7 +366,7 @@ class VxlanFlowSpec extends HealthCheckSpecification { def defaultFlow = flowHelperV2.randomFlow(switchPair) defaultFlow.source.vlanId = 0 defaultFlow.destination.vlanId = 0 - defaultFlow.encapsulationType = FlowEncapsulationType.VXLAN + defaultFlow.encapsulationType = VXLAN flowHelperV2.addFlow(defaultFlow) def flow = flowHelperV2.randomFlow(switchPair) @@ -401,8 +402,8 @@ class VxlanFlowSpec extends HealthCheckSpecification { when: "Try to create a VXLAN flow" def flow = flowHelperV2.randomFlow(switchPair) - flow.encapsulationType = FlowEncapsulationType.VXLAN.toString() - def addedFlow = northboundV2.addFlow(flow) + flow.encapsulationType = VXLAN.toString() + def addedFlow = flowHelperV2.addFlow(flow) then: "Human readable error is returned" def createError = thrown(HttpClientErrorException) @@ -417,7 +418,7 @@ class VxlanFlowSpec extends HealthCheckSpecification { addedFlow = flowHelperV2.addFlow(flow) and: "Try updated its encap type to VXLAN" - northboundV2.updateFlow(flow.flowId, flow.tap { it.encapsulationType = FlowEncapsulationType.VXLAN.toString() }) + northboundV2.updateFlow(flow.flowId, flow.tap { it.encapsulationType = VXLAN.toString() }) then: "Human readable error is returned" def updateError = thrown(HttpClientErrorException) @@ -468,8 +469,8 @@ class VxlanFlowSpec extends HealthCheckSpecification { when: "Create a VXLAN flow" def flow = flowHelperV2.randomFlow(switchPair) - flow.encapsulationType = FlowEncapsulationType.VXLAN - northboundV2.addFlow(flow) + flow.encapsulationType = VXLAN + flowHelperV2.addFlow(flow) then: "Flow is built through vxlan-enabled path, even though it is not the shortest" pathHelper.convert(northbound.getFlowPath(flow.flowId)) != noVxlanPath @@ -499,9 +500,8 @@ class VxlanFlowSpec extends HealthCheckSpecification { .supportedTransitEncapsulation.collect { it.toUpperCase() } when: "Try to create a flow" - def flow = flowHelperV2.randomFlow(switchPair) - flow.encapsulationType = FlowEncapsulationType.VXLAN - northboundV2.addFlow(flow) + def flow = flowHelperV2.randomFlow(switchPair).tap {it.encapsulationType = VXLAN} + flowHelperV2.addFlow(flow) then: "Human readable error is returned" def exc = thrown(HttpClientErrorException) @@ -521,9 +521,8 @@ class VxlanFlowSpec extends HealthCheckSpecification { when: "Try to create a one-switch flow" def sw = topology.activeSwitches.find { switchHelper.isVxlanEnabled(it.dpId) } assumeTrue(sw as boolean, "Require at least 1 VXLAN supported switch") - def flow = flowHelperV2.singleSwitchFlow(sw) - flow.encapsulationType = encapsulationCreate - northboundV2.addFlow(flow) + def flow = flowHelperV2.singleSwitchFlow(sw).tap {it.encapsulationType = encapsulationCreate} + flowHelperV2.addFlow(flow) then: "Flow is created with the #encapsulationCreate.toString() encapsulation type" def flowInfo1 = northboundV2.getFlow(flow.flowId) @@ -588,8 +587,8 @@ class VxlanFlowSpec extends HealthCheckSpecification { where: encapsulationCreate | encapsulationUpdate - FlowEncapsulationType.TRANSIT_VLAN | FlowEncapsulationType.VXLAN - FlowEncapsulationType.VXLAN | FlowEncapsulationType.TRANSIT_VLAN + FlowEncapsulationType.TRANSIT_VLAN | VXLAN + VXLAN | FlowEncapsulationType.TRANSIT_VLAN } @@ -630,7 +629,7 @@ class VxlanFlowSpec extends HealthCheckSpecification { def getUnsupportedVxlanErrorDescription(endpointName, dpId, supportedEncapsulationTypes) { return "Flow's $endpointName endpoint $dpId doesn't support requested encapsulation type " + - "$FlowEncapsulationType.VXLAN. Choose one of the supported encapsulation types " + + "$VXLAN. Choose one of the supported encapsulation types " + "$supportedEncapsulationTypes or update switch properties and add needed encapsulation type." } } 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 index 6a383268f6a..d8752c75b60 100644 --- 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 @@ -67,7 +67,7 @@ class SubFlowSpec extends HealthCheckSpecification { and: "Flow history doesn't contain info about illegal action" //create action only - northbound.getFlowHistory(yFlow.YFlowId).last().payload.last().action == CREATE_SUCCESS_Y + flowHelper.getLatestHistoryEntry(yFlow.YFlowId).payload.last().action == CREATE_SUCCESS_Y and: "Sub flow is pingable" verifyAll(northbound.pingFlow(subFlow.flowId, new PingInput())) { 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 30a2016ea59..5c4ce936dd0 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 @@ -70,9 +70,9 @@ class YFlowCreateSpec extends HealthCheckSpecification { northboundV2.getAllFlows()*.flowId.sort() == yFlow.subFlows*.flowId.sort() and: "History has relevant entries about y-flow creation" - Wrappers.wait(FLOW_CRUD_TIMEOUT) { northbound.getFlowHistory(yFlow.YFlowId).last().payload.last().action == CREATE_SUCCESS_Y } + Wrappers.wait(FLOW_CRUD_TIMEOUT) { flowHelper.getLatestHistoryEntry(yFlow.YFlowId).payload.last().action == CREATE_SUCCESS_Y } yFlow.subFlows.each { sf -> - Wrappers.wait(FLOW_CRUD_TIMEOUT) { assert northbound.getFlowHistory(sf.flowId).last().payload.last().action == CREATE_SUCCESS } + Wrappers.wait(FLOW_CRUD_TIMEOUT) { assert flowHelper.getLatestHistoryEntry(sf.flowId).payload.last().action == CREATE_SUCCESS } } and: "User is able to view y-flow paths" @@ -182,10 +182,10 @@ class YFlowCreateSpec extends HealthCheckSpecification { } and: "History has relevant entries about y-flow deletion" - Wrappers.wait(FLOW_CRUD_TIMEOUT) { northbound.getFlowHistory(yFlow.YFlowId).last().payload.last().action == DELETE_SUCCESS_Y } + Wrappers.wait(FLOW_CRUD_TIMEOUT) { flowHelper.getLatestHistoryEntry(yFlow.YFlowId).payload.last().action == DELETE_SUCCESS_Y } yFlow.subFlows.each { sf -> Wrappers.wait(FLOW_CRUD_TIMEOUT) { - assert northbound.getFlowHistory(sf.flowId).last().payload.last().action == DELETE_SUCCESS + assert flowHelper.getLatestHistoryEntry(sf.flowId).payload.last().action == DELETE_SUCCESS } } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowDiversitySpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowDiversitySpec.groovy index 3fddb58200c..cddae187329 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowDiversitySpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/flows/yflows/YFlowDiversitySpec.groovy @@ -68,7 +68,7 @@ class YFlowDiversitySpec extends HealthCheckSpecification { and: "YFlows histories contains 'diverse?' information" [yFlow2.subFlows[0], yFlow3.subFlows[0]].each {//flow1 had no diversity at the time of creation - assert northbound.getFlowHistory(it.flowId).find { it.action == CREATE_ACTION }.dumps + assert flowHelper.getEarliestHistoryEntryByAction(it.flowId, CREATE_ACTION).dumps .find { it.type == "stateAfter" }?.diverseGroupId } @@ -80,7 +80,7 @@ class YFlowDiversitySpec extends HealthCheckSpecification { def yFlowsAreDeleted = true then: "YFlows' histories contain 'diverseGroupId' information in 'delete' operation" - verifyAll(northbound.getFlowHistory(yFlow1.subFlows[0].flowId).find { it.action == DELETE_ACTION }.dumps) { + verifyAll(flowHelper.getEarliestHistoryEntryByAction(yFlow1.subFlows[0].flowId, DELETE_ACTION).dumps) { it.find { it.type == "stateBefore" }?.diverseGroupId !it.find { it.type == "stateAfter" }?.diverseGroupId } @@ -134,7 +134,7 @@ class YFlowDiversitySpec extends HealthCheckSpecification { assert involvedIslSubFlowAfterUpdate != involvedIslSimpleFlow and: "First sub flow history contains 'groupId' information" - verifyAll(northbound.getFlowHistory(subFlow.flowId).find { it.action == UPDATE_ACTION }.dumps) { + verifyAll(flowHelper.getEarliestHistoryEntryByAction(subFlow.flowId, UPDATE_ACTION).dumps) { !it.find { it.type == "stateBefore" }?.diverseGroupId it.find { it.type == "stateAfter" }?.diverseGroupId } 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 32b1e5f7aa3..5200a2254e1 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 @@ -87,10 +87,10 @@ class YFlowRerouteSpec extends HealthCheckSpecification { then: "The flow was rerouted after reroute delay" and: "History has relevant entries about y-flow reroute" wait(FLOW_CRUD_TIMEOUT) { - assert northbound.getFlowHistory(yFlow.YFlowId).last().payload.last().action == REROUTE_SUCCESS_Y + assert flowHelper.getLatestHistoryEntry(yFlow.getYFlowId()).payload.last().action == REROUTE_SUCCESS_Y } yFlow.subFlows.each { sf -> - assert northbound.getFlowHistory(sf.flowId).last().payload.last().action == REROUTE_SUCCESS + assert flowHelper.getLatestHistoryEntry(sf.flowId).payload.last().action == REROUTE_SUCCESS } wait(rerouteDelay + WAIT_OFFSET) { yFlow = northboundV2.getYFlow(yFlow.YFlowId) diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchFailuresSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchFailuresSpec.groovy index 756d8fe025c..7190185e560 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchFailuresSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchFailuresSpec.groovy @@ -72,8 +72,8 @@ class SwitchFailuresSpec extends HealthCheckSpecification { def currentIsls = pathHelper.getInvolvedIsls(PathHelper.convert(northbound.getFlowPath(flow.flowId))) def pathChanged = !currentIsls.contains(isl) && !currentIsls.contains(isl.reversed) assert pathChanged || (northboundV2.getFlowStatus(flow.flowId).status == FlowState.DOWN && - northbound.getFlowHistory(flow.flowId).find { - it.action == REROUTE_ACTION && it.taskId =~ (/.+ : retry #1 ignore_bw true/) + flowHelper.getHistoryEntriesByAction(flow.flowId, REROUTE_ACTION).find { + it.taskId =~ (/.+ : retry #1 ignore_bw true/) }?.payload?.last()?.action == REROUTE_FAIL) } @@ -102,7 +102,7 @@ class SwitchFailuresSpec extends HealthCheckSpecification { and: "Switch reconnects in the middle of reroute" Wrappers.wait(WAIT_OFFSET, 0) { - def reroute = northbound.getFlowHistory(flow.flowId).find { it.action == REROUTE_ACTION } + def reroute = flowHelper.getEarliestHistoryEntryByAction(flow.flowId, REROUTE_ACTION) assert reroute.payload.last().action == "Started validation of installed non ingress rules" } lockKeeper.reviveSwitch(swPair.src, lockKeeper.knockoutSwitch(swPair.src, RW)) @@ -110,7 +110,7 @@ class SwitchFailuresSpec extends HealthCheckSpecification { then: "Flow reroute is successful" Wrappers.wait(PATH_INSTALLATION_TIME * 2) { //double timeout since rerouted is slowed by delay assert northboundV2.getFlowStatus(flow.flowId).status == FlowState.UP - assert northbound.getFlowHistory(flow.flowId).last().payload.last().action == REROUTE_SUCCESS + assert flowHelper.getLatestHistoryEntry(flow.flowId).payload.last().action == REROUTE_SUCCESS } and: "Blinking switch has no rule anomalies" @@ -132,7 +132,7 @@ class SwitchFailuresSpec extends HealthCheckSpecification { when: "Start creating a flow between switches and lose connection to src before rules are set" def (Switch srcSwitch, Switch dstSwitch) = topology.activeSwitches def flow = flowHelperV2.randomFlow(srcSwitch, dstSwitch) - northboundV2.addFlow(flow) + flowHelperV2.attemptToAddFlow(flow) sleep(50) def blockData = lockKeeper.knockoutSwitch(srcSwitch, RW) diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchesSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchesSpec.groovy index 938fc5a30e0..3955f61f75e 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchesSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/switches/SwitchesSpec.groovy @@ -163,9 +163,9 @@ class SwitchesSpec extends HealthCheckSpecification { and: "Get all flows going through the src switch" Wrappers.wait(WAIT_OFFSET * 2) { assert northboundV2.getFlowStatus(protectedFlow.flowId).status == FlowState.DOWN - assert northbound.getFlowHistory(protectedFlow.flowId).last().payload.find { it.action == REROUTE_FAIL } + assert flowHelper.getLatestHistoryEntry(protectedFlow.flowId).payload.find { it.action == REROUTE_FAIL } assert northboundV2.getFlowStatus(defaultFlow.flowId).status == FlowState.DOWN - def defaultFlowHistory = northbound.getFlowHistory(defaultFlow.flowId).findAll { it.action == REROUTE_ACTION } + def defaultFlowHistory = flowHelper.getHistoryEntriesByAction(defaultFlow.flowId, REROUTE_ACTION) assert defaultFlowHistory.last().payload.find { it.action == REROUTE_FAIL } } def getSwitchFlowsResponse6 = northbound.getSwitchFlows(switchPair.src.dpId) diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/toggles/FeatureTogglesSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/toggles/FeatureTogglesSpec.groovy index ccb07dedcbc..60e2a431b78 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/toggles/FeatureTogglesSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/toggles/FeatureTogglesSpec.groovy @@ -35,7 +35,7 @@ class FeatureTogglesSpec extends HealthCheckSpecification { def disableFlowCreation = northbound.toggleFeature(FeatureTogglesDto.builder().createFlowEnabled(false).build()) and: "Try to create a new flow" - northbound.addFlow(flowHelper.randomFlow(topology.activeSwitches[0], topology.activeSwitches[1])) + flowHelper.addFlow(flowHelper.randomFlow(topology.activeSwitches[0], topology.activeSwitches[1])) then: "Error response is returned, explaining that feature toggle doesn't allow such operation" def e = thrown(HttpClientErrorException) diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/toggles/FeatureTogglesV2Spec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/toggles/FeatureTogglesV2Spec.groovy index 7049ee79b7b..29c7016c5e2 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/toggles/FeatureTogglesV2Spec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/toggles/FeatureTogglesV2Spec.groovy @@ -54,7 +54,7 @@ class FeatureTogglesV2Spec extends HealthCheckSpecification { def disableFlowCreation = northbound.toggleFeature(FeatureTogglesDto.builder().createFlowEnabled(false).build()) and: "Try to create a new flow" - northboundV2.addFlow(flowHelperV2.randomFlow(topology.activeSwitches[0], topology.activeSwitches[1])) + flowHelperV2.addFlow(flowHelperV2.randomFlow(topology.activeSwitches[0], topology.activeSwitches[1])) then: "Error response is returned, explaining that feature toggle doesn't allow such operation" def e = thrown(HttpClientErrorException) @@ -339,16 +339,16 @@ feature toggle"() { then: "The flow becomes 'Down'" wait(discoveryTimeout + rerouteDelay + WAIT_OFFSET * 2) { assert northboundV2.getFlowStatus(flow.flowId).status == FlowState.DOWN - assert northbound.getFlowHistory(flow.flowId).find { - it.action == REROUTE_ACTION && it.taskId =~ (/.+ : retry #1 ignore_bw true/) + assert flowHelper.getHistoryEntriesByAction(flow.flowId, REROUTE_ACTION).find { + it.taskId =~ (/.+ : retry #1 ignore_bw true/) }?.payload?.last()?.action == REROUTE_FAIL assert northboundV2.getFlowHistoryStatuses(flow.flowId, 1).historyStatuses*.statusBecome == ["DOWN"] } wait(WAIT_OFFSET) { - def prevHistorySize = northbound.getFlowHistory(flow.flowId).size() + def prevHistorySize = flowHelper.getHistorySize(flow.flowId) Wrappers.timedLoop(4) { //history size should no longer change for the flow, all retries should give up - def newHistorySize = northbound.getFlowHistory(flow.flowId).size() + def newHistorySize = flowHelper.getHistorySize(flow.flowId) assert newHistorySize == prevHistorySize assert northbound.getFlowStatus(flow.flowId).status == FlowState.DOWN sleep(500) diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/xresilience/ChaosSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/xresilience/ChaosSpec.groovy index 63b1e9f998d..99e4b1d503f 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/xresilience/ChaosSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/xresilience/ChaosSpec.groovy @@ -40,7 +40,7 @@ class ChaosSpec extends HealthCheckSpecification { List flows = [] flowsAmount.times { def flow = flowHelperV2.randomFlow(*topologyHelper.randomSwitchPair, false, flows) - northboundV2.addFlow(flow) + flowHelperV2.addFlow(flow) flows << flow } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/xresilience/FloodlightKafkaConnectionSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/xresilience/FloodlightKafkaConnectionSpec.groovy index 0849d230c61..ea8a0bcaa0a 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/xresilience/FloodlightKafkaConnectionSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/xresilience/FloodlightKafkaConnectionSpec.groovy @@ -105,7 +105,7 @@ class FloodlightKafkaConnectionSpec extends HealthCheckSpecification { updatedRegions[pair.src.dpId] != updatedRegions[pair.dst.dpId] } def flow = flowHelperV2.randomFlow(swPair) - northboundV2.addFlow(flow) + flowHelperV2.attemptToAddFlow(flow) wait(WAIT_OFFSET * 2) { //FL may be a bit laggy right after comming up, so this may take a bit longer than usual assert northboundV2.getFlowStatus(flow.flowId).status == FlowState.UP } northbound.validateFlow(flow.flowId).each { assert it.asExpected } diff --git a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/xresilience/RetriesSpec.groovy b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/xresilience/RetriesSpec.groovy index 8ebf3115dcf..c143dc31d15 100644 --- a/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/xresilience/RetriesSpec.groovy +++ b/src-java/testing/functional-tests/src/test/groovy/org/openkilda/functionaltests/spec/xresilience/RetriesSpec.groovy @@ -86,8 +86,8 @@ and at least 1 path must remain safe" then: "System fails to install rules on desired path and tries to retry reroute and find new path (global retry)" wait(WAIT_OFFSET * 3, 0.1) { - assert northbound.getFlowHistory(flow.flowId).find { - it.action == REROUTE_ACTION && it.taskId =~ (/.+ : retry #1/) + assert flowHelper.getHistoryEntriesByAction(flow.flowId, REROUTE_ACTION).find { + it.taskId =~ (/.+ : retry #1/) } } @@ -187,9 +187,8 @@ and at least 1 path must remain safe" then: "System retried to #data.description" wait(WAIT_OFFSET) { - assert northbound.getFlowHistory(flow.flowId).findAll { - it.action == data.historyAction - }.last().payload*.details.findAll{ it =~ /.+ Retrying/}.size() == data.retriesAmount + assert flowHelper.getHistoryEntriesByAction(flow.flowId, data.historyAction) + .last().payload*.details.findAll{ it =~ /.+ Retrying/}.size() == data.retriesAmount } then: "Flow is DOWN" @@ -282,7 +281,7 @@ and at least 1 path must remain safe" then: "Flow history shows failed delete rule retry attempts but flow deletion is successful at the end" wait(WAIT_OFFSET) { - def history = northbound.getFlowHistory(flow.flowId).last().payload + def history = flowHelper.getLatestHistoryEntry(flow.flowId).payload //egress and ingress rule and egress and ingress mirror rule on a broken switch, 3 retries each = total 12 assert history.count { it.details ==~ /Failed to remove the rule.*Retrying \(attempt \d+\)/ } == 12 assert history.last().action == DELETE_SUCCESS @@ -345,9 +344,8 @@ and at least 1 path must remain safe" then: "System retries to install/delete rules on the dst switch" wait(WAIT_OFFSET) { - assert northbound.getFlowHistory(flow.flowId).findAll { - it.action == REROUTE_ACTION - }.last().payload*.details.findAll{ it =~ /.+ Retrying/}.size() == 15 + assert flowHelper.getHistoryEntriesByAction(flow.flowId, REROUTE_ACTION) + .last().payload*.details.findAll{ it =~ /.+ Retrying/}.size() == 15 //install: 3 attempts, revert: delete 9 attempts + install 3 attempts // delete: 3 attempts * (1 flow rule + 1 ingress mirror rule + 1 egress mirror rule) = 9 attempts } @@ -434,7 +432,7 @@ class RetriesIsolatedSpec extends HealthCheckSpecification { and: "Flow remains down and no new history events appear for the next 3 seconds (no retry happens)" timedLoop(3) { assert northboundV2.getFlowStatus(flow.flowId).status == FlowState.DOWN - assert northbound.getFlowHistory(flow.flowId).size() == eventsAmount + assert flowHelper.getHistorySize(flow.flowId) == eventsAmount } and: "Src/dst switches are valid" From 9894eb9e985cb1f968ab1c608c9519fa7a536cd9 Mon Sep 17 00:00:00 2001 From: Dmitrii Beliakov Date: Wed, 1 Nov 2023 16:09:33 +0100 Subject: [PATCH 2/5] Fix a bug when a misconfigured rule with incorrect cookie is not re-installed. --- .../topology/switchmanager/fsm/SwitchSyncFsm.java | 3 +++ .../service/impl/ValidationServiceImpl.java | 12 +++++++++++- .../service/impl/ValidationServiceImplTest.java | 6 +++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/SwitchSyncFsm.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/SwitchSyncFsm.java index 89196240271..3ffca167e23 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/SwitchSyncFsm.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/fsm/SwitchSyncFsm.java @@ -299,6 +299,9 @@ protected void computeMisconfiguredRules(SwitchSyncState from, SwitchSyncState t List misconfiguredRulesToInstall = validationResult.getExpectedEntries().stream() .filter(entry -> entry instanceof FlowSpeakerData) .map(entry -> (FlowSpeakerData) entry) + //This filter cannot find the actual rule to install if the misconfigured rule has an incorrect + //cookie. The misconfigured rule will be removed here and the proper rule is installed because + //SwitchValidation marks the same rule as a missing one. .filter(flowEntry -> reinstalledRulesCookies.contains(flowEntry.getCookie().getValue())) .collect(Collectors.toList()); toInstall.addAll(misconfiguredRulesToInstall); diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/impl/ValidationServiceImpl.java b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/impl/ValidationServiceImpl.java index 587888daebd..725ef56659c 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/impl/ValidationServiceImpl.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/main/java/org/openkilda/wfm/topology/switchmanager/service/impl/ValidationServiceImpl.java @@ -370,7 +370,17 @@ private void processRulesValidation(SwitchId switchId, Set miss } else { log.info("On switch {} rule {} is misconfigured. Actual: {} : expected : {}", switchId, actualRuleValue.getCookie(), actualRuleValue, expectedRuleValue); - misconfiguredRules.add(calculateMisconfiguredRule(expectedRuleValue, actualRuleValue)); + MisconfiguredInfo misconfiguredInfo = + calculateMisconfiguredRule(expectedRuleValue, actualRuleValue); + misconfiguredRules.add(misconfiguredInfo); + + // This is needed because of the SwitchSyncFsm implementation: it adds rules to install by + // looking at the misconfigured rule's cookie in the list of expected rules. Therefore, when + // the rule has the incorrect cookie, it will be removed, but not added in SwitchSyncFsm + // misconfigured rules' handler. + if (misconfiguredInfo.getDiscrepancies().getCookie() != null) { + missingRules.add(expectedRuleValue); + } } } }); diff --git a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/impl/ValidationServiceImplTest.java b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/impl/ValidationServiceImplTest.java index 2bad13e7517..e52c0a13864 100644 --- a/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/impl/ValidationServiceImplTest.java +++ b/src-java/swmanager-topology/swmanager-storm-topology/src/test/java/org/openkilda/wfm/topology/switchmanager/service/impl/ValidationServiceImplTest.java @@ -21,6 +21,7 @@ import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -630,7 +631,10 @@ public void validateRulesMisconfiguredRules() { true); assertTrue(response.getExcessRules().isEmpty()); - assertTrue(response.getMissingRules().isEmpty()); + assertFalse(response.getMissingRules().isEmpty(), + "The rule is considered missing, because the misconfigured rule has an incorrect cookie " + + "and SwitchSync has no way to pick the correct rule for installation using this cookie."); + assertNotEquals(expected.get(0).getCookie(), misconfigured.getCookie()); assertFalse(response.getMisconfiguredRules().isEmpty()); assertTrue(response.getProperRules().isEmpty()); assertFalse(response.isAsExpected()); From d7e96149df884a92db5bc018f2464238e00326ad Mon Sep 17 00:00:00 2001 From: ichupin Date: Thu, 2 Nov 2023 17:11:06 +0100 Subject: [PATCH 3/5] replacing opentsdb in kilda-gui with victoriaDB. GUI:new stats.service.ts with the following methods: *getFlowPathStats *getSwitchPortsStats *getPortGraphData *getForwardGraphData *getIslLossGraphData *getBackwardGraphData KILDA-bff: *introduced new common api for victoria metrics data requests from GUI side. *minor existed bug fixes --- .../java/org/openkilda/constants/Metrics.java | 30 +- .../openkilda/controller/StatsController.java | 75 +- .../org/openkilda/model/FlowPathStats.java | 116 - .../org/openkilda/model/VictoriaStatsReq.java | 38 + .../model/victoria/VictoriaData.java | 2 +- .../org/openkilda/service/StatsService.java | 243 +- .../common/data-models/flowMetricVictoria.ts | 17 +- .../src/app/common/data-models/port-info.ts | 60 + .../app/common/services/dygraph.service.ts | 101 +- .../src/app/common/services/flows.service.ts | 44 - .../app/common/services/isl-detail.service.ts | 8 - .../src/app/common/services/stats.service.ts | 349 +++ .../src/app/common/services/switch.service.ts | 10 - .../flows/flow-add/flow-add.component.ts | 21 +- .../flows/flow-edit/flow-edit.component.ts | 28 +- .../flows/flow-graph/flow-graph.component.ts | 16 +- .../flow-path-graph.component.ts | 54 +- .../isl/isl-detail/isl-detail.component.html | 4 +- .../isl/isl-detail/isl-detail.component.ts | 2138 +++++++++-------- .../port-graph/port-graph.component.ts | 13 +- .../switches/port-list/port-list.component.ts | 7 +- 21 files changed, 1883 insertions(+), 1491 deletions(-) delete mode 100644 src-gui/src/main/java/org/openkilda/model/FlowPathStats.java create mode 100644 src-gui/src/main/java/org/openkilda/model/VictoriaStatsReq.java create mode 100644 src-gui/ui/src/app/common/data-models/port-info.ts create mode 100644 src-gui/ui/src/app/common/services/stats.service.ts diff --git a/src-gui/src/main/java/org/openkilda/constants/Metrics.java b/src-gui/src/main/java/org/openkilda/constants/Metrics.java index 6919a74fd2d..1c4ea606f0e 100644 --- a/src-gui/src/main/java/org/openkilda/constants/Metrics.java +++ b/src-gui/src/main/java/org/openkilda/constants/Metrics.java @@ -123,6 +123,11 @@ public final String getMetricName(String prefix) { return prefix + this.metricName; } + public static String getFullMetricNameByMetricName(String metricName, String prefix) { + return Arrays.stream(values()).filter(value -> value.metricName.equals(metricName)) + .map(val -> val.getMetricName(prefix)).findFirst().orElse(null); + } + /** * Flow value. * @@ -176,14 +181,14 @@ public static String meterMetricName(String metricPart, String prefix) { /** * Flow raw value. * - * @param tag the tag + * @param metricEnding the tag * @return the list */ - public static List flowRawValue(String tag, String prefix) { + public static List flowRawValue(String metricEnding, String prefix) { List list = new ArrayList<>(); - tag = "Flow_raw_" + tag; + metricEnding = "Flow_raw_" + metricEnding; for (Metrics metric : values()) { - if (metric.getTag().equalsIgnoreCase(tag)) { + if (metric.getTag().equalsIgnoreCase(metricEnding)) { list.add(metric.getMetricName(prefix)); } } @@ -193,21 +198,22 @@ public static List flowRawValue(String tag, String prefix) { /** * Switch value. * - * @param tag the tag + * @param metricEnding the tag * @return the list */ - public static List switchValue(String tag, String prefix) { + public static List switchValue(String metricEnding, String prefix) { List list = new ArrayList<>(); + String metricTag; - if (tag.equalsIgnoreCase("latency")) { - tag = "Isl_" + tag; - } else if (tag.equalsIgnoreCase("rtt")) { - tag = "Isl_" + tag; + if (metricEnding.equalsIgnoreCase("latency")) { + metricTag = "Isl_" + metricEnding; + } else if (metricEnding.equalsIgnoreCase("rtt")) { + metricTag = "Isl_" + metricEnding; } else { - tag = "Switch_" + tag; + metricTag = "Switch_" + metricEnding; } for (Metrics metric : values()) { - if (metric.getTag().equalsIgnoreCase(tag)) { + if (metric.getTag().equalsIgnoreCase(metricTag)) { list.add(metric.getMetricName(prefix)); } } diff --git a/src-gui/src/main/java/org/openkilda/controller/StatsController.java b/src-gui/src/main/java/org/openkilda/controller/StatsController.java index 62c8eb742d0..5f4b051d341 100644 --- a/src-gui/src/main/java/org/openkilda/controller/StatsController.java +++ b/src-gui/src/main/java/org/openkilda/controller/StatsController.java @@ -25,8 +25,8 @@ import org.openkilda.constants.Metrics; import org.openkilda.constants.OpenTsDb; import org.openkilda.exception.InvalidRequestException; -import org.openkilda.model.FlowPathStats; import org.openkilda.model.PortInfo; +import org.openkilda.model.VictoriaStatsReq; import org.openkilda.model.victoria.Status; import org.openkilda.model.victoria.VictoriaData; import org.openkilda.model.victoria.VictoriaStatsRes; @@ -55,9 +55,6 @@ public class StatsController { private static final Logger LOGGER = Logger.getLogger(StatsController.class); - - private static final String VICTORIA_METRICS_URL_EMPTY = "Victoria Metrics DB URL has not been configured" - + " for Openkilda-gui."; private static final String REQUIRED_START_DATE_ERROR = "startDate must not be null or empty."; private static final String STATS_TYPE_ERROR = "statsType path variable should not be empty or wrong."; private static final String REQUIRED_METRIC_ERROR = "metric must not be null or empty."; @@ -160,11 +157,11 @@ public String getFlowStats(@PathVariable String flowid, @PathVariable String sta /** * Retrieves Victoria statistics data for a specific flow based on the provided parameters. * - * @param flowId The ID of the flow for which statistics are retrieved. + * @param flowId The ID of the flow for which statistics are retrieved. * @param startDate The start date of the data retrieval period. - * @param endDate The end date of the data retrieval period. - * @param step The time step for data aggregation. - * @param metric A list of metrics for which statistics are retrieved. + * @param endDate The end date of the data retrieval period. + * @param step The time step for data aggregation. + * @param metric A list of metrics for which statistics are retrieved. * @param direction The direction of the flow data ('forward' or 'reverse'). Optional. * @return A {@link ResponseEntity} containing a {@link VictoriaStatsRes} object with the retrieved statistics. * @see VictoriaStatsRes @@ -182,11 +179,6 @@ public ResponseEntity getFlowVictoriaStats(@PathVariable Strin @RequestParam List metric, @RequestParam(required = false) String direction) { - if (StringUtils.isBlank(applicationProperties.getVictoriaBaseUrl()) - || applicationProperties.getVictoriaBaseUrl().contains("://:/prometheus")) { - return buildServiceUnavailableRes(); - } - LOGGER.info("Get victoria stat for flow"); if (StringUtils.isBlank(startDate)) { return buildVictoriaBadRequestErrorRes(REQUIRED_START_DATE_ERROR); @@ -277,40 +269,57 @@ public String getFlowLossPacketStats(@PathVariable String flowid, @PathVariable } /** - * Gets the flow path stat. + * Gets the flow path stat from victoria db. * - * @param flowPathStats the flow path stat (flowid , list of switchids, start date, end date) + * @param victoriaStatsReq the flow path stat (flowid , switchId, startDate, endDate, labels) * @return the flow path stat */ - @RequestMapping(value = "flowpath", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @RequestMapping(value = "common", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Permissions(values = {IConstants.Permission.MENU_FLOWS}) @ResponseBody - public String getFlowPathStat(@RequestBody FlowPathStats flowPathStats) { + public ResponseEntity commonVictoriaStats(@RequestBody VictoriaStatsReq victoriaStatsReq) { - LOGGER.info("Get flow path stat "); - return statsService.getFlowPathStats(flowPathStats); + LOGGER.info(String.format("Get flow path stat request: %s", victoriaStatsReq)); + ResponseEntity res; + try { + List victoriaResult = statsService.getVictoriaStats(victoriaStatsReq); + + Optional errorData = victoriaResult.stream().filter(this::hasError).findFirst(); + + if (errorData.isPresent()) { + VictoriaData err = errorData.get(); + res = buildVictoriaBadRequestErrorRes(Integer.parseInt(err.getErrorType()), err.getError()); + } else { + res = ResponseEntity.ok(VictoriaStatsRes.builder().status(SUCCESS) + .dataList(victoriaResult).build()); + } + } catch (InvalidRequestException e) { + res = buildVictoriaBadRequestErrorRes(e.getMessage()); + } + return res; } /** - * Gets the switch ports stats. + * Retrieves statistics for switch ports. + * This method handles HTTP POST requests to fetch statistics for switch ports. It requires a valid + * VictoriaStatsReq object in the request body to specify the statistics parameters. * - * @param switchid the switchid - * @param startDate the start date - * @param endDate the end date - * @param downsample the downsample - * @return the switch ports stats + * @param statsReq The VictoriaStatsReq object containing the statistics parameters. + * @return A list of PortInfo objects representing the statistics for switch ports. + * @throws InvalidRequestException If the request parameters are invalid or malformed. + * @see VictoriaStatsReq + * @see PortInfo + * @see StatsService#getSwitchPortsStats(VictoriaStatsReq) */ - @RequestMapping(value = "switchports/{switchid}/{startDate}/{endDate}/{downsample}", method = RequestMethod.GET, + @RequestMapping(value = "switchports", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Permissions(values = {IConstants.Permission.MENU_SWITCHES}) @ResponseBody - public List getSwitchPortsStats(@PathVariable String switchid, @PathVariable String startDate, - @PathVariable String endDate, @PathVariable String downsample) { - - LOGGER.info("Get switch ports stat "); - return statsService.getSwitchPortsStats(startDate, endDate, downsample, switchid); + public List switchPortsStats(@RequestBody VictoriaStatsReq statsReq) throws InvalidRequestException { + LOGGER.info("POST switch ports stat "); + return statsService.getSwitchPortsStats(statsReq); } @@ -342,10 +351,6 @@ private boolean hasError(VictoriaData victoriaData) { return victoriaData != null && Status.ERROR.equals(victoriaData.getStatus()); } - private ResponseEntity buildServiceUnavailableRes() { - return buildVictoriaBadRequestErrorRes(503, StatsController.VICTORIA_METRICS_URL_EMPTY); - } - private ResponseEntity buildVictoriaBadRequestErrorRes(String message) { return buildVictoriaBadRequestErrorRes(400, message); } diff --git a/src-gui/src/main/java/org/openkilda/model/FlowPathStats.java b/src-gui/src/main/java/org/openkilda/model/FlowPathStats.java deleted file mode 100644 index f8e4bb6d50e..00000000000 --- a/src-gui/src/main/java/org/openkilda/model/FlowPathStats.java +++ /dev/null @@ -1,116 +0,0 @@ -/* Copyright 2018 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.model; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; - -import lombok.Data; - -import java.util.List; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonIgnoreProperties(ignoreUnknown = true) -@JsonPropertyOrder({ "flowid", "startDate", "endDate", "downsample", "switches", "direction", "metric" }) -@Data -public class FlowPathStats { - - @JsonProperty("flowid") - private String flowid; - - @JsonProperty("direction") - private String direction; - - @JsonProperty("metric") - private String metric; - - @JsonProperty("startdate") - private String startDate; - - @JsonProperty("enddate") - private String endDate; - - @JsonProperty("downsample") - private String downsample; - - @JsonProperty("inPort") - private String inPort; - - @JsonProperty("outPort") - private String outPort; - - @JsonProperty("switches") - private List switches; - - public String getFlowid() { - return flowid; - } - - public void setFlowid(String flowid) { - this.flowid = flowid; - } - - public String getStartDate() { - return startDate; - } - - public void setStartDate(String startDate) { - this.startDate = startDate; - } - - public String getEndDate() { - return endDate; - } - - public void setEndDate(String endDate) { - this.endDate = endDate; - } - - public String getDownsample() { - return downsample; - } - - public void setDownsample(String downsample) { - this.downsample = downsample; - } - - public List getSwitches() { - return switches; - } - - public void setSwitches(List switches) { - this.switches = switches; - } - - public String getDirection() { - return direction; - } - - public void setDirection(String direction) { - this.direction = direction; - } - - public String getMetric() { - return metric; - } - - public void setMetric(String metric) { - this.metric = metric; - } - -} diff --git a/src-gui/src/main/java/org/openkilda/model/VictoriaStatsReq.java b/src-gui/src/main/java/org/openkilda/model/VictoriaStatsReq.java new file mode 100644 index 00000000000..610696a942f --- /dev/null +++ b/src-gui/src/main/java/org/openkilda/model/VictoriaStatsReq.java @@ -0,0 +1,38 @@ +/* Copyright 2018 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.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonNaming(value = PropertyNamingStrategies.LowerCamelCaseStrategy.class) +@Data +public class VictoriaStatsReq { + private List metrics; + private String statsType; + private String startDate; + private String endDate; + private String step; + private Map labels; +} diff --git a/src-gui/src/main/java/org/openkilda/model/victoria/VictoriaData.java b/src-gui/src/main/java/org/openkilda/model/victoria/VictoriaData.java index e28708574c5..9399b3ffb6e 100644 --- a/src-gui/src/main/java/org/openkilda/model/victoria/VictoriaData.java +++ b/src-gui/src/main/java/org/openkilda/model/victoria/VictoriaData.java @@ -32,7 +32,7 @@ public class VictoriaData { private String metric; private Map tags; - @JsonProperty("timeToValue") + @JsonProperty("dps") LinkedHashMap timeToValueMap; private Status status; private String error; diff --git a/src-gui/src/main/java/org/openkilda/service/StatsService.java b/src-gui/src/main/java/org/openkilda/service/StatsService.java index 2bf745b3cb6..4cccc75abbd 100644 --- a/src-gui/src/main/java/org/openkilda/service/StatsService.java +++ b/src-gui/src/main/java/org/openkilda/service/StatsService.java @@ -16,6 +16,9 @@ package org.openkilda.service; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; +import static org.openkilda.constants.Metrics.ISL_LATENCY; +import static org.openkilda.constants.Metrics.ISL_RTT; +import static org.openkilda.constants.Metrics.SWITCH_STATE; import org.openkilda.config.ApplicationProperties; import org.openkilda.constants.Direction; @@ -31,11 +34,10 @@ import org.openkilda.integration.service.SwitchIntegrationService; import org.openkilda.integration.source.store.SwitchStoreService; import org.openkilda.integration.source.store.dto.Port; -import org.openkilda.model.FlowPathStats; import org.openkilda.model.PortDiscrepancy; import org.openkilda.model.PortInfo; import org.openkilda.model.SwitchLogicalPort; -import org.openkilda.model.SwitchPortStats; +import org.openkilda.model.VictoriaStatsReq; import org.openkilda.model.victoria.RangeQueryParams; import org.openkilda.model.victoria.VictoriaData; import org.openkilda.model.victoria.dbdto.VictoriaDbRes; @@ -43,8 +45,7 @@ import org.openkilda.utility.CollectionUtil; import org.openkilda.utility.IoUtil; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.type.TypeFactory; +import com.google.common.collect.Lists; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; @@ -58,11 +59,11 @@ import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; /** @@ -184,7 +185,7 @@ public List getTransformedFlowVictoriaStats(String statsType, for (Direction dir : directions) { Map queryParamLabelFilters = buildQueryParamLabelFilters(flowId, dir); RangeQueryParams rangeQueryParams = buildRangeQueryParams(startTimeStamp, endTimeStamp, step, - metricName, queryParamLabelFilters); + metricName, queryParamLabelFilters, true, true); victoriaDataList.add(buildVictoriaData(statsIntegrationService.getVictoriaStats(rangeQueryParams), metricName)); } @@ -252,51 +253,74 @@ public String getFlowLossPacketStats(String startDate, String endDate, String do } /** - * Gets the flow path stat. + * Retrieves Victoria Flow Path statistics data based on the provided request parameters. * - * @param flowPathStats the flow path stat - * @return the flow path stat + * @param statsReq The request parameters for querying Victoria Flow Path statistics. + * @return A list of VictoriaData objects representing the queried statistics. + * @throws InvalidRequestException if the request parameters are invalid or if there are issues with the query. */ - public String getFlowPathStats(FlowPathStats flowPathStats) { - return statsIntegrationService.getStats(flowPathStats.getStartDate(), flowPathStats.getEndDate(), - flowPathStats.getDownsample(), getSwitches(flowPathStats), null, flowPathStats.getFlowid(), - null, flowPathStats.getInPort(), null, flowPathStats.getOutPort(), - StatsType.FLOW_RAW_PACKET, flowPathStats.getMetric(), flowPathStats.getDirection()); + public List getVictoriaStats(VictoriaStatsReq statsReq) throws InvalidRequestException { + validateRequestParameters(statsReq.getStartDate(), statsReq.getStatsType(), statsReq.getMetrics()); + + Long startTimeStamp = parseTimeStamp(statsReq.getStartDate()); + Long endTimeStamp = Optional.ofNullable(parseTimeStamp(statsReq.getEndDate())) + .orElse(System.currentTimeMillis() / 1000); + + List victoriaDataList = new ArrayList<>(); + List metricNameList = Lists.newArrayList(); + + if (CollectionUtils.isNotEmpty(statsReq.getMetrics())) { + statsReq.getMetrics().forEach(metric -> + metricNameList.addAll( + getMetricByMetricEndingAngStatsType( + StatsType.byJsonValue(statsReq.getStatsType()), metric))); + } else { + metricNameList.addAll(getMetricByStatsType(StatsType.byJsonValue(statsReq.getStatsType()))); + } + + for (String metricName : metricNameList) { + if (StringUtils.isBlank(metricName)) { + throw new InvalidRequestException(String.format("There is no such metric: %s", metricName)); + } + boolean useRate = true; + boolean useSum = true; + if (metricName.equals(SWITCH_STATE.getMetricName(appProps.getMetricPrefix()))) { + useRate = false; + useSum = false; + } + if (metricName.equals(ISL_LATENCY.getMetricName(appProps.getMetricPrefix())) + || metricName.equals(ISL_RTT.getMetricName(appProps.getMetricPrefix()))) { + useRate = false; + } + RangeQueryParams rangeQueryParams = buildRangeQueryParams(startTimeStamp, endTimeStamp, statsReq.getStep(), + metricName, statsReq.getLabels(), useRate, useSum); + victoriaDataList.addAll(buildVictoriaDataList(statsIntegrationService.getVictoriaStats(rangeQueryParams), + metricName)); + } + LOGGER.debug("Received the following metrics responses: {}", victoriaDataList); + return victoriaDataList; } /** - * Gets the switch ports stats. + * Retrieve statistics for switch ports. * - * @param startDate the start date - * @param endDate the end date - * @param downSample the down sample - * @param switchId the switch id - * @return the switch ports stats + * @param statsReq The request object containing statistics parameters. + * @return A list of PortInfo objects representing switch port statistics. + * @throws InvalidRequestException If the request is invalid or malformed. */ - public List getSwitchPortsStats(String startDate, String endDate, String downSample, String switchId) { - List switchIds = Arrays.asList(switchId); - List switchPortStats = new ArrayList(); - try { - String result = statsIntegrationService.getStats(startDate, endDate, downSample, switchIds, - null, null, null, null, null, null, - StatsType.SWITCH_PORT, null, null); - ObjectMapper mapper = new ObjectMapper(); - switchPortStats = mapper.readValue(result, - TypeFactory.defaultInstance().constructCollectionLikeType(List.class, SwitchPortStats.class)); - } catch (Exception e) { - LOGGER.error("Error occurred while retriving switch port stats", e); - } - List portStats = getSwitchPortStatsReport(switchPortStats, switchId); - if (storeService.getSwitchStoreConfig().getUrls().size() > 0) { - if (!CollectionUtil.isEmpty(switchIds)) { - try { - List inventoryPorts = switchStoreService - .getSwitchPort(IoUtil.switchCodeToSwitchId(switchIds.get(0))); - processInventoryPorts(portStats, inventoryPorts); - } catch (Exception ex) { - LOGGER.error("Error occurred while retriving switch ports stats for inventory", ex); - } + public List getSwitchPortsStats(VictoriaStatsReq statsReq) throws InvalidRequestException { + List victoriaDataList = getVictoriaStats(statsReq); + String switchId = statsReq.getLabels().get("switchid"); + List portStats = getSwitchPortStatsReport(victoriaDataList, switchId); + if (!storeService.getSwitchStoreConfig().getUrls().isEmpty()) { + try { + List inventoryPorts = switchStoreService + .getSwitchPort(IoUtil.switchCodeToSwitchId(switchId)); + processInventoryPorts(portStats, inventoryPorts); + } catch (Exception ex) { + LOGGER.error("Error occurred while retriving switch ports stats for inventory", ex); } + } return portStats; } @@ -379,10 +403,10 @@ private void processInventoryPorts(final List portStats, final List getSwitchPortStatsReport(List switchPortStats, String switchId) { + private List getSwitchPortStatsReport(List switchPortStats, String switchId) { Map> portStatsByPortNo = new HashMap>(); - for (SwitchPortStats stats : switchPortStats) { - String port = stats.getTags().getPort(); + for (VictoriaData stats : switchPortStats) { + String port = stats.getTags().get("port"); if (Integer.parseInt(port) > 0) { if (!portStatsByPortNo.containsKey(port)) { @@ -390,7 +414,7 @@ private List getSwitchPortStatsReport(List switchPort } portStatsByPortNo.get(port).put( stats.getMetric().replace(appProps.getMetricPrefix() + "switch.", ""), - calculateHighestValue(stats.getDps())); + getLastValue(stats.getTimeToValueMap())); } } @@ -403,20 +427,20 @@ private List getSwitchPortStatsReport(List switchPort * @param dps the dps * @return the double */ - private double calculateHighestValue(Map dps) { - double maxVal = 0.0; + private double getLastValue(LinkedHashMap dps) { + double lastValue = 0.0; if (!dps.isEmpty()) { - long maxTimestamp = 0; - for (String key : dps.keySet()) { - long val = Long.parseLong(key); - if (maxTimestamp < val) { - maxTimestamp = val; + long lastTimeStamp = 0; + for (Map.Entry entry : dps.entrySet()) { + Long timeStamp = entry.getKey(); + if (timeStamp > lastTimeStamp) { + lastTimeStamp = timeStamp; } } - maxVal = BigDecimal.valueOf(dps.get(String.valueOf(maxTimestamp))).setScale(2, RoundingMode.HALF_UP) + lastValue = BigDecimal.valueOf(dps.get(lastTimeStamp)).setScale(2, RoundingMode.HALF_UP) .doubleValue(); } - return maxVal; + return lastValue; } /** @@ -483,18 +507,6 @@ private List getPortInfo(final Map> portSt return portInfos; } - private List getSwitches(FlowPathStats flowPathStats) { - List switches = null; - if (flowPathStats != null) { - switches = flowPathStats.getSwitches(); - if (switches == null || switches.isEmpty()) { - switches = new ArrayList(); - switches.add("*"); - } - } - return switches; - } - private VictoriaData buildVictoriaData(VictoriaDbRes dbData, String metricName) { LinkedHashMap timeToValueMap = new LinkedHashMap<>(); @@ -515,6 +527,29 @@ private VictoriaData buildVictoriaData(VictoriaDbRes dbData, String metricName) .build(); } + private List buildVictoriaDataList(VictoriaDbRes dbData, String metricName) { + List result = Lists.newArrayList(); + + if (dbData.getData() != null && isNotEmpty(dbData.getData().getResult())) { + dbData.getData().getResult().forEach(metricValues -> { + LinkedHashMap timeToValueMap = new LinkedHashMap<>(); + metricValues.getValues().forEach(timeToValue -> { + timeToValueMap.put(Long.parseLong(timeToValue[0]), Double.valueOf(timeToValue[1])); + }); + + result.add(VictoriaData.builder() + .tags(metricValues.getTags()) + .metric(metricName) + .timeToValueMap(timeToValueMap) + .status(dbData.getStatus()) + .error(dbData.getError()) + .errorType(dbData.getErrorType()) + .build()); + }); + } + return result; + } + private Map buildQueryParamLabelFilters(String flowId, Direction direction) { Map queryParamLabelFilters = new LinkedHashMap<>(); queryParamLabelFilters.put("flowid", flowId); @@ -524,22 +559,40 @@ private Map buildQueryParamLabelFilters(String flowId, Direction private RangeQueryParams buildRangeQueryParams(Long startTimeStamp, Long endTimeStamp, String step, String metricName, - Map queryParamLabelFilters) { + Map queryParamLabelFilters, + boolean useRate, boolean useSum) { return RangeQueryParams.builder() .start(startTimeStamp) .end(endTimeStamp) .step(step) - .query(buildVictoriaRequestRangeQueryFormParam(metricName, queryParamLabelFilters)) + .query(buildVictoriaRequestRangeQueryFormParam(metricName, queryParamLabelFilters, useRate, useSum)) .build(); } private String buildVictoriaRequestRangeQueryFormParam(String metricName, - Map queryParamLableFilters) { - String lableFilterString = queryParamLableFilters.entrySet().stream() + Map queryParamLabelFilters, + boolean useRate, boolean useSum) { + String lableFilterString = queryParamLabelFilters.entrySet().stream() + .filter(keyValue -> !keyValue.getValue().equals("*")) .map(entry -> String.format("%s='%s'", entry.getKey(), entry.getValue())) .collect(Collectors.joining(", ")); - String labelList = String.join(",", queryParamLableFilters.keySet()); - return String.format("rate(sum(%s{%s}) by (%s))", metricName, lableFilterString, labelList); + String labelList = String.join(",", queryParamLabelFilters.keySet()); + String query = String.format("%s{%s}", metricName.replace("-", "\\-"), lableFilterString); + if (useSum) { + query = addSumToQuery(query, labelList); + } + if (useRate) { + query = addRateToQuery(query); + } + return query; + } + + private String addRateToQuery(String query) { + return "rate(" + query + ")"; + } + + private String addSumToQuery(String query, String groupByLabels) { + return String.format("sum(" + query + ") by (%s)", groupByLabels); } private void validateRequestParameters(String startDate, List metric, String flowId) @@ -549,6 +602,16 @@ private void validateRequestParameters(String startDate, List metric, St } } + private void validateRequestParameters(String startDate, String statsType, List metrics) + throws InvalidRequestException { + if (StringUtils.isBlank(startDate)) { + throw new InvalidRequestException("Empty startDate"); + } + if ((CollectionUtils.isEmpty(metrics) && StringUtils.isBlank(statsType))) { + throw new InvalidRequestException("Metric list and statsType can not be null or empty at the same time"); + } + } + private Long parseTimeStamp(String date) throws InvalidRequestException { if (StringUtils.isBlank(date)) { return null; @@ -564,4 +627,36 @@ private Long convertToTimeStamp(String timeString, DateTimeFormatter formatter) LocalDateTime localDateTime = LocalDateTime.parse(timeString, formatter); return localDateTime.toEpochSecond(ZoneOffset.UTC); } + + private List getMetricByStatsType(StatsType statsType) { + return getMetricByMetricEndingAngStatsType(statsType, null); + } + + private List getMetricByMetricEndingAngStatsType(StatsType statsType, String metric) { + List metricList = new ArrayList<>(); + if (statsType == null) { + String fullMetricName = Metrics.getFullMetricNameByMetricName(metric, appProps.getMetricPrefix()); + if (fullMetricName != null) { + metricList.add(fullMetricName); + } + } else if (statsType.equals(StatsType.PORT)) { + metricList = Metrics.switchValue(metric, appProps.getMetricPrefix()); + } else if (statsType.equals(StatsType.FLOW)) { + metricList = Metrics.flowValue(metric, true, appProps.getMetricPrefix()); + } else if (statsType.equals(StatsType.ISL)) { + metricList = Metrics.switchValue(metric, appProps.getMetricPrefix()); + } else if (statsType.equals(StatsType.ISL_LOSS_PACKET)) { + metricList = Metrics.switchValue(metric, appProps.getMetricPrefix()); + } else if (statsType.equals(StatsType.FLOW_LOSS_PACKET)) { + metricList = Metrics.flowValue("packets", false, appProps.getMetricPrefix()); + metricList.addAll(Metrics.flowValue(metric, false, appProps.getMetricPrefix())); + } else if (statsType.equals(StatsType.FLOW_RAW_PACKET)) { + metricList = Metrics.flowRawValue(metric, appProps.getMetricPrefix()); + } else if (statsType.equals(StatsType.SWITCH_PORT)) { + metricList = Metrics.getStartsWith("Switch_", appProps.getMetricPrefix()); + } else if (statsType.equals(StatsType.METER)) { + metricList = Metrics.meterValue(metric, appProps.getMetricPrefix()); + } + return metricList; + } } diff --git a/src-gui/ui/src/app/common/data-models/flowMetricVictoria.ts b/src-gui/ui/src/app/common/data-models/flowMetricVictoria.ts index 3311be604a9..77e69ffb567 100644 --- a/src-gui/ui/src/app/common/data-models/flowMetricVictoria.ts +++ b/src-gui/ui/src/app/common/data-models/flowMetricVictoria.ts @@ -1,15 +1,20 @@ -export interface TimeToValueMap { - [timestamp: string]: number; -} - export interface VictoriaData { metric: string; tags: Record; - status: string; - timeToValue: TimeToValueMap; + status?: string; + dps: Record; } export interface VictoriaStatsRes { dataList: VictoriaData[]; status: string; } + +export interface VictoriaStatsReq { + metrics?: string[]; + statsType?: string; + startDate: string; + endDate: string; + step: string; + labels: Record; +} diff --git a/src-gui/ui/src/app/common/data-models/port-info.ts b/src-gui/ui/src/app/common/data-models/port-info.ts new file mode 100644 index 00000000000..a27de349c92 --- /dev/null +++ b/src-gui/ui/src/app/common/data-models/port-info.ts @@ -0,0 +1,60 @@ +export interface PortInfo { + assignmenttype: string; + 'assignment-state': string; + 'assignment-date': number; + interfacetype: string; + status: string; + crossconnect: string; + customeruuid: string; + switch_id: string; + port_name: string; + port_number: any; + stats: Record; + 'unique-id': string; + 'pop-location': PopLocation; + 'inventory-port-uuid': string; + customer: Customer; + notes: string; + odfmdf: string; + mmr: string; + 'is-active': string; + is_logical_port: boolean; + logical_group_name: string; + lag_group: string[]; + discrepancy: PortDiscrepancy; +} + +export interface PopLocation { + 'state-code': string; + 'country-code': string; + 'pop-uuid': string; + 'pop-name': string; + 'pop-code': string; +} + + +export interface Customer { + 'customer-uuid': string; + 'company-name': string; + 'customer-account-number': string; + 'customer-type': string; + 'domain-id': string; + 'switch-id': string; + 'port-no': number; + flows: PortFlow[]; +} + +export interface PortFlow { + 'flow-id': string; + bandwidth: number; +} + + +export interface PortDiscrepancy { + 'assignment-type': boolean; + 'controller-assignment-type': string; + 'inventory-assignment-type': string; + 'controller-discrepancy': boolean; + 'inventory-discrepancy': boolean; +} + diff --git a/src-gui/ui/src/app/common/services/dygraph.service.ts b/src-gui/ui/src/app/common/services/dygraph.service.ts index 78976146baf..5ae8e205d00 100644 --- a/src-gui/ui/src/app/common/services/dygraph.service.ts +++ b/src-gui/ui/src/app/common/services/dygraph.service.ts @@ -1,9 +1,8 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { environment } from '../../../environments/environment'; -import { Observable, Subject, BehaviorSubject } from 'rxjs'; +import { Subject, BehaviorSubject } from 'rxjs'; import {FlowMetricTsdb} from '../data-models/flowMetricTsdb'; -import {VictoriaData} from '../data-models/flowMetricVictoria'; +import {VictoriaData, VictoriaStatsRes} from '../data-models/flowMetricVictoria'; @Injectable({ @@ -30,90 +29,6 @@ export class DygraphService { constructor(private httpClient: HttpClient) {} - getForwardGraphData( - src_switch, - src_port, - dst_switch, - dst_port, - frequency, - graph, - menu, - from, - to - ): Observable { - if (graph === 'latency') { - return this.httpClient.get( - `${ - environment.apiEndPoint - }/stats/isl/${src_switch}/${src_port}/${dst_switch}/${dst_port}/${from}/${to}/${frequency}/latency` - ); - } - if (graph === 'rtt') { - return this.httpClient.get( - `${ - environment.apiEndPoint - }/stats/isl/${src_switch}/${src_port}/${dst_switch}/${dst_port}/${from}/${to}/${frequency}/rtt` - ); - } - - if (graph === 'source') { - return this.httpClient.get( - `${ - environment.apiEndPoint - }/stats/switchid/${src_switch}/port/${src_port}/${from}/${to}/${frequency}/${menu}` - ); - } - - if (graph === 'target') { - return this.httpClient.get( - `${ - environment.apiEndPoint - }/stats/switchid/${dst_switch}/port/${dst_port}/${from}/${to}/${frequency}/${menu}` - ); - } - - if (graph === 'isllossforward') { - return this.httpClient.get( - `${ - environment.apiEndPoint - }/stats/isl/losspackets/${src_switch}/${src_port}/${dst_switch}/${dst_port}/${from}/${to}/${frequency}/${menu}` - ); - } - - if (graph === 'isllossreverse') { - return this.httpClient.get( - `${ - environment.apiEndPoint - }/stats/isl/losspackets/${dst_switch}/${dst_port}/${src_switch}/${src_port}/${from}/${to}/${frequency}/${menu}` - ); - } - } - getBackwardGraphData( - src_switch, - src_port, - dst_switch, - dst_port, - frequency, - graph, - from, - to - ): Observable { - if (graph === 'rtt') { - return this.httpClient.get( - `${ - environment.apiEndPoint - }/stats/isl/${dst_switch}/${dst_port}/${src_switch}/${src_port}/${from}/${to}/${frequency}/rtt` - ); - } - if (graph == 'latency') { - return this.httpClient.get( - `${ - environment.apiEndPoint - }/stats/isl/${dst_switch}/${dst_port}/${src_switch}/${src_port}/${from}/${to}/${frequency}/latency` - ); - } - - } changeMeterGraphData(graphData) { this.meterGraphSource.next(graphData); } @@ -192,9 +107,9 @@ export class DygraphService { graphData.push([startTime, ...addNullsToArray([], victoriaDataArr.length || 2)]); } - const fwdTimeToValueMap = victoriaDataArr[0] && victoriaDataArr[0].timeToValue ? victoriaDataArr[0].timeToValue : {}; + const fwdTimeToValueMap = victoriaDataArr[0] && victoriaDataArr[0].dps ? victoriaDataArr[0].dps : {}; const fwdTimeStamps = Object.keys(fwdTimeToValueMap); - const rvsTimeToValueMap = victoriaDataArr[1] && victoriaDataArr[1].timeToValue ? victoriaDataArr[1].timeToValue : {}; + const rvsTimeToValueMap = victoriaDataArr[1] && victoriaDataArr[1].dps ? victoriaDataArr[1].dps : {}; const rvsTimeStamps = Object.keys(rvsTimeToValueMap); fwdMetricDirectionLbl = victoriaDataArr[0] && victoriaDataArr[0].tags.direction ? `${victoriaDataArr[0].metric}(${victoriaDataArr[0].tags.direction})` : victoriaDataArr[0] ? victoriaDataArr[0].metric : ''; @@ -354,8 +269,8 @@ export class DygraphService { return constructedData; } - getCookieDataforFlowStats(data, type) { - const constructedData = []; + getCookieDataforFlowStats(data: VictoriaData[], type): VictoriaData[] { + const constructedData: VictoriaData[] = []; for (let i = 0; i < data.length; i++) { const cookieId = data[i].tags && data[i].tags['cookie'] ? data[i].tags['cookie'] : null; if (cookieId) { @@ -405,7 +320,7 @@ export class DygraphService { let timestampArray = []; const dpsArray = []; for (let j = 0; j < victoriaDataArr.length; j++) { - const dataValues = typeof victoriaDataArr[j] !== 'undefined' ? victoriaDataArr[j].timeToValue : null; + const dataValues = typeof victoriaDataArr[j] !== 'undefined' ? victoriaDataArr[j].dps : null; let metric = typeof victoriaDataArr[j] !== 'undefined' ? victoriaDataArr[j].metric : ''; metric = metric + '(switchid=' + victoriaDataArr[j].tags.switchid + ', meterid=' + victoriaDataArr[j].tags['meterid'] + ')'; @@ -551,7 +466,7 @@ export class DygraphService { return { labels: labels, data: graphData, color: color }; } - computeFlowPathGraphData(data, startDate, endDate, type, timezone) { + computeFlowPathGraphData(data: VictoriaData[], startDate, endDate, type, timezone) { const maxtrixArray = []; const labels = ['Date']; const color = []; diff --git a/src-gui/ui/src/app/common/services/flows.service.ts b/src-gui/ui/src/app/common/services/flows.service.ts index 95e9c200da8..6f23f0ebe3a 100644 --- a/src-gui/ui/src/app/common/services/flows.service.ts +++ b/src-gui/ui/src/app/common/services/flows.service.ts @@ -4,8 +4,6 @@ import { environment } from '../../../environments/environment'; import { Observable } from 'rxjs'; import { Flow } from '../data-models/flow'; import { CookieManagerService } from './cookie-manager.service'; -import {FlowMetricTsdb} from '../data-models/flowMetricTsdb'; -import {VictoriaStatsRes} from '../data-models/flowMetricVictoria'; @Injectable({ providedIn: 'root' @@ -83,48 +81,6 @@ export class FlowsService { return this.httpClient.get(`${environment.apiEndPoint}/y-flows/${yFlowId}/reroute`); } - getFlowPathStats(jsonPayload): Observable { - return this.httpClient.post(`${environment.apiEndPoint}/stats/flowpath`, jsonPayload); - } - - getFlowGraphData(flowid, convertedStartDate, convertedEndDate, downsampling, metric): Observable { - return this.httpClient.get(`${environment.apiEndPoint}/stats/flowid/${flowid}/${convertedStartDate}/${convertedEndDate}/${downsampling}/${metric}`); - } - - getFlowGraphVictoriaData(statsType: string, - flowid: string, - convertedStartDate: string, - convertedEndDate: string, - downsampling: string, - metrics: string[], - direction?: string): Observable { - const url = `${environment.apiEndPoint}/stats/victoria/${statsType}`; - - // Construct form data - const formData = new FormData(); - formData.append('flowId', flowid); - formData.append('startDate', convertedStartDate); - formData.append('endDate', convertedEndDate); - formData.append('step', downsampling); - metrics.forEach(metric => { - formData.append('metric', metric); - }); - if (direction && direction.trim() !== '') { - formData.append('direction', direction); - } - - // Make the POST request - return this.httpClient.post(url, formData); - } - - getMeterGraphData(flowid, convertedStartDate, convertedEndDate, downsampling, metric, direction): Observable { - return this.httpClient.get(`${environment.apiEndPoint}/stats/meter/${flowid}/${convertedStartDate}/${convertedEndDate}/${downsampling}/${metric}/${direction}`); - } - - getFlowPacketGraphData(flowid, convertedStartDate, convertedEndDate, downsampling, direction): Observable { - return this.httpClient.get(`${environment.apiEndPoint}/stats/flow/losspackets/${flowid}/${convertedStartDate}/${convertedEndDate}/${downsampling}/${direction}`); - } - getFlowCount(): Observable { return this.httpClient.get(`${environment.apiEndPoint}/flows/count`); } diff --git a/src-gui/ui/src/app/common/services/isl-detail.service.ts b/src-gui/ui/src/app/common/services/isl-detail.service.ts index 7ebab6deda8..e93263b058d 100644 --- a/src-gui/ui/src/app/common/services/isl-detail.service.ts +++ b/src-gui/ui/src/app/common/services/isl-detail.service.ts @@ -20,12 +20,4 @@ export class IslDetailService { getISLFlowsList(query?: any): Observable { return this.httpClient.get(`${environment.apiEndPoint}/switch/links/flows`, {params: query}); } - - getIslLatencyfromGraph(src_switch, src_port, dst_switch, dst_port, from, to, frequency) { - return this.httpClient.get( - `${ - environment.apiEndPoint - }/stats/isl/${src_switch}/${src_port}/${dst_switch}/${dst_port}/${from}/${to}/${frequency}/latency` - ); - } } diff --git a/src-gui/ui/src/app/common/services/stats.service.ts b/src-gui/ui/src/app/common/services/stats.service.ts new file mode 100644 index 00000000000..d02e62f0c95 --- /dev/null +++ b/src-gui/ui/src/app/common/services/stats.service.ts @@ -0,0 +1,349 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {CookieManagerService} from './cookie-manager.service'; +import {Observable} from 'rxjs'; +import {VictoriaStatsReq, VictoriaStatsRes} from '../data-models/flowMetricVictoria'; +import {environment} from '../../../environments/environment'; +import {FlowMetricTsdb} from '../data-models/flowMetricTsdb'; +import * as moment from 'moment'; +import {PortInfo} from '../data-models/port-info'; +import {concatMap, map} from 'rxjs/operators'; + + +@Injectable({ + providedIn: 'root' +}) +export class StatsService { + + constructor(private httpClient: HttpClient, private cookieManager: CookieManagerService) { + + } + + getFlowGraphVictoriaData(statsType: string, + flowid: string, + convertedStartDate: string, + convertedEndDate: string, + downsampling: string, + metrics: string[], + direction?: string): Observable { + const url = `${environment.apiEndPoint}/stats/victoria/${statsType}`; + + // Construct form data + const formData = new FormData(); + formData.append('flowId', flowid); + formData.append('startDate', convertedStartDate); + formData.append('endDate', convertedEndDate); + formData.append('step', downsampling); + metrics.forEach(metric => { + formData.append('metric', metric); + }); + if (direction && direction.trim() !== '') { + formData.append('direction', direction); + } + + // Make the POST request + return this.httpClient.post(url, formData); + } + + getFlowGraphData(flowid, convertedStartDate, convertedEndDate, downsampling, metric): Observable { + return this.httpClient.get( + `${environment.apiEndPoint}/stats/flowid/${flowid}/${convertedStartDate}/${convertedEndDate}/${downsampling}/${metric}`); + } + + getMeterGraphData(flowid, convertedStartDate, convertedEndDate, downsampling, metric, direction): Observable { + return this.httpClient.get(`${environment.apiEndPoint}/stats/meter/ + ${flowid}/${convertedStartDate}/${convertedEndDate}/${downsampling}/${metric}/${direction}`); + } + + getFlowPacketGraphData(flowid, convertedStartDate, convertedEndDate, downsampling, direction): Observable { + return this.httpClient.get(`${environment.apiEndPoint}/stats/flow/losspackets/ + ${flowid}/${convertedStartDate}/${convertedEndDate}/${downsampling}/${direction}`); + } + + + getFlowPathStats(jsonPayload: VictoriaStatsReq): Observable { + return this.httpClient.post(`${environment.apiEndPoint}/stats/common`, jsonPayload); + } + + getSwitchPortsStats(switchId): Observable { + const startDate = moment().utc().subtract(30, 'minutes').format('YYYY-MM-DD-HH:mm:ss'); + const endDate = moment().utc().format('YYYY-MM-DD-HH:mm:ss'); + + const requestPayload: VictoriaStatsReq = { + statsType: 'switchPort', + startDate: startDate, + endDate: endDate, + step: '30s', + labels: { + port: '*', + switchid: switchId + } + }; + return this.httpClient.post( + `${environment.apiEndPoint}/stats/switchports`, requestPayload); + } + + getPortGraphData(src_switch, + src_port, + dst_switch, + dst_port, + frequency, + metric, + from, + to): Observable { + const requestPayload: VictoriaStatsReq = { + metrics: [metric], + statsType: 'port', + startDate: from, + endDate: to, + step: frequency, + labels: { + port: src_port, + switchid: src_switch + } + }; + return this.httpClient.post(`${environment.apiEndPoint}/stats/common`, requestPayload); + } + + getForwardGraphData( + src_switch, + src_port, + dst_switch, + dst_port, + frequency, + graph, + metric, + from, + to + ): Observable { + if (graph === 'latency') { + const requestPayload: VictoriaStatsReq = { + metrics: ['latency'], + statsType: 'isl', + startDate: from, + endDate: to, + step: frequency, + labels: { + src_port: src_port, + src_switch: src_switch, + dst_port: dst_port, + dst_switch: dst_switch + } + }; + return this.httpClient.post(`${environment.apiEndPoint}/stats/common`, requestPayload); + } + if (graph === 'rtt') { + const requestPayload: VictoriaStatsReq = { + metrics: ['rtt'], + statsType: 'isl', + startDate: from, + endDate: to, + step: frequency, + labels: { + src_port: src_port, + src_switch: src_switch, + dst_port: dst_port, + dst_switch: dst_switch + } + }; + return this.httpClient.post(`${environment.apiEndPoint}/stats/common`, requestPayload); + } + + if (graph === 'source') { + const requestPayload: VictoriaStatsReq = { + metrics: [metric], + statsType: 'port', + startDate: from, + endDate: to, + step: frequency, + labels: { + port: src_port, + switchid: src_switch + } + }; + return this.httpClient.post(`${environment.apiEndPoint}/stats/common`, requestPayload); + } + + if (graph === 'target') { + const requestPayload: VictoriaStatsReq = { + metrics: [metric], + statsType: 'port', + startDate: from, + endDate: to, + step: frequency, + labels: { + port: dst_port, + switchid: dst_switch + } + }; + return this.httpClient.post(`${environment.apiEndPoint}/stats/common`, requestPayload); + } + } + + getBackwardGraphData( + src_switch, + src_port, + dst_switch, + dst_port, + frequency, + graph, + from, + to + ): Observable { + if (graph === 'rtt') { + const requestPayload: VictoriaStatsReq = { + metrics: ['rtt'], + statsType: 'isl', + startDate: from, + endDate: to, + step: frequency, + labels: { + src_port: dst_port, + src_switch: dst_switch, + dst_port: src_port, + dst_switch: src_switch + } + }; + return this.httpClient.post(`${environment.apiEndPoint}/stats/common`, requestPayload); + } + if (graph === 'latency') { + const requestPayload: VictoriaStatsReq = { + metrics: ['latency'], + statsType: 'isl', + startDate: from, + endDate: to, + step: frequency, + labels: { + src_port: dst_port, + src_switch: dst_switch, + dst_port: src_port, + dst_switch: src_switch + } + }; + return this.httpClient.post(`${environment.apiEndPoint}/stats/common`, requestPayload); + } + } + + getIslLossGraphData(src_switch, + src_port, + dst_switch, + dst_port, + step, + graph, + metric, + from, + to): Observable { + const metricList: string[] = []; + switch (metric) { + case 'bits': + metricList.push('switch.rx-bits'); + metricList.push('switch.tx-bits'); + break; + case 'bytes': + metricList.push('switch.rx-bytes'); + metricList.push('switch.tx-bytes'); + break; + case 'packets': + metricList.push('switch.rx-packets'); + metricList.push('switch.tx-packets'); + break; + case 'drops': + metricList.push('switch.rx-dropped'); + metricList.push('switch.tx-dropped'); + break; + case 'errors': + metricList.push('switch.rx-errors'); + metricList.push('switch.tx-errors'); + break; + case 'collisions': + metricList.push('switch.collisions'); + break; + case 'frameerror': + metricList.push('switch.rx-frame-error'); + break; + case 'overerror': + metricList.push('switch.rx-over-error'); + break; + case 'crcerror': + metricList.push('switch.rx-crc-error'); + break; + } + if (graph === 'isllossforward') { + const requestPayloadRx: VictoriaStatsReq = { + metrics: [metricList[0]], + startDate: from, + endDate: to, + step: step, + labels: { + switchid: dst_switch, + port: dst_port + } + }; + if (metricList.length === 2) { + const res1$ = this.httpClient.post(`${environment.apiEndPoint}/stats/common`, requestPayloadRx); + const requestPayloadTx: VictoriaStatsReq = { + metrics: [metricList[1]], + startDate: from, + endDate: to, + step: step, + labels: { + switchid: src_switch, + port: src_port + } + }; + const res2$ = this.httpClient.post(`${environment.apiEndPoint}/stats/common`, requestPayloadTx); + return res1$.pipe( + concatMap(res1 => { + return res2$.pipe( + map(res2 => { + res1.dataList = res1.dataList.concat(res2.dataList); + return res1; + }) + ); + }) + ); + } else if (metricList.length === 1) { + return this.httpClient.post(`${environment.apiEndPoint}/stats/common`, requestPayloadRx); + } + } + + if (graph === 'isllossreverse') { + const requestPayloadRx: VictoriaStatsReq = { + metrics: [metricList[0]], + startDate: from, + endDate: to, + step: step, + labels: { + switchid: src_switch, + port: src_port + } + }; + if (metricList.length === 2) { + const res1$ = this.httpClient.post(`${environment.apiEndPoint}/stats/common`, requestPayloadRx); + const requestPayloadTx: VictoriaStatsReq = { + metrics: [metricList[1]], + startDate: from, + endDate: to, + step: step, + labels: { + switchid: dst_switch, + port: dst_port + } + }; + const res2$ = this.httpClient.post(`${environment.apiEndPoint}/stats/common`, requestPayloadTx); + return res1$.pipe( + concatMap(res1 => { + return res2$.pipe( + map(res2 => { + res1.dataList = res1.dataList.concat(res2.dataList); + return res1; + }) + ); + }) + ); + } else if (metricList.length === 1) { + return this.httpClient.post(`${environment.apiEndPoint}/stats/common`, requestPayloadRx); + } + } + } +} diff --git a/src-gui/ui/src/app/common/services/switch.service.ts b/src-gui/ui/src/app/common/services/switch.service.ts index 15514a33cb3..cfe75160a7e 100644 --- a/src-gui/ui/src/app/common/services/switch.service.ts +++ b/src-gui/ui/src/app/common/services/switch.service.ts @@ -4,9 +4,7 @@ import { environment } from '../../../environments/environment'; import { Observable } from 'rxjs'; import { Switch } from '../data-models/switch'; import { catchError } from 'rxjs/operators'; -import * as _moment from 'moment'; import { CookieManagerService } from './cookie-manager.service'; -declare var moment: any; @Injectable({ @@ -28,14 +26,6 @@ export class SwitchService { return this.httpClient.get(`${environment.apiEndPoint}/switch/${switchId}/rules?_=${timestamp}`, {responseType: 'text'}); } - getSwitchPortsStats(switchId): Observable { - const timestamp = new Date().getTime(); - const endDate = moment().utc().format('YYYY-MM-DD-HH:mm:ss'); - const startDate = moment().utc().subtract(30, 'minutes').format('YYYY-MM-DD-HH:mm:ss'); - const downSample = '30s'; - return this.httpClient.get(`${environment.apiEndPoint}/stats/switchports/${switchId}/${startDate}/${endDate}/${downSample}?_=${timestamp}`); -} - getNetworkPath(source_switch, target_switch, strategy, max_latency) { const timestamp = new Date().getTime(); return this.httpClient.get(`${environment.apiEndPoint}/network/paths?src_switch=${source_switch}&dst_switch=${target_switch}&strategy=${strategy}&max_latency=${max_latency}&_=${timestamp}`); diff --git a/src-gui/ui/src/app/modules/flows/flow-add/flow-add.component.ts b/src-gui/ui/src/app/modules/flows/flow-add/flow-add.component.ts index 47e358476e1..9dc4832ee64 100644 --- a/src-gui/ui/src/app/modules/flows/flow-add/flow-add.component.ts +++ b/src-gui/ui/src/app/modules/flows/flow-add/flow-add.component.ts @@ -14,6 +14,7 @@ import { ModalconfirmationComponent } from '../../../common/components/modalconf import { CommonService } from 'src/app/common/services/common.service'; import { MessageObj } from 'src/app/common/constants/constants'; import { Message } from '@angular/compiler/src/i18n/i18n_ast'; +import {StatsService} from '../../../common/services/stats.service'; declare var jQuery: any; @@ -47,6 +48,7 @@ export class FlowAddComponent implements OnInit { private flowService: FlowsService, private toaster: ToastrService, private switchService: SwitchService, + private statsService: StatsService, private switchIdMaskPipe: SwitchidmaskPipe, private router: Router, private loaderService: LoaderService, @@ -157,33 +159,34 @@ export class FlowAddComponent implements OnInit { } this.loaderService.show('Loading Ports'); - this.switchService.getSwitchPortsStats(switchId).subscribe( + this.statsService.getSwitchPortsStats(switchId).subscribe( ports => { const filteredPorts = ports.filter(function(d) { return d.assignmenttype != 'ISL'; }); - let sortedPorts = filteredPorts.sort(function(a, b) { + const sortedPorts = filteredPorts.sort(function(a, b) { return a.port_number - b.port_number; }); - sortedPorts = sortedPorts.map(portInfo => { + const portNumbers = sortedPorts.map(portInfo => { return { label: portInfo.port_number, value: portInfo.port_number }; }); if (switchType == 'source_switch') { - this.sourcePorts = sortedPorts; - this.mainSourcePorts = sortedPorts; + this.sourcePorts = portNumbers; + this.mainSourcePorts = portNumbers; } else { - this.targetPorts = sortedPorts; - this.mainTargetPorts = sortedPorts; + this.targetPorts = portNumbers; + this.mainTargetPorts = portNumbers; } - if (sortedPorts.length == 0) { + if (portNumbers.length == 0) { this.toaster.info(MessageObj.no_ports, 'Info'); } this.loaderService.hide(); }, error => { - const errorMsg = error && error.error && error.error['error-auxiliary-message'] ? error.error['error-auxiliary-message'] : 'Unable to get port information'; + const errorMsg = error && error.error && error.error['error-auxiliary-message'] + ? error.error['error-auxiliary-message'] : 'Unable to get port information'; this.toaster.error(errorMsg, 'Error'); this.loaderService.hide(); } diff --git a/src-gui/ui/src/app/modules/flows/flow-edit/flow-edit.component.ts b/src-gui/ui/src/app/modules/flows/flow-edit/flow-edit.component.ts index 2927e915526..134502421b9 100644 --- a/src-gui/ui/src/app/modules/flows/flow-edit/flow-edit.component.ts +++ b/src-gui/ui/src/app/modules/flows/flow-edit/flow-edit.component.ts @@ -14,6 +14,7 @@ import { ModalconfirmationComponent } from '../../../common/components/modalconf import { ModalComponent } from '../../../common/components/modal/modal.component'; import { CommonService } from '../../../common/services/common.service'; import { MessageObj } from 'src/app/common/constants/constants'; +import {StatsService} from '../../../common/services/stats.service'; @Component({ selector: 'app-flow-edit', @@ -48,6 +49,7 @@ export class FlowEditComponent implements OnInit { private route: ActivatedRoute, private toaster: ToastrService, private switchService: SwitchService, + private statsService: StatsService, private switchIdMaskPipe: SwitchidmaskPipe, private loaderService: LoaderService, private modalService: NgbModal, @@ -179,26 +181,20 @@ export class FlowEditComponent implements OnInit { this.loaderService.show(MessageObj.load_ports); } - this.switchService.getSwitchPortsStats(switchId).subscribe( + this.statsService.getSwitchPortsStats(switchId).subscribe( ports => { - const filteredPorts = ports.filter(function(d) { - return d.assignmenttype != 'ISL'; + const filteredPorts = ports.filter(function(portInfo) { + return portInfo.assignmenttype != 'ISL'; }); - let sortedPorts = filteredPorts.sort(function(a, b) { + const sortedPorts = filteredPorts.sort(function(a, b) { return a.port_number - b.port_number; }); - sortedPorts = sortedPorts.map(portInfo => { - if (portInfo.port_number == this.flowDetail.source_port) { - return { - label: portInfo.port_number, - value: portInfo.port_number - }; - } + const labels = sortedPorts.map(portInfo => { return { label: portInfo.port_number, value: portInfo.port_number }; }); if (switchType == 'source_switch') { - this.sourcePorts = sortedPorts; - this.mainSourcePorts = sortedPorts; + this.sourcePorts = labels; + this.mainSourcePorts = labels; if (!flag) { this.flowEditForm.controls['source_port'].setValue(null); this.flowEditForm.controls['source_vlan'].setValue('0'); @@ -206,8 +202,8 @@ export class FlowEditComponent implements OnInit { } } else { - this.targetPorts = sortedPorts; - this.mainTargetPorts = sortedPorts; + this.targetPorts = labels; + this.mainTargetPorts = labels; if (!flag) { this.flowEditForm.controls['target_port'].setValue(null); this.flowEditForm.controls['target_vlan'].setValue('0'); @@ -215,7 +211,7 @@ export class FlowEditComponent implements OnInit { } } - if (sortedPorts.length == 0) { + if (labels.length == 0) { this.toaster.info(MessageObj.no_ports, 'Info'); if (switchType == 'source_switch') { this.flowEditForm.controls['source_port'].setValue(null); diff --git a/src-gui/ui/src/app/modules/flows/flow-graph/flow-graph.component.ts b/src-gui/ui/src/app/modules/flows/flow-graph/flow-graph.component.ts index 1d467ead81b..c335b71e226 100644 --- a/src-gui/ui/src/app/modules/flows/flow-graph/flow-graph.component.ts +++ b/src-gui/ui/src/app/modules/flows/flow-graph/flow-graph.component.ts @@ -1,9 +1,9 @@ import {AfterViewInit, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges} from '@angular/core'; import {DygraphService} from '../../../common/services/dygraph.service'; import {FormBuilder, FormGroup, Validators} from '@angular/forms'; -import {FlowsService} from '../../../common/services/flows.service'; import {ToastrService} from 'ngx-toastr'; import {CommonService} from 'src/app/common/services/common.service'; +import {StatsService} from '../../../common/services/stats.service'; declare var moment: any; @@ -26,7 +26,7 @@ export class FlowGraphComponent implements OnInit, AfterViewInit, OnDestroy, OnC constructor( private dygraphService: DygraphService, private formBuiler: FormBuilder, - private flowService: FlowsService, + private statsService: StatsService, private toaster: ToastrService, private commonService: CommonService ) { @@ -217,7 +217,7 @@ export class FlowGraphComponent implements OnInit, AfterViewInit, OnDestroy, OnC if (formdata.graph == 'flow') { if (formdata.victoriaSource) { - this.flowService.getFlowGraphVictoriaData( + this.statsService.getFlowGraphVictoriaData( 'flow', flowid, convertedStartDate, @@ -226,12 +226,12 @@ export class FlowGraphComponent implements OnInit, AfterViewInit, OnDestroy, OnC [metric]) .subscribe(handleSuccessForFlow, handleErrorForFlow); } else { - this.flowService.getFlowGraphData(flowid, convertedStartDate, convertedEndDate, downsampling, metric) + this.statsService.getFlowGraphData(flowid, convertedStartDate, convertedEndDate, downsampling, metric) .subscribe(handleSuccessForFlow, handleErrorForFlow); } } else if (formdata.graph == 'flowmeter') { if (formdata.victoriaSource) { - this.flowService.getFlowGraphVictoriaData( + this.statsService.getFlowGraphVictoriaData( 'meter', flowid, convertedStartDate, @@ -242,7 +242,7 @@ export class FlowGraphComponent implements OnInit, AfterViewInit, OnDestroy, OnC console.log(res); }); } else { - this.flowService + this.statsService .getMeterGraphData( flowid, convertedStartDate, @@ -255,7 +255,7 @@ export class FlowGraphComponent implements OnInit, AfterViewInit, OnDestroy, OnC } } else { // packet loss if (formdata.victoriaSource) { - this.flowService.getFlowGraphVictoriaData( + this.statsService.getFlowGraphVictoriaData( 'flow', flowid, convertedStartDate, @@ -265,7 +265,7 @@ export class FlowGraphComponent implements OnInit, AfterViewInit, OnDestroy, OnC direction) .subscribe(handleSuccessForFlow, handleErrorForFlow); } else { - this.flowService + this.statsService .getFlowPacketGraphData( flowid, convertedStartDate, diff --git a/src-gui/ui/src/app/modules/flows/flow-path-graph/flow-path-graph.component.ts b/src-gui/ui/src/app/modules/flows/flow-path-graph/flow-path-graph.component.ts index d1e033f5249..55b8df0c2ca 100644 --- a/src-gui/ui/src/app/modules/flows/flow-path-graph/flow-path-graph.component.ts +++ b/src-gui/ui/src/app/modules/flows/flow-path-graph/flow-path-graph.component.ts @@ -1,11 +1,12 @@ import { Component, OnInit, Input, AfterViewInit, OnDestroy } from '@angular/core'; -import { FlowsService } from '../../../common/services/flows.service'; import { DygraphService } from '../../../common/services/dygraph.service'; import { FormBuilder, FormGroup } from '@angular/forms'; import { ToastrService } from 'ngx-toastr'; import { SwitchidmaskPipe } from '../../../common/pipes/switchidmask.pipe'; import { ClipboardService } from 'ngx-clipboard'; import { MessageObj } from 'src/app/common/constants/constants'; +import {StatsService} from '../../../common/services/stats.service'; +import {VictoriaData, VictoriaStatsReq, VictoriaStatsRes} from '../../../common/data-models/flowMetricVictoria'; declare var moment: any; @Component({ @@ -33,7 +34,7 @@ export class FlowPathGraphComponent implements OnInit, AfterViewInit, OnDestroy }); constructor( - private flowService: FlowsService, + private statsService: StatsService, private dygraphService: DygraphService, private formBuilder: FormBuilder, private toaster: ToastrService, @@ -164,36 +165,35 @@ export class FlowPathGraphComponent implements OnInit, AfterViewInit, OnDestroy }); } const metric = formData.metric; - const requestPayload = { - flowid: this.data.flowid, - // switches: switches, - startdate: startDate, - enddate: endDate, - downsample: '30s', - direction: this.type, - metric: metric + const requestPayload: VictoriaStatsReq = { + metrics: [metric], + statsType: 'flowRawPacket', + startDate: startDate, + endDate: endDate, + step: '30s', + labels: { + flowid: this.data.flowid, + direction: this.type, + cookie: '*', + switchid: '*' + } }; - this.flowService.getFlowPathStats(requestPayload).subscribe( - response => { - const dataforgraph = this.dygraphService.getCookieDataforFlowStats(response, this.type); - const cookieBasedData = this.dygraphService.getCookieBasedData(response, this.type); - this.cookieData = Object.keys(cookieBasedData); - const data = (dataforgraph && dataforgraph.length) ? dataforgraph : [] ; - this.plotFlowPathGraph(data, fromDate, toDate, this.type, formData.timezone); - }, - error => { - const dataforgraph = this.dygraphService.getCookieDataforFlowStats([], this.type); - const cookieBasedData = this.dygraphService.getCookieBasedData([], this.type); - this.cookieData = Object(cookieBasedData).keys; - const data = (dataforgraph && dataforgraph.length) ? dataforgraph : []; - this.plotFlowPathGraph(data, fromDate, toDate, this.type, formData.timezone); - } - ); + this.statsService.getFlowPathStats(requestPayload).subscribe( + response => handleResponse(response.dataList), + error => handleResponse([])); + + const handleResponse = (response: VictoriaData[]) => { + const dataforgraph = this.dygraphService.getCookieDataforFlowStats(response, this.type); + const cookieBasedData = this.dygraphService.getCookieBasedData(response, this.type); + this.cookieData = Object.keys(cookieBasedData); + const data = (dataforgraph && dataforgraph.length) ? dataforgraph : [] ; + this.plotFlowPathGraph(data, fromDate, toDate, this.type, formData.timezone); + }; } - plotFlowPathGraph(data, startDate, endDate, type, timezone) { + plotFlowPathGraph(data: VictoriaData[], startDate, endDate, type, timezone) { const graph_data = this.dygraphService.computeFlowPathGraphData( data, startDate, 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 aa738b0a74a..aeda71e7edc 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 @@ -489,7 +489,7 @@

{{currentGraphName}}

-
+