Skip to content
This repository has been archived by the owner on Feb 8, 2019. It is now read-only.

Fixes #5807: Precise action tracking on technique update allows to auto-enable/disable them in active library #62

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
22 changes: 11 additions & 11 deletions src/main/scala/com/normation/cfclerk/domain/TechniqueCategory.scala
Expand Up @@ -170,23 +170,23 @@ sealed trait TechniqueCategory {
}

case class RootTechniqueCategory(
name : String,
description : String,
subCategoryIds: Set[SubTechniqueCategoryId] = Set(),
packageIds : SortedSet[TechniqueId] = SortedSet(),
isSystem : Boolean = false
name : String
, description : String
, subCategoryIds: Set[SubTechniqueCategoryId] = Set()
, packageIds : SortedSet[TechniqueId] = SortedSet()
, isSystem : Boolean = false
) extends TechniqueCategory with HashcodeCaching {
type A = RootTechniqueCategoryId.type
override lazy val id : A = RootTechniqueCategoryId
}

case class SubTechniqueCategory(
override val id : SubTechniqueCategoryId,
name : String,
description : String,
subCategoryIds: Set[SubTechniqueCategoryId] = Set(),
packageIds : SortedSet[TechniqueId] = SortedSet(),
isSystem : Boolean = false
override val id : SubTechniqueCategoryId
, name : String
, description : String
, subCategoryIds : Set[SubTechniqueCategoryId] = Set()
, packageIds : SortedSet[TechniqueId] = SortedSet()
, isSystem : Boolean = false
) extends TechniqueCategory with HashcodeCaching {
type A = SubTechniqueCategoryId
}
Expand Down
Expand Up @@ -34,9 +34,37 @@

package com.normation.cfclerk.services

import com.normation.cfclerk.domain.TechniqueId
import com.normation.cfclerk.domain.TechniqueName
import com.normation.cfclerk.domain.TechniqueVersion
import com.normation.eventlog.EventActor
import com.normation.eventlog.ModificationId
import com.normation.cfclerk.domain.TechniqueName


sealed trait TechniqueVersionModType
case object VersionDeleted extends TechniqueVersionModType
case object VersionAdded extends TechniqueVersionModType
case object VersionUpdated extends TechniqueVersionModType

sealed trait TechniquesLibraryUpdateType {
val techniqueName: TechniqueName
}

/**
* All version of a technique were deleted, so the technique is
* full deleted
*/
case class TechniqueDeleted(techniqueName: TechniqueName, deletedVersion: Set[TechniqueVersion]) extends TechniquesLibraryUpdateType

/**
* Some version of a technique were modified, either:
* - added,
* - deleted,
* - modified.
*
* One version can have only ONE state update.
*/
case class TechniqueUpdated(techniqueName: TechniqueName, diff: Map[TechniqueVersion, TechniqueVersionModType]) extends TechniquesLibraryUpdateType

/**
* A trait that allows its implementation to get notification
Expand All @@ -51,7 +79,13 @@ trait TechniquesLibraryUpdateNotification {
/**
* A name to identify that callback
*/
def name:String
def name: String

/**
* Order:
* higher value for order mean the callback will happen latter
*/
def order: Int

/**
* That method will be called when techniques are updated.
Expand All @@ -60,6 +94,6 @@ trait TechniquesLibraryUpdateNotification {
* Description is a log description to explain why techniques should be updated
* (user action, commit, etc).
*/
def updatedTechniques(TechniqueIds:Seq[TechniqueId], modId: ModificationId, actor: EventActor, reason: Option[String]) : Unit
def updatedTechniques(techniqueIds: Map[TechniqueName, TechniquesLibraryUpdateType], modId: ModificationId, actor: EventActor, reason: Option[String]) : Unit

}
}
Expand Up @@ -48,7 +48,9 @@ case class TechniquesInfo(
, techniques: Map[TechniqueName, SortedMap[TechniqueVersion, Technique]]
//head of categories is the root category
, subCategories: Map[SubTechniqueCategoryId, TechniqueCategory]
) extends HashcodeCaching
) extends HashcodeCaching {
val allCategories = Map[TechniqueCategoryId, TechniqueCategory]() ++ subCategories + (rootCategory.id -> rootCategory)
}

