Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #23469: Reporting by node is not correct on directives and Rules #5050

Draft
wants to merge 1 commit into
base: branches/rudder/7.3
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,8 @@ final case class BlockStatusReport(
subComponents.find(_.componentName == componentName).toList :::
subComponents.collect { case g: BlockStatusReport => g }.flatMap(_.findChildren(componentName))
}
def compliance: ComplianceLevel = {

override lazy val compliance: ComplianceLevel = {
import ReportingLogic._
reportingLogic match {
// simple weighted compliance, as usual
Expand All @@ -343,23 +344,25 @@ final case class BlockStatusReport(
}
ComplianceLevel.compute(kept)
// focus on a given sub-component name (can be present several time, or 0 which leads to N/A)
case FocusReport(component) => ComplianceLevel.sum(findChildren(component).map(_.compliance))
case FocusReport(component) =>
ComplianceLevel.sum(findChildren(component).map(_.compliance))
}
}

def getValues(predicate: ComponentValueStatusReport => Boolean): Seq[ComponentValueStatusReport] = {
subComponents.flatMap(_.getValues(predicate))
}

def componentValues: List[ComponentValueStatusReport] = ComponentValueStatusReport.merge(getValues(_ => true))
def withFilteredElement(predicate: ComponentValueStatusReport => Boolean): Option[ComponentStatusReport] = {
val componentValues: List[ComponentValueStatusReport] = ComponentValueStatusReport.merge(getValues(_ => true))

def withFilteredElement(predicate: ComponentValueStatusReport => Boolean): Option[ComponentStatusReport] = {
subComponents.flatMap(_.withFilteredElement(predicate)) match {
case Nil => None
case l => Some(this.copy(subComponents = l))
}
}

def status: ReportType = {
val status: ReportType = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are oscillating between def and val because val (or lazy val) makes the number of allocation/memory consumption exploses. Did you look in history to see if we did the opposite change at some point?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should at least be lazy val I believe (like other status field like in ComponentValueStatusReport, in all case not a val since it override a base field, it may lead to NPE)

reportingLogic match {
case WorstReportWeightedOne | WorstReportWeightedSum | WeightedReport =>
ReportType.getWorseType(subComponents.map(_.status))
Expand All @@ -370,22 +373,23 @@ final case class BlockStatusReport(
}
final case class ValueStatusReport(
componentName: String,
expectedComponentName: String, // only one ComponentValueStatusReport by valuex.

componentValues: List[ComponentValueStatusReport]
expectedComponentName: String, // only one ComponentValueStatusReport by values.
componentValues: List[ComponentValueStatusReport]
) extends ComponentStatusReport {

override def toString() = s"${componentName}:${componentValues.toSeq.sortBy(_.componentValue).mkString("[", ",", "]")}"

override lazy val compliance = ComplianceLevel.sum(componentValues.map(_.compliance))

/*
* Get all values matching the predicate
*/
def getValues(predicate: ComponentValueStatusReport => Boolean): Seq[ComponentValueStatusReport] = {
componentValues.filter(v => predicate(v)).toSeq
}

def status: ReportType = ReportType.getWorseType(getValues(_ => true).map(_.status))
val status: ReportType = ReportType.getWorseType(getValues(_ => true).map(_.status))

/*
* Rebuild a componentStatusReport, keeping only values matching the predicate
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@

package com.normation.rudder.rest.data

import com.normation.cfclerk.domain.ReportingLogic
import com.normation.cfclerk.domain.ReportingLogic.FocusReport
import com.normation.cfclerk.domain.ReportingLogic.WeightedReport
import com.normation.cfclerk.domain.ReportingLogic.WorstReportWeightedOne
import com.normation.cfclerk.domain.ReportingLogic.WorstReportWeightedSum
import com.normation.cfclerk.domain.WorstReportReportingLogic
import com.normation.inventory.domain.NodeId
import com.normation.rudder.domain.policies.DirectiveId
import com.normation.rudder.domain.policies.PolicyMode
Expand Down Expand Up @@ -131,27 +137,143 @@ final case class ByRuleDirectiveCompliance(
sealed trait ByRuleComponentCompliance {
def name: String
def compliance: ComplianceLevel

def getValues(predicate: ByRuleNodeCompliance => Boolean): Seq[ByRuleNodeCompliance]

def withFilteredElement(predicate: ByRuleNodeCompliance => Boolean): Option[ByRuleComponentCompliance]

def componentValues: List[ByRuleNodeCompliance]

def componentValues(v: String): List[ByRuleNodeCompliance] = componentValues.filter(_.values.exists(_.componentValue == v))

def status: ReportType
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the meaning of status here is "the worst report type in sub component, based on target rules?"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact i don't think it's used by component but i may be wrong, but for value it's is the worst in all cases. Probably in a block it should depends on reporting logic, But I'm not sure it is really used for a block
.

}

object ByRuleComponentCompliance {

def merge(components: Iterable[ByRuleComponentCompliance]): List[ByRuleComponentCompliance] = {
components.groupBy(_.name).flatMap {
case (cptName, reports) =>
val valueComponents = reports.collect { case c: ByRuleValueCompliance => c }.toList
ByRuleValueCompliance(cptName, valueComponents.flatMap(_.nodes))

val groupComponent = reports.collect { case c: ByRuleBlockCompliance => c }.toList match {
case Nil => Nil
case r =>
import ReportingLogic._
val reportingLogic = r
.map(_.reportingLogic)
.reduce((a, b) => {
(a, b) match {
case (WorstReportWeightedOne, _) | (_, WorstReportWeightedOne) => WorstReportWeightedOne
case (WorstReportWeightedSum, _) | (_, WorstReportWeightedSum) => WorstReportWeightedSum
case (WeightedReport, _) | (_, WeightedReport) => WeightedReport
case (FocusReport(a), _) => FocusReport(a)
}
})
ByRuleBlockCompliance(cptName, ByRuleComponentCompliance.merge(r.flatMap(_.subComponents)), reportingLogic) :: Nil
}
groupComponent ::: valueComponents

}
}.toList
}

final case class ByRuleBlockCompliance(
name: String,
compliance: ComplianceLevel,
subComponents: Seq[ByRuleComponentCompliance]
) extends ByRuleComponentCompliance
name: String,
subComponents: Seq[ByRuleComponentCompliance],
reportingLogic: ReportingLogic
) extends ByRuleComponentCompliance {
def findChildren(componentName: String): List[ByRuleComponentCompliance] = {
subComponents.find(_.name == componentName).toList :::
subComponents.collect { case g: ByRuleBlockCompliance => g }.flatMap(_.findChildren(componentName)).toList
}

override lazy val compliance: ComplianceLevel = {
import ReportingLogic._
reportingLogic match {
// simple weighted compliance, as usual
case WeightedReport => ComplianceLevel.sum(subComponents.map(_.compliance))
// worst case bubble up, and its weight can be either 1 or the sum of sub-component weight
case worst: WorstReportReportingLogic =>
val worstReport = ReportType.getWorseType(subComponents.map(_.status))
val allReports = getValues(_ => true).flatMap(_.values.flatMap(_.messages.map(_ => worstReport)))
val kept = worst match {
case WorstReportWeightedOne => allReports.take(1)
case WorstReportWeightedSum => allReports
}
ComplianceLevel.compute(kept)
// focus on a given sub-component name (can be present several time, or 0 which leads to N/A)
case FocusReport(component) =>
ComplianceLevel.sum(findChildren(component).map(_.compliance))
}
}

def getValues(predicate: ByRuleNodeCompliance => Boolean): Seq[ByRuleNodeCompliance] = {
subComponents.flatMap(_.getValues(predicate))
}

val componentValues: List[ByRuleNodeCompliance] = getValues(_ => true).toList

def withFilteredElement(predicate: ByRuleNodeCompliance => Boolean): Option[ByRuleComponentCompliance] = {
subComponents.flatMap(_.withFilteredElement(predicate)) match {
case Nil => None
case l => Some(this.copy(subComponents = l))
}
}

val status: ReportType = {
reportingLogic match {
case WorstReportWeightedOne | WorstReportWeightedSum | WeightedReport =>
ReportType.getWorseType(subComponents.map(_.status))
case FocusReport(component) =>
ReportType.getWorseType(findChildren(component).map(_.status))
}
}

}

final case class ByRuleValueCompliance(
name: String,
compliance: ComplianceLevel,
nodes: Seq[ByRuleNodeCompliance]
) extends ByRuleComponentCompliance
name: String,
// compliance: ComplianceLevel,
nodes: Seq[ByRuleNodeCompliance]
) extends ByRuleComponentCompliance {

override lazy val compliance = ComplianceLevel.sum(componentValues.map(_.compliance))

/*
* Get all values matching the predicate
*/
def getValues(predicate: ByRuleNodeCompliance => Boolean): Seq[ByRuleNodeCompliance] = {
nodes.filter(v => predicate(v)).toSeq
}

val status: ReportType = ReportType.getWorseType(getValues(_ => true).map(_.status))

val componentValues = nodes.toList

/*
* Rebuild a componentStatusReport, keeping only values matching the predicate
*/
def withFilteredElement(predicate: ByRuleNodeCompliance => Boolean): Option[ByRuleComponentCompliance] = {
val values = componentValues.filter { case v => predicate(v) }
if (values.isEmpty) None
else Some(this.copy(nodes = values))
}

}

final case class ByRuleNodeCompliance(
id: NodeId,
name: String,
mode: Option[PolicyMode],
compliance: ComplianceLevel,
values: Seq[ComponentValueStatusReport]
)
id: NodeId,
name: String,
mode: Option[PolicyMode],
// compliance: ComplianceLevel,
values: Seq[ComponentValueStatusReport]
) {
lazy val compliance = ComplianceLevel.sum(values.map(_.compliance))

val status: ReportType = ReportType.getWorseType(values.map(_.status))
}

/* This is the same compliance structure than ByRuleByDirectiveCompliance except that the entry point is a Node
The full hierarchy is
Expand Down Expand Up @@ -179,19 +301,101 @@ final case class ByRuleByNodeByDirectiveCompliance(
sealed trait ByRuleByNodeByDirectiveByComponentCompliance {
def name: String
def compliance: ComplianceLevel

def getValues(predicate: ComponentValueStatusReport => Boolean): Seq[ComponentValueStatusReport]

def withFilteredElement(predicate: ComponentValueStatusReport => Boolean): Option[ByRuleByNodeByDirectiveByComponentCompliance]

def componentValues: List[ComponentValueStatusReport]

def componentValues(v: String): List[ComponentValueStatusReport] = componentValues.filter(_.componentValue == v)

def status: ReportType
}

final case class ByRuleByNodeByDirectiveByBlockCompliance(
name: String,
compliance: ComplianceLevel,
subComponents: Seq[ByRuleByNodeByDirectiveByComponentCompliance]
) extends ByRuleByNodeByDirectiveByComponentCompliance
name: String,
subComponents: Seq[ByRuleByNodeByDirectiveByComponentCompliance],
reportingLogic: ReportingLogic
) extends ByRuleByNodeByDirectiveByComponentCompliance {

def findChildren(componentName: String): List[ByRuleByNodeByDirectiveByComponentCompliance] = {
subComponents.find(_.name == componentName).toList :::
subComponents.collect { case g: ByRuleByNodeByDirectiveByBlockCompliance => g }.flatMap(_.findChildren(componentName)).toList
}

override lazy val compliance: ComplianceLevel = {
import ReportingLogic._
reportingLogic match {
// simple weighted compliance, as usual
case WeightedReport => ComplianceLevel.sum(subComponents.map(_.compliance))
// worst case bubble up, and its weight can be either 1 or the sum of sub-component weight
case worst: WorstReportReportingLogic =>
val worstReport = ReportType.getWorseType(subComponents.map(_.status))
val allReports = getValues(_ => true).flatMap(_.messages.map(_ => worstReport))
val kept = worst match {
case WorstReportWeightedOne => allReports.take(1)
case WorstReportWeightedSum => allReports
}
ComplianceLevel.compute(kept)
// focus on a given sub-component name (can be present several time, or 0 which leads to N/A)
case FocusReport(component) =>
ComplianceLevel.sum(findChildren(component).map(_.compliance))
}
}

def getValues(predicate: ComponentValueStatusReport => Boolean): Seq[ComponentValueStatusReport] = {
subComponents.flatMap(_.getValues(predicate))
}

val componentValues: List[ComponentValueStatusReport] = ComponentValueStatusReport.merge(getValues(_ => true))

def withFilteredElement(
predicate: ComponentValueStatusReport => Boolean
): Option[ByRuleByNodeByDirectiveByComponentCompliance] = {
subComponents.flatMap(_.withFilteredElement(predicate)) match {
case Nil => None
case l => Some(this.copy(subComponents = l))
}
}

val status: ReportType = {
reportingLogic match {
case WorstReportWeightedOne | WorstReportWeightedSum | WeightedReport =>
ReportType.getWorseType(subComponents.map(_.status))
case FocusReport(component) =>
ReportType.getWorseType(findChildren(component).map(_.status))
}
}

}

final case class ByRuleByNodeByDirectiveByValueCompliance(
name: String,
compliance: ComplianceLevel,
values: Seq[ComponentValueStatusReport]
) extends ByRuleByNodeByDirectiveByComponentCompliance
name: String,
componentValues: List[ComponentValueStatusReport]
) extends ByRuleByNodeByDirectiveByComponentCompliance {
override lazy val compliance = ComplianceLevel.sum(componentValues.map(_.compliance))

/*
* Get all values matching the predicate
*/
def getValues(predicate: ComponentValueStatusReport => Boolean): Seq[ComponentValueStatusReport] = {
componentValues.filter(v => predicate(v)).toSeq
}

val status: ReportType = ReportType.getWorseType(getValues(_ => true).map(_.status))

/*
* Rebuild a componentStatusReport, keeping only values matching the predicate
*/
def withFilteredElement(
predicate: ComponentValueStatusReport => Boolean
): Option[ByRuleByNodeByDirectiveByComponentCompliance] = {
val values = componentValues.filter { case v => predicate(v) }
if (values.isEmpty) None
else Some(this.copy(componentValues = values))
}
}

object GroupComponentCompliance {
// This function do the recursive treatment of components, we will have each time a pair of Sequence of tuple (NodeId , component compliance structure)
Expand All @@ -212,7 +416,8 @@ object GroupComponentCompliance {
// All subComponents are regrouped by Node, rebuild our block for each node
case (nodeId, s) =>
val subs = s.map(_._2)
(nodeId, ByRuleByNodeByDirectiveByBlockCompliance(b.name, ComplianceLevel.sum(subs.map(_.compliance)), subs))

(nodeId, ByRuleByNodeByDirectiveByBlockCompliance(b.name, subs, b.reportingLogic))
}
.toSeq
// Value case
Expand All @@ -227,8 +432,7 @@ object GroupComponentCompliance {
(nodeId, data.map(_.name).headOption.getOrElse(nodeId.value), data.map(_.mode).headOption.getOrElse(None)),
ByRuleByNodeByDirectiveByValueCompliance(
v.name,
ComplianceLevel.sum(data.map(_.compliance)),
data.flatMap(_.values)
data.flatMap(_.values).toList
)
)
}).toSeq
Expand Down Expand Up @@ -492,7 +696,7 @@ object JsonCompliance {
case component: ByRuleByNodeByDirectiveByBlockCompliance =>
("components" -> byNodeByDirectiveByComponents(component.subComponents, level, precision))
case component: ByRuleByNodeByDirectiveByValueCompliance =>
("values" -> values(component.values, level))
("values" -> values(component.componentValues, level))
})
)
})
Expand Down Expand Up @@ -724,7 +928,7 @@ object JsonCompliance {
case component: ByRuleByNodeByDirectiveByBlockCompliance =>
("components" -> byNodeByDirectiveByComponents(component.subComponents, level, precision))
case component: ByRuleByNodeByDirectiveByValueCompliance =>
("values" -> values(component.values, level))
("values" -> values(component.componentValues, level))
})
)
})
Expand Down
Loading