Skip to content

Commit

Permalink
Work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
ElaadF committed Mar 15, 2020
1 parent e1996c2 commit 432704b
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import com.normation.plugins.RudderPluginModule
import com.normation.plugins.scaleoutrelay.ScalaOutRelayPluginDef
import com.normation.plugins.scaleoutrelay.CheckRudderPluginEnableImpl
import com.normation.plugins.scaleoutrelay.ScaleOutRelayAgentSpecificGeneration
import com.normation.plugins.scaleoutrelay.api.ScaleOutRelayApiImpl

/*
* Actual configuration of the plugin logic
Expand All @@ -53,6 +54,8 @@ object ScalaOutRelayConf extends RudderPluginModule {

lazy val pluginDef = new ScalaOutRelayPluginDef(ScalaOutRelayConf.pluginStatusService)

lazy val api = new ScaleOutRelayApiImpl(RudderConfig.restExtractorService)

// add policy generation for AIX nodes
RudderConfig.agentRegister.addAgentLogic(new ScaleOutRelayAgentSpecificGeneration(pluginStatusService))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,15 @@

package com.normation.plugins.scaleoutrelay

import bootstrap.liftweb.Boot
import bootstrap.rudder.plugin.ScalaOutRelayConf
import com.normation.plugins._
import com.normation.rudder.AuthorizationType.Administration
import com.normation.rudder.rest.EndpointSchema
import com.normation.rudder.rest.lift.LiftApiModuleProvider
import net.liftweb.http.ClasspathTemplates
import net.liftweb.sitemap.Loc.{LocGroup, Template, TestAccess}
import net.liftweb.sitemap.Menu

class ScalaOutRelayPluginDef(override val status: PluginStatus) extends DefaultPluginDef {

Expand All @@ -48,4 +56,16 @@ class ScalaOutRelayPluginDef(override val status: PluginStatus) extends DefaultP
def oneTimeInit : Unit = {}

val configFiles = Seq()

override def apis: Option[LiftApiModuleProvider[_ <: EndpointSchema]] = Some(ScalaOutRelayConf.api)


override def pluginMenuEntry: Option[Menu] = {
Some(Menu("scaleoutrelay", <span>Scale Out Relay</span>) /
"secure" / "plugins" / "scaleoutrelay"
>> LocGroup("pluginsGroup")
>> TestAccess ( () => Boot.userIsAllowed("/secure/index", Administration.Read))
>> Template(() => ClasspathTemplates("template" :: "ScaleOutRelay" :: Nil ) openOr <div>Template not found</div>)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package com.normation.plugins.scaleoutrelay

import bootstrap.liftweb.RudderConfig
import com.normation.box.IOToBox
import com.normation.cfclerk.domain.TechniqueVersion
import com.normation.eventlog.{EventActor, ModificationId}
import com.normation.inventory.domain.NodeId
import com.normation.rudder.domain.nodes.NodeGroup
import com.normation.rudder.domain.nodes.NodeGroupCategoryId
import com.normation.rudder.domain.nodes.NodeGroupId
import com.normation.rudder.domain.policies._
import com.normation.rudder.domain.queries._
import com.normation.rudder.rule.category.RuleCategoryId
import net.liftweb.common.Failure
import net.liftweb.common.Full

object ScaleOutRelayService {
private val nodeInfosService = RudderConfig.nodeInfoService
private val woLDAPNodeGroupRepository = RudderConfig.woNodeGroupRepository
private val woLDAPNodeRepository = RudderConfig.woNodeRepository
private val woDirectiveRepository = RudderConfig.woDirectiveRepository
private val woRuleRepository = RudderConfig.woRuleRepository
private val uuidGen = RudderConfig.stringUuidGenerator
private val policyServerManagementService = RudderConfig.policyServerManagementService
private val removeNodeService = RudderConfig.removeNodeService

def saveAllObjects(uuid: NodeId, actor: EventActor, reason:Option[String]) = {
for {
updatedNode <- createNodeToPolicyServerType(uuid)
commonDirective <- createCommonDirective(uuid)

directDistribPolicy = createDirectiveDistributePolicy(uuid)
ruleTarget = createPolicyServer(uuid)
nodeGroup = createNodeGroup(uuid)
ruleDistribPolicy = createRuleDistributePolicy(uuid)
ruleSetup = createRuleSetup(uuid)
modId = ModificationId(uuidGen.newUuid)
categoryId = NodeGroupCategoryId("SystemGroups")
activeTechniqueId = ActiveTechniqueId("distributePolicy")
activeTechniqueIdCommon = ActiveTechniqueId("common")

This comment has been minimized.

Copy link
@fanf

fanf Mar 16, 2020

It feels like these three strings should be defined as constants somewhere (in the object, most likely)


removedNode <- removeNodeService.removeNode(uuid, modId, actor)

g <- woLDAPNodeRepository.createNode(updatedNode, modId, actor, reason).toBox

This comment has been minimized.

Copy link
@fanf

fanf Mar 16, 2020

you can use _ <- ... - and it's better to do so - if you don't use the returned value.

a <- woLDAPNodeGroupRepository.createPolicyServerTarget(ruleTarget, modId, actor, reason).toBox
b <- woLDAPNodeGroupRepository.create(nodeGroup, categoryId, modId, actor, reason).toBox

This comment has been minimized.

Copy link
@fanf

fanf Mar 16, 2020

Avoid as much as possible toBox, it has a dire impact on performance (for an unknown reason for now).
So you can either change getNodeInfo and removeNodeService to IOResult (.toIO) or group all the last methods in one createRelayNode(node: NodeInfo). It would be better perhaps, since it allows to make clear that all of them are needed to have a working relay, and the method flow becomes:

node <- getNodeToPromote
_ <- delete(node.id)
_ <- createRelayFromNode(node)

which is what the you-in-three-months want to see :)

c <- woDirectiveRepository.saveSystemDirective(activeTechniqueId,directDistribPolicy, modId, actor, reason).toBox
d <- woDirectiveRepository.saveSystemDirective(activeTechniqueIdCommon,commonDirective, modId, actor, reason).toBox
e <- woRuleRepository.create(ruleSetup, modId, actor, reason).toBox
f <- woRuleRepository.create(ruleDistribPolicy, modId, actor, reason).toBox

} yield {
uuid
}
}

def createNodeToPolicyServerType(uuid: NodeId) = {

This comment has been minimized.

Copy link
@fanf

fanf Mar 16, 2020

Can you split the pure part from the effectful one? IE getNodeInfo from other parts? It can fail (for tons of reasons: LDAP connection error, missing entry, etc) and it's harder to test. Pure parts can only fail in very decidable way (and in that case, none) and a simple to test.

This comment has been minimized.

Copy link
@ElaadF

ElaadF Mar 16, 2020

Author Owner

My understanding is : getNodeInfo have many reason to fail, it would be easier to debug if I move getNodeInfo outside the for comprehension to get the error message ?

This comment has been minimized.

Copy link
@fanf

fanf Mar 16, 2020

more than that: you need to split appart as much as you can effectful code (IO, exception, etc) from pure code (pure function, no connection to db, etc - with the exception of logs which are tricky).
Pure function gets its value by its parameter (always - it can't get it by any other mean).

So in your case, you need to have:
1/ a getNode which will retrieve the node that will be promoted from LDAP,
2/ and in all other function, use that node (ie: if you need info from the node, pass it as a parameter).

createNodeGroup below is pure: it uses only information from its parameter to create its output, and whatever happens outside of it, each time you pass the same uuid you get the same NodeGroup.

createNodeToPolicyServerType is effectful (== impure). Depending of the state of LDAP directory, it does not return the same output.
So change it to:
1/

def getNodeToPromote(uuid: NodeId): IOResult[NodeInfo] = 
   nodeInfosService.getNodeInfo(uuid).notOptional(s"...")

2/

def createNodeToPolicyServerType(node: NodeInfo): NodeInfo = { //see, that can't fail
  node.copy(id = uuid, name = uuid.value, isSystem = true, isPolicyServer = true)
}```
for {
nodeInfos <- nodeInfosService.getNodeInfo(uuid)
node <- nodeInfos match {
case Some(nodeinf) => Full(nodeinf.node)
case _ => Failure(s"Update node to Policy Server failed, cannot find ${uuid.value}")
}
} yield {
node.copy(id = uuid, name = uuid.value, isSystem = true, isPolicyServer = true)
}
}

def createPolicyServer(uuid: NodeId) = {
PolicyServerTarget(uuid)
}

def createNodeGroup(uuid: NodeId) = {
val objectType = ObjectCriterion("node", Seq(Criterion("policyServerId", StringComparator, None),Criterion("agentName", AgentComparator, None)))
val attribute = Criterion("agentName", StringComparator)
val comparator = Equals
val value = "cfengine"

val attribute2 = Criterion("policyServerId", StringComparator)
val comparator2 = Equals
val value2 = uuid.value
NodeGroup(
NodeGroupId(s"hasPolicyServer-${uuid.value}")
, s"All classic Nodes managed by ${uuid.value} policy server"
, s"All classic Nodes known by Rudder directly connected to the ${uuid.value} server. This group exists only as internal purpose and should not be used to configure Nodes."
, Some(
Query(
NodeAndPolicyServerReturnType
, And
, List(CriterionLine(objectType, attribute, comparator, value)
, CriterionLine(objectType, attribute2, comparator2, value2))
)
)
, true
, Set()
, true
, true
)
}

def createDirectiveDistributePolicy(uuid: NodeId) = {
Directive(
DirectiveId(s"${uuid.value}-distributePolicy")
, TechniqueVersion("1.0")
, Map()
, s"${uuid.value}-Distribute Policy"
, "Distribute policy - Technical"
, None
, ""
, 0
, true
, true
, Tags(Set.empty)
)
}

def createCommonDirective(uuid: NodeId) = {
for {
nodeInfos <- nodeInfosService.getNodeInfo(uuid)

This comment has been minimized.

Copy link
@fanf

fanf Mar 16, 2020

You should not retrieve node several times for the method. Get "nodeInfo" as a parameter of your method.

hostname <- nodeInfos match {
case Some(n) => Full(n.hostname)
case _ => Failure(s"Cannot retrieve hostname of ${uuid.value}")

This comment has been minimized.

Copy link
@fanf

fanf Mar 16, 2020

Here, the error is not about hostname. The problem is that you may have no nodeInfo for that UUID. If it's 6.1 (ie with zio), you need a getNodeInfo.notOptionnal(s"Node with UUID ${xxx} is missing and can not be upgraded to relay") to specify that it's an error for that method to not be able to find the node.

}
policyserverId <- nodeInfos match {
case Some(n) => Full(n.policyServerId)
case _ => Failure(s"Cannot retrieve Policy Server ID of ${uuid.value}")
}
authorizedNetworks <- policyServerManagementService.getAuthorizedNetworks(uuid)
} yield {
val parameters =
Map (
"OWNER" -> Seq("${rudder.node.admin}")
, "UUID" -> Seq("${rudder.node.id}")
, "POLICYSERVER" -> Seq(hostname)
, "POLICYSERVER_ID" -> Seq(policyserverId.value)
, "POLICYSERVER_ADMIN" -> Seq("root")
, "ALLOWEDNETWORK" -> authorizedNetworks
)
Directive(
DirectiveId(s"common-${uuid.value}")
, TechniqueVersion("1.0")
, parameters
, s"Common-${uuid.value}"
, "Common - Technical"
, None
, ""
, 0
, true
, true
, Tags(Set.empty)
)
}
}

def createRuleDistributePolicy(uuid: NodeId) = {
Rule(
RuleId(s"${uuid.value}-DP")
, s"${uuid.value}-distributePolicy"
, RuleCategoryId("rootRuleCategory")
, Set(PolicyServerTarget(uuid))
, Set(DirectiveId(s"${uuid.value}-distributePolicy"))
, "Distribute Policy - Technical"
, "This rule allows to distribute policies to nodes"
, true
, true
)
}

def createRuleSetup(uuid: NodeId) = {
Rule(
RuleId(s"hasPolicyServer-${uuid.value}")
, s"Rudder system policy: basic setup (common)-${uuid.value}"
, RuleCategoryId("rootRuleCategory")
, Set(GroupTarget(NodeGroupId(s"hasPolicyServer-${uuid.value}")))
, Set(DirectiveId(s"common-${uuid.value}"))
, "Common - Technical"
, "This is the basic system rule which all nodes must have."
, true
, true
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.normation.plugins.scaleoutrelay.api


import com.normation.eventlog.EventActor
import com.normation.inventory.domain.NodeId
import com.normation.plugins.scaleoutrelay.ScaleOutRelayService
import com.normation.rudder.api.HttpAction.POST
import com.normation.rudder.repository.json.DataExtractor.CompleteJson
import com.normation.rudder.rest.EndpointSchema.syntax._
import com.normation.rudder.rest.RestUtils.toJsonError
import com.normation.rudder.rest.RestUtils.toJsonResponse
import com.normation.rudder.rest._
import com.normation.rudder.rest.lift.DefaultParams
import com.normation.rudder.rest.lift.LiftApiModule
import com.normation.rudder.rest.lift.LiftApiModuleProvider
import net.liftweb.common.Box
import net.liftweb.common.Full
import net.liftweb.http.LiftResponse
import net.liftweb.http.Req
import net.liftweb.json.JsonAST.JValue
import net.liftweb.json.JsonDSL._
import net.liftweb.json.NoTypeHints
import sourcecode.Line



sealed trait ScaleOutRelayApi extends EndpointSchema with GeneralApi with SortIndex
object ScaleOutRelayApi extends ApiModuleProvider[ScaleOutRelayApi] {

final case object PromoteNodeToRelay extends ScaleOutRelayApi with OneParam with StartsAtVersion10 {
val z = implicitly[Line].value
val description = "Promote a node to relay"
val (action, path) = POST / "scaleoutrelay" / "promote" / "{nodeId}"
}

override def endpoints: List[ScaleOutRelayApi] = ca.mrvisser.sealerate.values[ScaleOutRelayApi].toList.sortBy( _.z )
}

class ScaleOutRelayApiImpl(
restExtractorService: RestExtractorService
) extends LiftApiModuleProvider[ScaleOutRelayApi] {

api =>

implicit val formats = net.liftweb.json.Serialization.formats((NoTypeHints))
override def schemas = ScaleOutRelayApi

override def getLiftEndpoints(): List[LiftApiModule] = {
ScaleOutRelayApi.endpoints.map {
case ScaleOutRelayApi.PromoteNodeToRelay => PromoteNodeToRelay
}.toList
}

def extractNodeUUID(json: JValue): Box[String] = {

This comment has been minimized.

Copy link
@fanf

fanf Mar 16, 2020

Not sure why you need that? The method doesn't seems to do anything more than just CompleteJson.extractJsonString(json, "UUID") (and it doesn't seem to be called)

for {
nodeUUID <- CompleteJson.extractJsonString(json, "UUID")
} yield {
nodeUUID
}
}

def response(function: Box[JValue], req: Req, errorMessage: String, id: Option[String], dataName: String)(implicit action: String): LiftResponse = {
RestUtils.response(restExtractorService, dataName, id)(function, req, errorMessage)
}

object PromoteNodeToRelay extends LiftApiModule {
val schema = ScaleOutRelayApi.PromoteNodeToRelay
val restExtractor = api.restExtractorService

def process(version: ApiVersion, path: ApiPath, nodeId: String, req: Req, params: DefaultParams, authz: AuthzToken): LiftResponse = {
val response: JValue = "toto" -> "Ok"
val uuid = ScaleOutRelayService.saveAllObjects(NodeId(nodeId), EventActor("rudder"), Some(s"Promote node ${nodeId} to relay"))
println(uuid)
uuid match {
case Full(id) => toJsonResponse(None,id.value)("promoteToRelay",true)
case _ => toJsonError(None, s"Error when trying to promote mode $nodeId")("promoteToRelay",true)
}
}
}
}

0 comments on commit 432704b

Please sign in to comment.