//a mutable version of TechniquesInfo, for internal use only !
private[services] class InternalTechniquesInfo(
Expand Down Expand Up @@ -117,6 +119,6 @@ trait TechniqueReader {
* If the sequence is empty, then nothing changed. Else, the list of Technique with
* *any* change will be given
*/
def getModifiedTechniques : Seq[TechniqueId]
def getModifiedTechniques : Map[TechniqueName, TechniquesLibraryUpdateType]
}

Expand Up @@ -45,10 +45,6 @@ import scala.collection.SortedSet
*
*
*/
/**
* @author vincent
*
*/
trait TechniqueRepository {

/**
Expand All @@ -70,6 +66,15 @@ trait TechniqueRepository {

// def packageDirectory : File


/*
* Return the full information about technique.
* That TechniquesInfo data structure is self-consistent by construction,
* so any category reference as a subcategory of an other is
* defined in the map, all techniques in categories is also accessible. etc.
*/
def getTechniquesInfo(): TechniquesInfo

/**
* Return all the policies available
*/
Expand Down
Expand Up @@ -38,6 +38,7 @@ import net.liftweb.common.Box
import com.normation.cfclerk.domain.TechniqueId
import com.normation.eventlog.EventActor
import com.normation.eventlog.ModificationId
import com.normation.cfclerk.domain.TechniqueName

/**
* A trait that allows to update the reference policy
Expand All @@ -49,11 +50,11 @@ trait UpdateTechniqueLibrary {
* Update the lib, and return the list of
* actually updated policy templates.
*/
def update(modId: ModificationId, actor:EventActor, reason: Option[String]) : Box[Seq[TechniqueId]]
def update(modId: ModificationId, actor:EventActor, reason: Option[String]) : Box[Map[TechniqueName, TechniquesLibraryUpdateType]]

/**
* Allows callbacks to be called on a Policy Template library update.
*/
def registerCallback(callback:TechniquesLibraryUpdateNotification) : Unit

}
}
Expand Up @@ -107,7 +107,7 @@ class FSTechniqueReader(
}

//we never know when to read again
override def getModifiedTechniques : Seq[TechniqueId] = Seq()
override def getModifiedTechniques : Map[TechniqueName, TechniquesLibraryUpdateType] = Map()

