Skip to content

Commit 4070069

Browse files
[NU-2009] feature: active scenarios limit (global and per processing type limits) (#7970)
1 parent b560a6f commit 4070069

File tree

62 files changed

+2415
-1046
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+2415
-1046
lines changed

.run/RunEnvForLocalDesigner.run.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
<option name="Make" enabled="true" />
1313
</method>
1414
</configuration>
15-
</component>
15+
</component>

designer/client/src/components/graph/node-modal/fragment-input-definition/settings/variants/fields/UserDefinedListInput.tsx

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
1-
import { useSelector } from "react-redux";
2-
import { getProcessingType } from "../../../../../../../reducers/selectors/graph";
3-
import { GenericValidationRequest } from "../../../../../../../actions/nk/adhocTesting";
4-
import { debounce } from "lodash";
5-
import { useSettings } from "../../SettingsProvider";
61
import { Box, Button, CircularProgress, FormControl, Stack } from "@mui/material";
2+
import { debounce } from "lodash";
73
import React, { useMemo, useRef, useState } from "react";
84
import type AceEditor from "react-ace";
9-
10-
115
import type { IAceEditor } from "react-ace/lib/types";
126
import { useTranslation } from "react-i18next";
7+
import { useSelector } from "react-redux";
8+
9+
import type { GenericValidationRequest } from "../../../../../../../actions/nk/adhocTesting";
1310
import HttpService from "../../../../../../../http/HttpService";
11+
import { getProcessingType } from "../../../../../../../reducers/selectors/graph";
1412
import type { NodeValidationError, ReturnedType, VariableTypes } from "../../../../../../../types";
1513
import { useDelayedEnterAction } from "../../../../../../toolbars/scenarioDetails/useDelayedEnterAction";
1614
import { SpelEditor } from "../../../../editors/expression/SpelEditor";
1715
import { ExpressionLang } from "../../../../editors/expression/types";
1816
import { getValidationErrorsForField, mandatoryValueValidator, uniqueValueValidator } from "../../../../editors/Validators";
1917
import type { FieldName, FixedValuesOption, onChangeType } from "../../../item";
18+
import { useSettings } from "../../SettingsProvider";
2019
import { ListItems } from "./ListItems";
2120
import { SettingLabelStyled } from "./StyledSettingsComponnets";
2221

designer/client/src/components/graph/node-modal/parametersListAdvanced.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { partition } from "lodash";
2-
import type { PropsWithChildren} from "react";
2+
import type { PropsWithChildren } from "react";
33
import React, { useCallback, useMemo } from "react";
44
import { useTranslation } from "react-i18next";
55

6-
import type { Parameter} from "../../../types";
6+
import type { Parameter } from "../../../types";
77
import { ParameterCategory } from "../../../types";
88
import { Expandable } from "../../common/Expandable";
99
import type { ParameterExpressionFieldProps } from "./ParameterExpressionField";

designer/deployment-manager-api/src/main/scala/pl/touk/nussknacker/engine/api/deployment/DeploymentManager.scala

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package pl.touk.nussknacker.engine.api.deployment
22

3-
import cats.effect.{IO, Resource, SyncIO}
3+
import cats.effect.{Resource, SyncIO}
44
import com.typesafe.config.Config
55
import pl.touk.nussknacker.engine.api.definition.EngineScenarioCompilationDependencies
66
import pl.touk.nussknacker.engine.api.deployment.scheduler.services._
@@ -35,7 +35,6 @@ trait DeploymentManager extends AutoCloseable {
3535

3636
protected final def notImplemented: Future[Nothing] =
3737
Future.failed(new NotImplementedError())
38-
3938
}
4039

4140
trait ManagerSpecificScenarioActivitiesStoredByManager { self: DeploymentManager =>

designer/deployment-manager-api/src/main/scala/pl/touk/nussknacker/engine/api/deployment/DeploymentStatus.scala

+39-18
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,9 @@ import io.circe.generic.extras.semiauto.deriveUnwrappedCodec
88
// Currently DeploymentStatus are limited set of allowed statuses. Only ProblemDeploymentStatus can have different
99
// descriptions depending on DM implementation. It makes implementation of logic based on statuses easier. In case
1010
// if we have requirement to make it more flexible, we can relax this restriction.
11-
sealed trait DeploymentStatus extends EnumEntry with UpperSnakecase {
12-
def name: DeploymentStatusName = DeploymentStatusName(entryName)
13-
}
14-
15-
sealed abstract class NoAttributesDeploymentStatus extends DeploymentStatus
16-
17-
final case class ProblemDeploymentStatus(description: String) extends DeploymentStatus {
18-
override def name: DeploymentStatusName = ProblemDeploymentStatus.name
19-
}
11+
sealed trait DeploymentStatus extends EnumEntry with UpperSnakecase
12+
sealed abstract class NoAttributesDeploymentStatus extends DeploymentStatus
13+
final case class ProblemDeploymentStatus(problemDescription: String) extends DeploymentStatus
2014

2115
object DeploymentStatus extends Enum[DeploymentStatus] {
2216

@@ -41,19 +35,45 @@ object DeploymentStatus extends Enum[DeploymentStatus] {
4135
case object DuringCancel extends NoAttributesDeploymentStatus
4236
case object Canceled extends NoAttributesDeploymentStatus
4337

44-
}
38+
implicit class IsActive(val status: DeploymentStatus) extends AnyVal {
4539

46-
object ProblemDeploymentStatus {
47-
def name: DeploymentStatusName = DeploymentStatusName("PROBLEM")
40+
def isActive: Boolean = {
41+
status match {
42+
case DuringDeploy | Running | Restarting => true
43+
case Finished | DuringCancel | Canceled | ProblemDeploymentStatus(_) => false
44+
}
45+
}
46+
47+
}
4848

49-
def extractDescription(status: DeploymentStatus): Option[String] =
50-
status match {
51-
case problem: ProblemDeploymentStatus =>
52-
Some(problem.description)
53-
case _: NoAttributesDeploymentStatus =>
54-
None
49+
implicit class ProblemDescription(val status: DeploymentStatus) extends AnyVal {
50+
51+
def problemDescription: Option[String] = status match {
52+
case _: NoAttributesDeploymentStatus => None
53+
case ProblemDeploymentStatus(problemDescription) => Some(problemDescription)
5554
}
5655

56+
}
57+
58+
implicit class ToDeploymentStatusName(val status: DeploymentStatus) extends AnyVal {
59+
60+
def name: DeploymentStatusName = status match {
61+
case status: NoAttributesDeploymentStatus => DeploymentStatusName(status.entryName)
62+
case ProblemDeploymentStatus(_) => DeploymentStatusName.problemStatusName
63+
}
64+
65+
}
66+
67+
def from(name: DeploymentStatusName, description: Option[String]): DeploymentStatus = {
68+
name match {
69+
case DeploymentStatusName.problemStatusName =>
70+
val desc = description.getOrElse(throw new IllegalStateException("No description for ProblemDeploymentStatus"))
71+
ProblemDeploymentStatus(desc)
72+
case other =>
73+
DeploymentStatus.withName(other.value)
74+
}
75+
}
76+
5777
}
5878

5979
final case class DeploymentStatusName(value: String) {
@@ -64,4 +84,5 @@ object DeploymentStatusName {
6484

6585
implicit val codec: Codec[DeploymentStatusName] = deriveUnwrappedCodec[DeploymentStatusName]
6686

87+
val problemStatusName: DeploymentStatusName = DeploymentStatusName("PROBLEM")
6788
}

designer/deployment-manager-api/src/main/scala/pl/touk/nussknacker/engine/api/deployment/simple/SimpleProcessStateDefinitionManager.scala

+9-9
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ import pl.touk.nussknacker.engine.api.deployment.StateStatus.StatusName
1212
import pl.touk.nussknacker.engine.api.deployment.simple.SimpleStateStatus.{statusActionsPF, ProblemStateStatus}
1313

1414
/**
15-
* Base [[ProcessStateDefinitionManager]] with basic state definitions and state transitions.
16-
* Provides methods to handle erroneous edge cases.
17-
* @see [[SimpleStateStatus]]
18-
*/
15+
* Base [[ProcessStateDefinitionManager]] with basic state definitions and state transitions.
16+
* Provides methods to handle erroneous edge cases.
17+
*
18+
* @see [[SimpleStateStatus]]
19+
*/
1920
object SimpleProcessStateDefinitionManager extends ProcessStateDefinitionManager {
2021

2122
override def statusActions(input: ScenarioStatusWithScenarioContext): Set[ScenarioActionName] =
@@ -26,16 +27,15 @@ object SimpleProcessStateDefinitionManager extends ProcessStateDefinitionManager
2627
)
2728

2829
private[nussknacker] def statusDescription(status: StateStatus): String = status match {
29-
case _ @ProblemStateStatus(message, _, _) => message
30-
case _ => SimpleStateStatus.definitions(status.name).description
30+
case s: ProblemStateStatus => s.description
31+
case _ => SimpleStateStatus.definitions(status.name).description
3132
}
3233

3334
override def statusTooltip(input: ScenarioStatusWithScenarioContext): String = statusTooltip(input.scenarioStatus)
3435

3536
private[nussknacker] def statusTooltip(status: StateStatus): String = status match {
36-
case _ @ProblemStateStatus(message, _, Some(tooltip)) => tooltip
37-
case _ @ProblemStateStatus(message, _, _) => message
38-
case _ => SimpleStateStatus.definitions(status.name).tooltip
37+
case s: ProblemStateStatus => s.tooltip.getOrElse(s.description)
38+
case _ => SimpleStateStatus.definitions(status.name).tooltip
3939
}
4040

4141
override def stateDefinitions: Map[StatusName, StateDefinitionDetails] =

designer/deployment-manager-api/src/main/scala/pl/touk/nussknacker/engine/api/deployment/simple/SimpleStateStatus.scala

+79-35
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ package pl.touk.nussknacker.engine.api.deployment.simple
22

33
import pl.touk.nussknacker.engine.api.deployment._
44
import pl.touk.nussknacker.engine.api.deployment.StateStatus.StatusName
5-
import pl.touk.nussknacker.engine.api.deployment.simple.SimpleStateStatus.ProblemStateStatus.defaultActions
5+
import pl.touk.nussknacker.engine.api.deployment.simple.SimpleStateStatus.ProblemStateStatus.{
6+
GeneralProblemStateStatus,
7+
MultipleJobsRunning,
8+
ShouldNotBeRunning
9+
}
610
import pl.touk.nussknacker.engine.api.process.VersionId
711
import pl.touk.nussknacker.engine.deployment.DeploymentId
812

@@ -12,23 +16,56 @@ object SimpleStateStatus {
1216

1317
def fromDeploymentStatus(deploymentStatus: DeploymentStatus): StateStatus = {
1418
deploymentStatus match {
15-
case noAttributes: NoAttributesDeploymentStatus => NoAttributesStateStatus(noAttributes.name.value)
19+
case status: NoAttributesDeploymentStatus => NoAttributesStateStatus(status.name.value)
1620
// We assume that all deployment status have default allowedActions. Non-default allowedActions have only
1721
// statuses that are not deployment statuses but scenario statuses.
18-
case problem: ProblemDeploymentStatus => ProblemStateStatus(problem.description)
22+
case status: ProblemDeploymentStatus => GeneralProblemStateStatus(status.problemDescription)
1923
}
2024
}
2125

22-
// Represents general problem.
23-
final case class ProblemStateStatus(
24-
description: String,
25-
allowedActions: Set[ScenarioActionName] = defaultActions,
26-
tooltip: Option[String] = None
27-
) extends StateStatus {
26+
sealed trait ProblemStateStatus extends StateStatus {
2827
override def name: StatusName = ProblemStateStatus.name
28+
29+
def description: String
30+
def allowedActions: Set[ScenarioActionName]
31+
def tooltip: Option[String]
2932
}
3033

3134
object ProblemStateStatus {
35+
36+
final case class ShouldNotBeRunning(deployed: Boolean) extends ProblemStateStatus {
37+
38+
override val description: String =
39+
if (deployed) "Scenario has been canceled but still is running."
40+
else "Scenario has been never deployed but now is running."
41+
42+
override val allowedActions: Set[ScenarioActionName] = defaultActions
43+
override val tooltip: Option[String] = None
44+
}
45+
46+
final case class MultipleJobsRunning(
47+
firstNonFinalDeployment: (DeploymentId, StateStatus),
48+
secondNonFinalDeployment: (DeploymentId, StateStatus),
49+
otherNonFinalDeployments: (DeploymentId, StateStatus)*
50+
) extends ProblemStateStatus {
51+
52+
override val allowedActions: Set[ScenarioActionName] = Set(ScenarioActionName.Cancel)
53+
override val description: String = "More than one deployment is running."
54+
55+
override val tooltip: Option[StatusName] = Some {
56+
(firstNonFinalDeployment :: secondNonFinalDeployment :: otherNonFinalDeployments.toList)
57+
.map { case (deploymentId, deploymentStatus) => s"$deploymentId - $deploymentStatus" }
58+
.mkString("Expected one job, instead: ", ", ", "")
59+
}
60+
61+
}
62+
63+
final case class GeneralProblemStateStatus(
64+
override val description: String,
65+
override val allowedActions: Set[ScenarioActionName] = defaultActions,
66+
override val tooltip: Option[String] = None
67+
) extends ProblemStateStatus
68+
3269
val name: String = "PROBLEM"
3370

3471
def isProblemStatus(status: StateStatus): Boolean = status.name == name
@@ -40,48 +77,54 @@ object SimpleStateStatus {
4077

4178
// Problem factory methods
4279

43-
val Failed: ProblemStateStatus = ProblemStateStatus(defaultDescription)
80+
val Failed: ProblemStateStatus = GeneralProblemStateStatus(defaultDescription)
4481

4582
val ArchivedShouldBeCanceled: ProblemStateStatus =
46-
ProblemStateStatus("Archived scenario should be canceled.", Set(ScenarioActionName.Cancel))
83+
GeneralProblemStateStatus("Archived scenario should be canceled.", Set(ScenarioActionName.Cancel))
4784

4885
val FailedToGet: ProblemStateStatus =
49-
ProblemStateStatus(s"Failed to get a state of the scenario.")
86+
GeneralProblemStateStatus(s"Failed to get a state of the scenario.")
5087

5188
def shouldBeRunning(deployedVersionId: VersionId, user: String): ProblemStateStatus =
52-
ProblemStateStatus(s"Scenario deployed in version $deployedVersionId by $user is not running.")
89+
GeneralProblemStateStatus(s"Scenario deployed in version $deployedVersionId by $user is not running.")
5390

5491
def mismatchDeployedVersion(
5592
deployedVersionId: VersionId,
5693
exceptedVersionId: VersionId,
5794
user: String
5895
): ProblemStateStatus =
59-
ProblemStateStatus(
96+
GeneralProblemStateStatus(
6097
s"Scenario deployed in version $deployedVersionId by $user, expected version $exceptedVersionId."
6198
)
6299

63-
def shouldNotBeRunning(deployed: Boolean): ProblemStateStatus = {
64-
val shouldNotBeRunningMessage =
65-
if (deployed) "Scenario has been canceled but still is running."
66-
else "Scenario has been never deployed but now is running."
67-
ProblemStateStatus(shouldNotBeRunningMessage)
68-
}
100+
def shouldNotBeRunning(deployed: Boolean): ProblemStateStatus =
101+
ShouldNotBeRunning(deployed)
69102

70103
def missingDeployedVersion(exceptedVersionId: VersionId, user: String): ProblemStateStatus =
71-
ProblemStateStatus(s"Scenario deployed without version by $user, expected version $exceptedVersionId.")
72-
73-
def multipleJobsRunning(nonFinalDeploymentIds: List[(DeploymentId, StateStatus)]): ProblemStateStatus =
74-
ProblemStateStatus(
75-
description = "More than one deployment is running.",
76-
allowedActions = Set(ScenarioActionName.Cancel),
77-
tooltip = Some(
78-
nonFinalDeploymentIds
79-
.map { case (deploymentId, deploymentStatus) =>
80-
s"$deploymentId - $deploymentStatus"
81-
}
82-
.mkString("Expected one job, instead: ", ", ", "")
83-
)
84-
)
104+
GeneralProblemStateStatus(s"Scenario deployed without version by $user, expected version $exceptedVersionId.")
105+
106+
def multipleJobsRunning(
107+
first: (DeploymentId, StateStatus),
108+
second: (DeploymentId, StateStatus),
109+
others: (DeploymentId, StateStatus)*
110+
): ProblemStateStatus =
111+
MultipleJobsRunning(first, second, others: _*)
112+
113+
}
114+
115+
implicit class CanBeConsideredAsActiveStatus(val status: StateStatus) extends AnyVal {
116+
117+
def isActive: Boolean = {
118+
status match {
119+
case problemStatus: ProblemStateStatus =>
120+
problemStatus match {
121+
case _: ShouldNotBeRunning | _: MultipleJobsRunning => true
122+
case _: GeneralProblemStateStatus => false
123+
}
124+
case `Restarting` => true
125+
case status => DefaultFollowingDeployStatuses.contains(status)
126+
}
127+
}
85128

86129
}
87130

@@ -119,7 +162,8 @@ object SimpleStateStatus {
119162
Set(ScenarioActionName.Deploy, ScenarioActionName.Cancel)
120163
// When Failed - process is in terminal state in Flink and it doesn't require any cleanup in Flink, but in NK it does
121164
// - that's why Cancel action is available
122-
case SimpleStateStatus.ProblemStateStatus(_, allowedActions, _) => allowedActions
165+
case s: ShouldNotBeRunning => s.allowedActions
166+
case GeneralProblemStateStatus(_, allowedActions, _) => allowedActions
123167
}
124168

125169
val definitions: Map[StatusName, StateDefinitionDetails] = Map(

designer/server/src/main/scala/pl/touk/nussknacker/ui/api/DeploymentApiHttpService.scala

+5-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ class DeploymentApiHttpService(
5757
ScenarioGraphValidationError(errors)
5858
case DeploymentService.DeployValidationError(message) =>
5959
DeployValidationError(message)
60+
case DeploymentService.MaxActiveScenariosCountExceededError(maxCount) =>
61+
DeployValidationError(
62+
s"The limit of active scenarios has been reached. You can have a maximum of $maxCount active scenarios."
63+
)
6064
}
6165
case ActivityService.CommentValidationError(message) => CommentValidationError(message)
6266
})
@@ -75,7 +79,7 @@ class DeploymentApiHttpService(
7579
_.map { statusWithModifiedAt =>
7680
GetDeploymentStatusResponse(
7781
statusWithModifiedAt.value.name,
78-
ProblemDeploymentStatus.extractDescription(statusWithModifiedAt.value),
82+
statusWithModifiedAt.value.problemDescription,
7983
statusWithModifiedAt.modifiedAt.toInstant
8084
)
8185
}.left.map {

designer/server/src/main/scala/pl/touk/nussknacker/ui/api/description/DeploymentApiEndpoints.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ class DeploymentApiEndpoints(auth: EndpointInput[AuthCredentials]) extends BaseE
138138
Example.of(
139139
GetDeploymentStatusResponse(
140140
DeploymentStatus.Problem.Failed.name,
141-
Some(DeploymentStatus.Problem.Failed.description),
141+
Some(DeploymentStatus.Problem.Failed.problemDescription),
142142
exampleInstant
143143
),
144144
Some("PROBLEM status")

0 commit comments

Comments
 (0)