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

Commit

Permalink
Merge pull request #77 from fanf/ust_2881/allow_user_to_choose_order_…
Browse files Browse the repository at this point in the history
…of_directive_application_in_a_rule

Fixes #2881: Allow user to choose order of Directive application in a Rule
  • Loading branch information
ncharles committed Jul 29, 2015
2 parents 5113df0 + 406d064 commit e765b95
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 7 deletions.
42 changes: 42 additions & 0 deletions src/main/scala/com/normation/cfclerk/domain/Cf3PolicyDraft.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,47 @@ import net.liftweb.common._
*/
case class Cf3PolicyDraftId(value: String) extends HashcodeCaching


final case class BundleOrder(value: String)

object BundleOrder {
val default: BundleOrder = BundleOrder("")

/**
* Comparison logic for bundle: purelly alpha-numeric.
* The empty string come first.
* The comparison is stable, meaning that a sorted list
* with equals values stay in the same order after a sort.
*
* The sort is case insensitive.
*/
def compare(a: BundleOrder, b: BundleOrder): Int = {
String.CASE_INSENSITIVE_ORDER.compare(a.value, b.value)
}

def compareList(a: List[BundleOrder], b: List[BundleOrder]): Int = {

//only works on list of the same size
def compareListRec(a: List[BundleOrder], b: List[BundleOrder]): Int = {
(a, b) match {
case (ha :: ta, hb :: tb) =>
val comp = compare(ha,hb)
if(comp == 0) {
compareList(ta, tb)
} else {
comp
}
case _ => //we know they have the same size by construction, so it's a real equality
0
}
}

val maxSize = List(a.size, b.size).max
compareListRec(a.padTo(maxSize, BundleOrder.default), b.padTo(maxSize, BundleOrder.default))

}
}

/**
* That policy instance object is an instance of a policy applied (bound)
* to a particular node, so that its variable can be specialized given the node
Expand All @@ -70,6 +111,7 @@ final case class Cf3PolicyDraft(
, priority : Int
, serial : Int
, modificationDate: DateTime = DateTime.now
, order : List[BundleOrder]
) extends Loggable {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ class Cf3PromisesFileWriterServiceImpl(
val inputs = scala.collection.mutable.Buffer[String]() // all the include file

// Fetch the policies configured, with the system policies first
val policies = techniqueRepository.getByIds(container.getAllIds).sortWith((x,y) => x.isSystem)
val policies = sortTechniques(techniqueRepository.getByIds(container.getAllIds), container)

for {
tml <- policies.flatMap(p => p.templates)
Expand Down Expand Up @@ -361,6 +361,49 @@ class Cf3PromisesFileWriterServiceImpl(
)
}

/**
* Sort the techniques according to the order of the associated BundleOrder of Cf3PolicyDraft.
* Sort at best: sort rule then directives, and take techniques on that order, only one time
* Sort system directive first.
*/
private[this] def sortTechniques(techniques: Seq[Technique], container: Cf3PolicyDraftContainer): Seq[Technique] = {

def sortByOrder(tech: Seq[Technique], container: Cf3PolicyDraftContainer): Seq[Technique] = {
def compareBundleOrder(a: List[BundleOrder], b: List[BundleOrder]): Boolean = {
BundleOrder.compareList(a, b) <= 0
}
val drafts = container.getAll().values.toSeq

//for each technique, get it's best order from draft (if several directive use it) and return a pair (technique, List(order))
val pairs = tech.map { t =>
val tDrafts = drafts.filter { _.technique.id == t.id }.sortWith( (d1,d2) => compareBundleOrder(d1.order, d2.order))

//the order we want is the one with the lowest draft order, or the default one if no draft found (but that should not happen by construction)
val order = tDrafts.map( _.order ).headOption.getOrElse(List(BundleOrder.default))

(t, order)
}

//now just sort the pair by order and keep only techniques
val ordered = pairs.sortWith { case ((_, o1), (_, o2)) => BundleOrder.compareList(o1, o2) <= 0 }

//some debug info to understand what order was used for each node:
if(logger.isDebugEnabled) {
logger.debug(s"Sorted Technique for path [${container.outPath}] (and their Rules and Directives used to sort):")
ordered.map(p => s" `-> ${p._1.name}: [${p._2.map(_.value).mkString(" | ")}]").foreach { logger.debug(_) }
}

ordered.map( _._1 )
}

//system technique go first whatever their order
val (sys, user) = techniques.partition { _.isSystem }

sys ++ sortByOrder(user, container)

}


/**
* From the container, convert the parameter into StringTemplate variable, that contains a list of
* parameterName, parameterValue (really, the ParameterEntry itself)
Expand Down
105 changes: 105 additions & 0 deletions src/test/scala/com/normation/cfclerk/domain/BundleOrderTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
*************************************************************************************
* Copyright 2011 Normation SAS
*************************************************************************************
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* In accordance with the terms of section 7 (7. Additional Terms.) of
* the GNU Affero GPL v3, the copyright holders add the following
* Additional permissions:
* Notwithstanding to the terms of section 5 (5. Conveying Modified Source
* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU Affero GPL v3
* licence, when you create a Related Module, this Related Module is
* not considered as a part of the work and may be distributed under the
* license agreement of your choice.
* A "Related Module" means a set of sources files including their
* documentation that, without modification of the Source Code, enables
* supplementary functions or services in addition to those offered by
* the Software.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/agpl.html>.
*
*************************************************************************************
*/