/**
* Read the policies
Expand Down Expand Up @@ -359,4 +359,4 @@ class FSTechniqueReader(

doc
}
}
}
Expand Up @@ -71,6 +71,7 @@ import org.eclipse.jgit.diff.DiffEntry
import scala.collection.JavaConversions._
import org.eclipse.jgit.diff.DiffFormatter
import org.eclipse.jgit.errors.MissingObjectException
import org.eclipse.jgit.diff.DiffEntry.ChangeType

/**
*
Expand Down Expand Up @@ -185,9 +186,9 @@ class GitTechniqueReader(

private[this] var nextTechniquesInfoCache : (ObjectId,TechniquesInfo) = (revisionProvider.currentRevTreeId, currentTechniquesInfoCache)
//a non empty list IS the indicator of differences between current and next
private[this] var modifiedTechniquesCache : Seq[TechniqueId] = Seq()
private[this] var modifiedTechniquesCache : Map[TechniqueName, TechniquesLibraryUpdateType] = Map()

override def getModifiedTechniques : Seq[TechniqueId] = {
override def getModifiedTechniques : Map[TechniqueName, TechniquesLibraryUpdateType] = {
val nextId = revisionProvider.getAvailableRevTreeId
if(nextId == nextTechniquesInfoCache._1) modifiedTechniquesCache
else reader.synchronized { //update next and calculate diffs
Expand All @@ -199,32 +200,78 @@ class GitTechniqueReader(

val diffFmt = new DiffFormatter(null)
diffFmt.setRepository(repo.db)
val diffPathEntries : Set[TechniquePath] =
val diffPathEntries : Set[(TechniquePath, ChangeType)] =
diffFmt.scan(revisionProvider.currentRevTreeId,nextId).flatMap { diffEntry =>
Seq(toTechniquePath(diffEntry.getOldPath), toTechniquePath(diffEntry.getNewPath))
Seq( (toTechniquePath(diffEntry.getOldPath), diffEntry.getChangeType), (toTechniquePath(diffEntry.getNewPath), diffEntry.getChangeType))
}.toSet
diffFmt.release

val modifiedTechniquePath = scala.collection.mutable.Set[TechniquePath]()
/*
* now, group diff entries by TechniqueId to find which were updated
* we take into account any modifications, as anything among a
* delete, rename, copy, add, modify must be accepted and the matching
* datetime saved.
*/
diffPathEntries.foreach { path =>
allKnownTechniquePaths.find { TechniquePath =>
path.path.startsWith(TechniquePath.path)
}.foreach { TechniquePath =>
modifiedTechniquePath += TechniquePath
} //else nothing
val modifiedTechnique : Set[(TechniqueId, ChangeType)] = diffPathEntries.flatMap { case (path, changeType) =>
allKnownTechniquePaths.find { techniquePath =>
path.path.startsWith(techniquePath.path)
}.map { techniquePath =>
val parts = techniquePath.path.split("/")
val id = TechniqueId(TechniqueName(parts(parts.size - 2)), TechniqueVersion(parts(parts.size - 1)))

(id, changeType)
}
}

//now, build the actual modification by techniques:
val techniqueMods = modifiedTechnique.groupBy( _._1.name ).map { case (name, mods) =>
//deduplicate mods by ids:
val versionsMods: Map[TechniqueVersion, TechniqueVersionModType] = mods.groupBy( _._1.version).map { case(version, pairs) =>
val modTypes = pairs.map( _._2 ).toSet

//merge logic
//delete + add (+mod) => have to check if still present
//delete (+ mod) => delete
//add (+ mod) => add
//other: mod
if(modTypes.contains(ChangeType.DELETE)) {
if(modTypes.contains(ChangeType.ADD)) {
if(currentTechniquesInfoCache.techniquesCategory.isDefinedAt(TechniqueId(name, version))) {
//it was present before mod, so can't have been added first, so
//it was deleted then added (certainly a move), so it is actually mod
(version, VersionUpdated)
} else {
//it was not here, so was added then deleted, so it's a noop.
//not sure we want to trace that ? As a deletion perhaps ?
(version, VersionDeleted)
}
} else {
(version, VersionDeleted)
}
} else if(modTypes.contains(ChangeType.ADD)) {
//add
(version, VersionAdded)
} else {
//mod
(version, VersionUpdated)
}
}.toMap

//now, build the technique mod.
//distinguish the case where all mod are deletion AND they represent the whole set of known version
//from other case (just simple updates)

if(versionsMods.values.forall( _ == VersionDeleted)
&& currentTechniquesInfoCache.techniques.get(name).map( _.size) == Some(versionsMods.size)
) {
(name, TechniqueDeleted(name, versionsMods.keySet))
} else {
(name, TechniqueUpdated(name, versionsMods))
}
}.toMap

//Ok, now rebuild Technique !
modifiedTechniquesCache = modifiedTechniquePath.map { s =>
val parts = s.path.split("/")
TechniqueId(TechniqueName(parts(parts.size - 2)), TechniqueVersion(parts(parts.size - 1)))
}.toSeq
modifiedTechniquesCache = techniqueMods
nextTechniquesInfoCache = (nextId, nextTechniquesInfo)
modifiedTechniquesCache
}
Expand Down Expand Up @@ -362,7 +409,7 @@ class GitTechniqueReader(
if(modifiedTechniquesCache.nonEmpty) {
currentTechniquesInfoCache = nextTechniquesInfoCache._2
revisionProvider.setCurrentRevTreeId(nextTechniquesInfoCache._1)
modifiedTechniquesCache = Seq()
modifiedTechniquesCache = Map()
}
currentTechniquesInfoCache
}
Expand Down
Expand Up @@ -56,7 +56,7 @@ class TechniqueRepositoryImpl(
/**
* Callback to call on PTLib update
*/
private[this] val callbacks = scala.collection.mutable.Buffer(refLibCallbacks:_*)
private[this] var callbacks = refLibCallbacks.sortBy( _.order )


/*
Expand Down Expand Up @@ -86,19 +86,27 @@ class TechniqueRepositoryImpl(


/**
* Register a new callback
* Register a new callback with a order.
* Sort by order after each registration
*/
override def registerCallback(callback:TechniquesLibraryUpdateNotification) : Unit = {
callbacks.append(callback)
callbacks = (callbacks :+ callback).sortBy( _.order )
}

override def update(modId: ModificationId, actor:EventActor, reason: Option[String]) : Box[Seq[TechniqueId]] = {
override def update(modId: ModificationId, actor:EventActor, reason: Option[String]) : Box[Map[TechniqueName, TechniquesLibraryUpdateType]] = {
try {
val modifiedPackages = techniqueReader.getModifiedTechniques
if (modifiedPackages.nonEmpty || /* first time init */ null == techniqueInfosCache) {
logger.info("Reloading technique library, " + {
if (modifiedPackages.isEmpty) "no modified techniques found"
else "found modified technique(s): " + modifiedPackages.mkString(", ")
else {
val details = modifiedPackages.values.map {
case TechniqueDeleted(name, versions) => s"['${name}': deleted (${versions.mkString(", ")})]"
case TechniqueUpdated(name, mods) => s"['${name}': updated (${mods.map(x => s"${x._1}: ${x._2}").mkString(", ")})]"
}

"found modified technique(s): " + details.mkString(", ")
}
})
techniqueInfosCache = techniqueReader.readTechniques

Expand Down Expand Up @@ -142,6 +150,8 @@ class TechniqueRepositoryImpl(
}).toMap
}

override def getTechniquesInfo() = techniqueInfosCache

override def getTechniqueVersions(name: TechniqueName): SortedSet[TechniqueVersion] = {
SortedSet[TechniqueVersion]() ++ techniqueInfosCache.techniques.get(name).toSeq.flatMap(_.keySet)
}
Expand Down Expand Up @@ -174,7 +184,11 @@ class TechniqueRepositoryImpl(
}

override def getLastTechniqueByName(policyName: TechniqueName): Option[Technique] = {
techniqueInfosCache.techniques.get(policyName).map { versions => versions.last._2 }
for {
versions <- techniqueInfosCache.techniques.get(policyName)
} yield {
versions.last._2
}
}

//////////////////////////////////// categories /////////////////////////////
Expand Down
Expand Up @@ -75,6 +75,6 @@ class DummyTechniqueReader(policies:Seq[Technique]=Seq(Technique(TechniqueId(Tec
def getMetadataContent[T](techniqueId: TechniqueId)(useIt : Option[InputStream] => T) : T = useIt(None)
def getReportingDetailsContent[T](techniqueId: TechniqueId)(useIt : Option[InputStream] => T) : T = useIt(None)
def getTemplateContent[T](templateName: Cf3PromisesFileTemplateId)(useIt : Option[InputStream] => T) : T = useIt(None)
def getModifiedTechniques : Seq[TechniqueId] = Seq()
def getModifiedTechniques : Map[TechniqueName, TechniquesLibraryUpdateType] = Map()

}