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 #7398: Update compliance UI to manage disable mode #963

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ object ComplianceDebugLogger extends Logger {
s"[NoReportInInterval: expected NodeConfigId: ${expectedConfigId.toLog}|"+
s" last run: none available (or too old)]"

case ReportsDisabledInInterval(expectedConfigId) =>
s"ReportsDisabledInInterval: expected NodeConfigId: ${expectedConfigId.toLog}|"+
s" last run: none available (compliance mode is reports-disabled)]"

case Pending(expectedConfigId, optLastRun, expirationDateTime, missingStatus) =>
s"[Pending: until ${expirationDateTime} expected NodeConfigId: ${expectedConfigId.toLog} |"+
s" last run: ${optLastRun.fold("none (or too old)")(x => s"nodeConfigId: ${x._2.toLog} received at ${x._1}")}]"
Expand All @@ -107,12 +111,13 @@ object ComplianceDebugLogger extends Logger {
}

val logName = c match {
case NoRunNoInit => "NoRunNoInit"
case _: VersionNotFound => "VersionNotFound"
case _: NoReportInInterval=> "NoReportInInterval"
case _: Pending => "Pending"
case _: UnexpectedVersion => "UnexpectedVersion"
case _: ComputeCompliance => "ComputeCompliance"
case NoRunNoInit => "NoRunNoInit"
case _: VersionNotFound => "VersionNotFound"
case _: NoReportInInterval => "NoReportInInterval"
case _: ReportsDisabledInInterval => "ReportsDisabledInInterval"
case _: Pending => "Pending"
case _: UnexpectedVersion => "UnexpectedVersion"
case _: ComputeCompliance => "ComputeCompliance"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ case object LongComparator extends CriterionType {

case object MemoryComparator extends CriterionType {
override val comparators = OrderedComparators.comparators
override protected def validateSubCase(v:String,comparator:CriterionComparator) = try {
override protected def validateSubCase(v:String,comparator:CriterionComparator) = {
Copy link
Member

Choose a reason for hiding this comment

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

shouldn't you keep the try ?
What happens if the parse fails ?

Copy link
Member Author

Choose a reason for hiding this comment

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

there is no catch

Copy link
Member

Choose a reason for hiding this comment

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

then why not adding a catch ??

Copy link
Member Author

Choose a reason for hiding this comment

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

because it is not supposed to throw exception (Full/Failure), the try must be a remeniscience of the past

if(MemorySize.parse(v).isDefined) Full(v)
else Failure("Invalid memory size : '%s', expecting '300 Mo', '16KB', etc".format(v))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@
package com.normation.rudder.domain.reports

import org.joda.time.DateTime

import com.normation.inventory.domain.NodeId
import com.normation.rudder.domain.policies.DirectiveId
import com.normation.rudder.domain.policies.RuleId
import net.liftweb.common.Loggable
import com.normation.rudder.reports.ReportsDisabled
import net.liftweb.http.js.JE
import net.liftweb.http.js.JE.JsArray

/**
* That file define a "compliance level" object, which store
Expand All @@ -52,45 +54,48 @@ import net.liftweb.common.Loggable
//simple data structure to hold percentages of different compliance
//the ints are actual numbers, percents are computed with the pc_ variants
case class ComplianceLevel(
pending : Int = 0
, success : Int = 0
, repaired : Int = 0
, error : Int = 0
, unexpected : Int = 0
, missing : Int = 0
, noAnswer : Int = 0
, notApplicable: Int = 0
pending : Int = 0
, success : Int = 0
, repaired : Int = 0
, error : Int = 0
, unexpected : Int = 0
, missing : Int = 0
, noAnswer : Int = 0
, notApplicable : Int = 0
, reportsDisabled: Int = 0
) {

override def toString() = s"[p:${pending} s:${success} r:${repaired} e:${error} u:${unexpected} m:${missing} nr:${noAnswer} na:${notApplicable}]"
override def toString() = s"[p:${pending} s:${success} r:${repaired} e:${error} u:${unexpected} m:${missing} nr:${noAnswer} na:${notApplicable} rd:${reportsDisabled}]"

val total = pending+success+repaired+error+unexpected+missing+noAnswer+notApplicable
val total = pending+success+repaired+error+unexpected+missing+noAnswer+notApplicable+reportsDisabled

val complianceWithoutPending = pc_for(success+repaired+notApplicable, total-pending)
val complianceWithoutPending = pc_for(success+repaired+notApplicable, total-pending-reportsDisabled)
Copy link
Member

Choose a reason for hiding this comment

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

Ha, shouldn't you remove the reportsDisabled on the left side as well ?

Copy link
Member

Choose a reason for hiding this comment

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

ha ok

val compliance = pc_for(success+repaired+notApplicable, total)

private[this] def pc_for(i:Int, total:Int) : Double = if(total == 0) 0 else (i * 100 / BigDecimal(total)).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble
private[this] def pc(i:Int) : Double = pc_for(i, total)

val pc_pending = pc(pending)
val pc_success = pc(success)
val pc_repaired = pc(repaired)
val pc_error = pc(error)
val pc_unexpected = pc(unexpected)
val pc_missing = pc(missing)
val pc_noAnswer = pc(noAnswer)
val pc_notApplicable = pc(notApplicable)
val pc_pending = pc(pending)
val pc_success = pc(success)
val pc_repaired = pc(repaired)
val pc_error = pc(error)
val pc_unexpected = pc(unexpected)
val pc_missing = pc(missing)
val pc_reportsDisabled = pc(reportsDisabled)
val pc_noAnswer = pc(noAnswer)
val pc_notApplicable = pc(notApplicable)

def +(compliance: ComplianceLevel): ComplianceLevel = {
ComplianceLevel(
pending = this.pending + compliance.pending
, success = this.success + compliance.success
, repaired = this.repaired + compliance.repaired
, error = this.error + compliance.error
, unexpected = this.unexpected + compliance.unexpected
, missing = this.missing + compliance.missing
, noAnswer = this.noAnswer + compliance.noAnswer
, notApplicable = this.notApplicable + compliance.notApplicable
pending = this.pending + compliance.pending
, success = this.success + compliance.success
, repaired = this.repaired + compliance.repaired
, error = this.error + compliance.error
, unexpected = this.unexpected + compliance.unexpected
, missing = this.missing + compliance.missing
, noAnswer = this.noAnswer + compliance.noAnswer
, notApplicable = this.notApplicable + compliance.notApplicable
, reportsDisabled = this.reportsDisabled + compliance.reportsDisabled
)
}

Expand All @@ -111,6 +116,7 @@ object ComplianceLevel {
case MissingReportType => compliance.copy(missing = compliance.missing + 1)
case NoAnswerReportType => compliance.copy(noAnswer = compliance.noAnswer + 1)
case PendingReportType => compliance.copy(pending = compliance.pending + 1)
case DisabledReportType => compliance.copy(reportsDisabled = compliance.reportsDisabled + 1)
}
}
}
Expand All @@ -120,3 +126,24 @@ object ComplianceLevel {
else compliances.reduce( _ + _)
}
}


object ComplianceLevelSerialisation {

//transform the compliance percent to a list with a given order:
// pc_reportDisabled, pc_notapplicable, pc_success, pc_repaired,
// pc_error, pc_pending, pc_noAnswer, pc_missing, pc_unknown
implicit class ComplianceLevelToJs(compliance: ComplianceLevel) {
def toJsArray(): JsArray = JsArray (
JE.Num(compliance.pc_reportsDisabled)
, JE.Num(compliance.pc_notApplicable)
, JE.Num(compliance.pc_success)
, JE.Num(compliance.pc_repaired)
, JE.Num(compliance.pc_error)
, JE.Num(compliance.pc_pending)
, JE.Num(compliance.pc_noAnswer)
, JE.Num(compliance.pc_missing)
, JE.Num(compliance.pc_unexpected)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,12 @@

package com.normation.rudder.domain.reports

/**
* List of all type of report we are expecting, as a result
* So far :
* Success
* Repaired
* Error
* Unknown
* No Answer
*/


/**
* Kind of reports that we can get as result of
* a merge/compare with expected reports.
*/
sealed trait ReportType {
val severity :String
}
Expand All @@ -68,6 +63,9 @@ case object UnexpectedReportType extends ReportType{
case object NoAnswerReportType extends ReportType{
val severity = "NoAnswer"
}
case object DisabledReportType extends ReportType{
val severity = "ReportsDisabled"
}
case object PendingReportType extends ReportType{
val severity = "Applying"
}
Expand All @@ -82,27 +80,20 @@ object ReportType {
NoAnswerReportType
} else {
( reportTypes :\ (NotApplicableReportType : ReportType) ) {
case (_, UnexpectedReportType) | (UnexpectedReportType, _) => UnexpectedReportType
case (_, ErrorReportType) | (ErrorReportType, _) => ErrorReportType
case (_, RepairedReportType) | (RepairedReportType, _) => RepairedReportType
case (_, MissingReportType) | (MissingReportType, _) => MissingReportType
case (_, NoAnswerReportType) | (NoAnswerReportType, _) => NoAnswerReportType
case (_, PendingReportType) | (PendingReportType, _) => PendingReportType
case (_, SuccessReportType) | (SuccessReportType, _) => SuccessReportType
case (_, NotApplicableReportType) | (NotApplicableReportType, _) => NotApplicableReportType
case (_, UnexpectedReportType) | (UnexpectedReportType, _) => UnexpectedReportType
case (_, ErrorReportType) | (ErrorReportType, _) => ErrorReportType
case (_, RepairedReportType) | (RepairedReportType, _) => RepairedReportType
case (_, MissingReportType) | (MissingReportType, _) => MissingReportType
case (_, NoAnswerReportType) | (NoAnswerReportType, _) => NoAnswerReportType
case (_, DisabledReportType)| (DisabledReportType, _) => DisabledReportType
case (_, PendingReportType) | (PendingReportType, _) => PendingReportType
case (_, SuccessReportType) | (SuccessReportType, _) => SuccessReportType
case (_, NotApplicableReportType) | (NotApplicableReportType, _) => NotApplicableReportType
case _ => UnexpectedReportType
}
}
}

def getWorseReport(reports: Seq[Reports]): ReportType = {
getWorseType(reports.map( apply(_) ))
}

def getSeverityFromStatus(status : ReportType) : String = {
status.severity
}

def apply(report : Reports) : ReportType = {
report match {
case _ : ResultSuccessReport => SuccessReportType
Expand All @@ -113,10 +104,10 @@ object ReportType {
}
}

implicit def reportTypeSeverity(reportType:ReportType):String = ReportType.getSeverityFromStatus(reportType)
implicit def reportTypeSeverity(reportType:ReportType):String = reportType.severity
}

/**
* Case class to store the previous (last received) configid and the expected NodeConfigId in the Pending case
*/
final case class PreviousAndExpectedNodeConfigId(previous: NodeAndConfigId, expected: NodeAndConfigId)
final case class PreviousAndExpectedNodeConfigId(previous: NodeAndConfigId, expected: NodeAndConfigId)
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ case object ChangesOnly extends ComplianceModeName {
val name = "changes-only"
}

case object ReportDisabled extends ComplianceModeName {
case object ReportsDisabled extends ComplianceModeName {
val name = "reports-disabled"
}

object ComplianceModeName {
val allModes : List[ComplianceModeName] = FullCompliance :: ChangesOnly :: ReportDisabled :: Nil
val allModes : List[ComplianceModeName] = FullCompliance :: ChangesOnly :: ReportsDisabled :: Nil

def parse (value : String) : Box[ComplianceModeName] = {
allModes.find { _.name == value } match {
Expand Down Expand Up @@ -111,4 +111,4 @@ class ComplianceModeServiceImpl (
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -963,7 +963,7 @@ object NodeConfigIdSerializer {
case x::Nil => Seq(NodeConfigIdInfo(x._1, x._2, None))
case t => t.sliding(2).map {
//we know the size of the list is 2
case x::Nil => throw new IllegalArgumentException("An impossible state was reached, please contact the dev about it!")
case _::Nil | Nil => throw new IllegalArgumentException("An impossible state was reached, please contact the dev about it!")
case x::y::t => NodeConfigIdInfo(x._1, x._2, Some(y._2))
}.toVector :+ {
val x = t.last
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,14 @@ case class NoReportInInterval(
expectedConfigId: NodeConfigIdInfo
) extends NoReport

/*
* No report of interest but expected because
* we are on the correct mode for that
*/
case class ReportsDisabledInInterval(
expectedConfigId: NodeConfigIdInfo
) extends NoReport

case class Pending(
expectedConfigId : NodeConfigIdInfo
, optLastRun : Option[(DateTime, NodeConfigIdInfo)]
Expand Down Expand Up @@ -269,7 +277,7 @@ object ExecutionBatch extends Loggable {
val missingReportType = complianceMode.mode match {
case FullCompliance => MissingReportType
case ChangesOnly => SuccessReportType
case ReportDisabled => MissingReportType
case ReportsDisabled => DisabledReportType
}

/*
Expand All @@ -287,7 +295,7 @@ object ExecutionBatch extends Loggable {
//expires after run*heartbeat period - we need an other run before that.
val heartbeat = Duration.standardMinutes((runIntervalInfo.interval.getStandardMinutes * runIntervalInfo.heartbeatPeriod ))
heartbeat.plus(GRACE_TIME_PENDING)
case FullCompliance | ReportDisabled =>
case FullCompliance | ReportsDisabled =>
updateValidityTime(runIntervalInfo)
}

Expand All @@ -296,14 +304,22 @@ object ExecutionBatch extends Loggable {
ComplianceDebugLogger.node(nodeId).debug(s"Node run configuration: ${(nodeId, complianceMode, runInfos).toLog }")
}


nodeIds.map { case (nodeId, intervalInfo) =>
implicit val _n = nodeId
val optInfo = nodeConfigIdInfos.getOrElse(nodeId, None)

val runInfo = {
//special case if we are on "reports-disabled" mode
val runInfo = if(complianceMode.mode == ReportsDisabled) {
optInfo match {
case Some(configs) if(configs.nonEmpty) =>
runType(s"compliance mode is set to '${}', it's ok to not having reports", ReportsDisabledInInterval(configs.maxBy(_.creation.getMillis)))
case _ =>
runType(s"nodeId has no configuration ID version (it should, even in ${ReportsDisabled.name} compliance mode", NoRunNoInit)
}
} else {

val optRun = runs.getOrElse(nodeId, None)
val optInfo = nodeConfigIdInfos.getOrElse(nodeId, None)

(optRun, optInfo) match {
case (None, None) =>
Expand Down Expand Up @@ -335,6 +351,7 @@ object ExecutionBatch extends Loggable {

}
}

(nodeId, runInfo)
}.toMap
}
Expand Down Expand Up @@ -487,6 +504,18 @@ object ExecutionBatch extends Loggable {

runInfo match {


case ReportsDisabledInInterval(expectedConfig) =>
ComplianceDebugLogger.node(nodeId).trace(s"Compliance mode is ${ReportsDisabled.name}, so we don't have to try to merge/compare with expected reports")
buildRuleNodeStatusReport(
//these reports don't really expires - without change, it will
//always be the same.
MergeInfo(nodeId, None, Some(expectedConfig.configId), END_OF_TIME)
, getExpectedReports(expectedConfig.configId)
, DisabledReportType
)


case ComputeCompliance(lastRunDateTime, lastRunConfigId, expectedConfigId, expirationTime, missingReportStatus) =>
ComplianceDebugLogger.node(nodeId).trace(s"Using merge/compare strategy between last reports from run ${lastRunConfigId.toLog} and expect reports ${expectedConfigId.toLog}")
mergeCompareByRule(
Expand Down