package com.normation.cfclerk.domain

import org.junit._
import org.junit.Assert._
import org.junit.runner._
import org.junit.runner.RunWith
import org.specs2.mutable._
import org.specs2.runner._




@RunWith(classOf[JUnitRunner])
class BundleOrderTest extends Specification {

implicit def s2b(s: String): BundleOrder = BundleOrder(s)

implicit def l2b(l:List[String]): List[BundleOrder] = l.map(BundleOrder(_))


/**
* Reminder for my self-me that keep forgeting what compare means:
* compare(a,b) < 0 => sorted
* compare(a,b) > 0 => reverse order
*/

"Simple BundleOrder" should {

"be equal with emtpy srting" in {
BundleOrder.compare("", "") === 0
}

"let emtpy string be the smallest" in {
(BundleOrder.compare("plop", "") > 0) must beTrue
}

"use alphaNum" in {
(BundleOrder.compare("plop", "foo") > 0) must beTrue
}

"be sortable with numbers" in {
(BundleOrder.compare("050 a", "010 b") > 0) must beTrue
}

"be becarefull, it's NOT BY NUMBER" in {
(BundleOrder.compare("50", "100") > 0) must beTrue
}
}

"List compare" should {

"be equals with empty list" in {
BundleOrder.compareList(List(), List()) === 0
}

"ok for equals" in {
BundleOrder.compareList(List("10", "20"), List("10", "20")) === 0
}
"ok for equals, different size" in {
BundleOrder.compareList(List("10", ""), List("10")) === 0
}
"handle less than" in {
(BundleOrder.compareList(List("10", "100"), List("20")) < 0) must beTrue
}

"handle more than" in {
(BundleOrder.compareList(List("101", "100"), List("020", "200", "300")) > 0) must beTrue
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class Cf3PolicyDraftContainerServiceTest {
Map[String, Variable](variable1.spec.name -> variable1),
TrackerVariable(TrackerVariableSpec(), Seq()),
priority =0,
serial = 0
serial = 0, order = List()
)

policy2 = Cf3PolicyDraft(
Expand All @@ -87,7 +87,7 @@ class Cf3PolicyDraftContainerServiceTest {
Map[String, Variable](variable2.spec.name -> variable2, variable22.spec.name -> variable22),
TrackerVariable(TrackerVariableSpec(), Seq()),
priority =0,
serial = 0
serial = 0, order = List()
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,14 @@ class DirectiveAgregationTest {

def createDirectiveWithBinding(activeTechniqueId:TechniqueId, i: Int): Cf3PolicyDraft = {
val instance = new Cf3PolicyDraft("id" + i, newTechnique(activeTechniqueId),
Map(), trackerVariable, priority = 0, serial = 0)
Map(), trackerVariable, priority = 0, serial = 0, order = List())

val variable = new InputVariable(InputVariableSpec("card", "varDescription1"), Seq("value" + i))
instance.copyWithAddedVariable(variable)
}

def createDirectiveWithArrayBinding(activeTechniqueId:TechniqueId, i: Int): Cf3PolicyDraft = {
val instance = new Cf3PolicyDraft("id" + i, newTechnique(activeTechniqueId), Map(), trackerVariable, priority = 0, serial = 0)
val instance = new Cf3PolicyDraft("id" + i, newTechnique(activeTechniqueId), Map(), trackerVariable, priority = 0, serial = 0, order = List())

val variable = InputVariable(
InputVariableSpec("card", "varDescription1", multivalued = true)
Expand All @@ -117,7 +117,7 @@ class DirectiveAgregationTest {
}

def createDirectiveWithArrayBindingAndNullValues(activeTechniqueId:TechniqueId, i: Int): Cf3PolicyDraft = {
val instance = new Cf3PolicyDraft("id" + i, newTechnique(activeTechniqueId), Map(), trackerVariable, priority = 0, serial = 0)
val instance = new Cf3PolicyDraft("id" + i, newTechnique(activeTechniqueId), Map(), trackerVariable, priority = 0, serial = 0, order = List())

val values = (0 until i).map(j =>
if (j > 0) "value" + i
Expand Down Expand Up @@ -166,7 +166,7 @@ class DirectiveAgregationTest {
val machineA = new Cf3PolicyDraftContainer("machineA", Set())

val instance = new Cf3PolicyDraft("id", newTechnique(TechniqueId(TechniqueName("name"), TechniqueVersion("1.0"))),
Map(), trackerVariable, priority = 0, serial = 0)
Map(), trackerVariable, priority = 0, serial = 0, order = List())
machineA.add(createDirectiveWithArrayBinding(activeTechniqueId1,1))
machineA.add(createDirectiveWithArrayBinding(activeTechniqueId1,2))

Expand Down

0 comments on commit e765b95

Please sign in to comment.