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 #5843: Add user defined (via API) node properties #671

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
9 changes: 8 additions & 1 deletion rudder-core/src/main/resources/ldap/rudder.schema
Expand Up @@ -377,6 +377,13 @@ attributetype ( RudderAttributes:351
EQUALITY caseExactMatch
SUBSTR caseIgnoreSubstringsMatch )

attributetype ( RudderAttributes:352
NAME 'serializedNodeProperty'
DESC 'Serialization of a node property, typically a key=value pair)'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
EQUALITY caseExactMatch
SUBSTR caseIgnoreSubstringsMatch )

#######################################################
################ Object Classes ######################
#######################################################
Expand All @@ -402,7 +409,7 @@ objectclass ( RudderObjectClasses:1
SUP top
STRUCTURAL
MUST ( nodeId $ cn $ isSystem $ isBroken)
MAY ( description $ serializedAgentRunInterval ) )
MAY ( description $ serializedNodeProperty $ serializedAgentRunInterval ) )

objectclass ( RudderObjectClasses:2
NAME 'rudderPolicyServer'
Expand Down
Expand Up @@ -87,6 +87,9 @@ object RudderLDAPConstants extends Loggable {
val A_SERIALIZED_AGENT_RUN_INTERVAL = "serializedAgentRunInterval"


val A_NODE_PROPERTY = "serializedNodeProperty"


val A_PRIORITY = "directivePriority"
val A_LONG_DESCRIPTION = "longDescription"
val A_SERIAL = "serial"
Expand Down
Expand Up @@ -37,6 +37,8 @@ package com.normation.rudder.domain.nodes
import com.normation.inventory.domain.NodeId
import com.normation.utils.HashcodeCaching
import com.normation.rudder.reports.ReportingConfiguration
import net.liftweb.json.JsonAST.JObject
import org.joda.time.DateTime

/**
* The entry point for a REGISTERED node in Rudder.
Expand All @@ -45,11 +47,54 @@ import com.normation.rudder.reports.ReportingConfiguration
*
*/
case class Node(
id : NodeId
, name : String
, description : String
, isBroken : Boolean
, isSystem : Boolean
, isPolicyServer : Boolean
id : NodeId
, name : String
, description : String
, isBroken : Boolean
, isSystem : Boolean
, isPolicyServer : Boolean
, creationDate : DateTime
Copy link
Member

Choose a reason for hiding this comment

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

why to you add the creationDate ?

Copy link
Member Author

Choose a reason for hiding this comment

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

To be able to have it in display where we don't need nodeInfo (because we also have inventory)

, nodeReportingConfiguration: ReportingConfiguration
, properties : Seq[NodeProperty]
) extends HashcodeCaching



case class NodeProperty(name: String, value: String)

object JsonSerialisation {

import net.liftweb.json.JsonDSL._
import net.liftweb.json._


implicit class JsonNodeProperty(x: NodeProperty) {
def toLdapJson(): JObject = (
( "name" , x.name )
~ ( "value" , x.value )
)
}

implicit class JsonNodeProperties(props: Seq[NodeProperty]) {
import net.liftweb.json.Serialization.write
implicit val formats = DefaultFormats

private[this] def json(x: NodeProperty): JObject = (
( "name" , x.name )
~ ( "value" , x.value )
)

def toApiJson(): JArray = {
JArray(props.map(json(_)).toList)
}
}

def unserializeLdapNodeProperty(value:String): NodeProperty = {
import net.liftweb.json.JsonParser._
implicit val formats = DefaultFormats

parse(value).extract[NodeProperty]
}

}

Expand Up @@ -42,7 +42,8 @@ import BuildFilter._
import scala.collection.{SortedMap,SortedSet}
import com.normation.rudder.services.queries.SpecialFilter
import com.normation.utils.HashcodeCaching

import com.normation.rudder.domain.NodeDit
import com.normation.rudder.domain.RudderLDAPConstants.A_NODE_PROPERTY

/*
* Here we define all data needed logic by the to create the search
Expand All @@ -69,7 +70,7 @@ case object QueryMachineDn extends DnType
case object QueryNodeDn extends DnType
case object QuerySoftwareDn extends DnType

class DitQueryData(dit:InventoryDit) {
class DitQueryData(dit:InventoryDit, nodeDit: NodeDit) {
private val peObjectCriterion = ObjectCriterion(OC_PE, Seq(
// Criterion(A_MACHINE_UUID, StringComparator),
// Criterion(A_MACHINE_DN, StringComparator), //we don't want to search on that
Expand Down Expand Up @@ -224,10 +225,10 @@ class DitQueryData(dit:InventoryDit) {
)),
ObjectCriterion(A_EV, Seq(
Criterion("name.value", JsonComparator(A_EV,"=") )
))/*,
ObjectCriterion(OC_GROUP_OF_DNS,Seq(
Criterion(A_NAME,GroupOfDnsComparator)
))*/ // Hidding a code difficult to import
))
, ObjectCriterion(A_NODE_PROPERTY, Seq(
Criterion("name.value", JsonComparator(A_NODE_PROPERTY,"=") )
))
)

val criteriaMap : SortedMap[String,ObjectCriterion] = SortedMap[String,ObjectCriterion]() ++ (criteriaSet map { crit => (crit.objectType,crit) })
Expand Down Expand Up @@ -281,6 +282,7 @@ case class LDAPObjectType(
"software" -> LDAPObjectType(dit.SOFTWARE.dn, One, LDAPObjectTypeFilter(ALL), None, DNJoin),
"node" -> LDAPObjectType(dit.NODES.dn, One, LDAPObjectTypeFilter(ALL), None, DNJoin),
"nodeAndPolicyServer" -> LDAPObjectType(dit.NODES.dn, One, LDAPObjectTypeFilter(ALL), None, DNJoin),
"serializedNodeProperty" -> LDAPObjectType(nodeDit.NODES.dn, One, LDAPObjectTypeFilter(ALL),None, DNJoin),
"networkInterfaceLogicalElement" -> LDAPObjectType(dit.NODES.dn, Sub, LDAPObjectTypeFilter(IS(OC_NET_IF)), None, ParentDNJoin),
"process" -> LDAPObjectType(dit.NODES.dn, One, LDAPObjectTypeFilter(ALL), None, DNJoin),
"virtualMachineLogicalElement" -> LDAPObjectType(dit.NODES.dn, Sub, LDAPObjectTypeFilter(IS(OC_VM_INFO)), None, ParentDNJoin),
Expand Down Expand Up @@ -308,6 +310,7 @@ case class LDAPObjectType(
"software" -> QuerySoftwareDn,
"node" -> QueryNodeDn,
"nodeAndPolicyServer" -> QueryNodeDn,
"serializedNodeProperty" -> QueryNodeDn,
"networkInterfaceLogicalElement" -> QueryNodeDn,
"fileSystemLogicalElement" -> QueryNodeDn,
"process" -> QueryNodeDn,
Expand All @@ -334,6 +337,7 @@ case class LDAPObjectType(
"software" -> DNJoin,
"node" -> DNJoin,
"nodeAndPolicyServer" -> DNJoin,
"serializedNodeProperty" -> DNJoin,
"networkInterfaceLogicalElement" -> ParentDNJoin,
"fileSystemLogicalElement" -> ParentDNJoin,
"process" -> DNJoin,
Expand Down
Expand Up @@ -49,6 +49,7 @@ import com.normation.rudder.domain.RudderLDAPConstants._
import com.normation.rudder.domain.{NodeDit,RudderDit}
import com.normation.rudder.domain.servers._
import com.normation.rudder.domain.nodes.Node
import com.normation.rudder.domain.nodes.JsonSerialisation._
import com.normation.rudder.domain.queries._
import com.normation.rudder.domain.policies._
import com.normation.rudder.domain.nodes._
Expand Down Expand Up @@ -108,9 +109,13 @@ class LDAPEntityMapper(
case Some(interval) => entry +=! (A_SERIALIZED_AGENT_RUN_INTERVAL, Printer.compact(JsonAST.render(serializeAgentRunInterval(interval))))
case _ =>
}

entry +=! (A_NODE_PROPERTY, node.properties.map(x => Printer.compact(JsonAST.render(x.toLdapJson))):_* )

entry
}


def serializeAgentRunInterval(agentInterval: AgentRunInterval) : JObject = {
import net.liftweb.json.JsonDSL._
( "overrides" , agentInterval.overrides ) ~
Expand All @@ -131,21 +136,21 @@ class LDAPEntityMapper(
if(e.isA(OC_RUDDER_NODE)||e.isA(OC_POLICY_SERVER_NODE)) {
//OK, translate
for {
id <- nodeDit.NODES.NODE.idFromDn(e.dn) ?~! s"Bad DN found for a Node: ${e.dn}"
name = e(A_NAME).getOrElse("")
description = e(A_DESCRIPTION).getOrElse("")
agentRunInterval = e(A_SERIALIZED_AGENT_RUN_INTERVAL).map(unserializeAgentRunInterval(_))
id <- nodeDit.NODES.NODE.idFromDn(e.dn) ?~! s"Bad DN found for a Node: ${e.dn}"
date <- e.getAsGTime(A_OBJECT_CREATION_DATE) ?~! s"Can not find mandatory attribute '${A_OBJECT_CREATION_DATE}' in entry"
} yield {
Node(
id
, name
, description
, e(A_NAME).getOrElse("")
, e(A_DESCRIPTION).getOrElse("")
, e.getAsBoolean(A_IS_BROKEN).getOrElse(false)
, e.getAsBoolean(A_IS_SYSTEM).getOrElse(false)
, e.isA(OC_POLICY_SERVER_NODE)
, date.dateTime
, ReportingConfiguration(
agentRunInterval
)
e(A_SERIALIZED_AGENT_RUN_INTERVAL).map(unserializeAgentRunInterval(_))
)
, e.valuesFor(A_NODE_PROPERTY).map(unserializeLdapNodeProperty(_)).toSeq
)
}
} else {
Expand Down
Expand Up @@ -45,6 +45,7 @@ import com.unboundid.ldap.sdk.{DN,Filter}
import com.normation.ldap.sdk._
import com.normation.rudder.services.user.PersonIdentService
import com.normation.rudder.domain.NodeDit
import com.normation.rudder.services.nodes.NodeInfoServiceImpl


class WoLDAPNodeRepository(
Expand All @@ -65,9 +66,10 @@ class WoLDAPNodeRepository(
* If the node is a system one, the methods fails.
*/
def update(node:Node, modId: ModificationId, actor:EventActor, reason:Option[String]) : Box[Node] = {
import NodeInfoServiceImpl.{nodeInfoAttributes => attrs}
repo.synchronized { for {
con <- ldap
existingEntry <- con.get(nodeDit.NODES.NODE.dn(node.id.value)) ?~! s"Cannot update node with id ${node.id.value} : there is no node with that id"
existingEntry <- con.get(nodeDit.NODES.NODE.dn(node.id.value), attrs:_*) ?~! s"Cannot update node with id ${node.id.value} : there is no node with that id"
oldNode <- mapper.entryToNode(existingEntry) ?~! "Error when transforming LDAP entry into a node for id ${node.id.value} . Entry: ${existingEntry}"
// here goes the check that we are not updating policy server
nodeEntry = mapper.nodeToEntry(node)
Expand All @@ -78,4 +80,4 @@ class WoLDAPNodeRepository(
} }
}

}
}
Expand Up @@ -42,7 +42,7 @@ import com.normation.rudder.domain.RudderDit
import com.normation.rudder.domain.NodeDit
import net.liftweb.common._
import net.liftweb.util.Helpers._
import com.normation.rudder.domain.nodes.NodeInfo
import com.normation.rudder.domain.nodes.{NodeInfo, Node}
import com.normation.rudder.domain.RudderLDAPConstants._
import com.normation.inventory.ldap.core.LDAPConstants._
import com.normation.rudder.domain.Constants._
Expand Down Expand Up @@ -79,19 +79,15 @@ trait NodeInfoService {
*/
def getNodeInfo(nodeId: NodeId) : Box[NodeInfo]

/**
* Return a seq of NodeInfo from a seq of NodeId.
* If any of them fails, then we return Failure
* @param nodeId
* @return
*/
// def find(nodeIds: Seq[NodeId]) : Box[Seq[NodeInfo]]


/**
* Get all node ids
* Get the node (not inventory).
* Most of the info are also in node info,
* but for some specific case (nodeProperties for ex),
* we need them.
*/
// def getAllIds() : Box[Seq[NodeId]]
def getNode(nodeId: NodeId): Box[Node]


/**
* Get all node infos.
Expand All @@ -102,12 +98,6 @@ trait NodeInfoService {
*/
def getAll() : Box[Map[NodeId, NodeInfo]]

/**
* Get all "simple" node ids (i.e, all user nodes,
* for example, NOT policy servers)
*/
// def getAllUserNodeIds() : Box[Seq[NodeId]]

/**
* Get all systen node ids, for example
* policy server node ids.
Expand All @@ -121,16 +111,26 @@ object NodeInfoServiceImpl {
}

class NodeInfoServiceImpl(
nodeDit : NodeDit,
rudderDit:RudderDit,
inventoryDit:InventoryDit,
ldap:LDAPConnectionProvider[RoLDAPConnection],
ldapMapper:LDAPEntityMapper,
inventoryMapper:InventoryMapper,
inventoryDitService:InventoryDitService
nodeDit : NodeDit
, rudderDit : RudderDit
, inventoryDit : InventoryDit
, ldap : LDAPConnectionProvider[RoLDAPConnection]
, ldapMapper : LDAPEntityMapper
, inventoryMapper : InventoryMapper
, inventoryDitService: InventoryDitService
) extends NodeInfoService with Loggable {
import NodeInfoServiceImpl._

def getNode(nodeId: NodeId): Box[Node] = {
for {
con <- ldap
entry <- con.get(nodeDit.NODES.NODE.dn(nodeId.value), nodeInfoAttributes:_*) ?~! s"Node with ID '${nodeId.value}' was not found"
node <- ldapMapper.entryToNode(entry)
} yield {
node
}
}

def getLDAPNodeInfo(nodeId: NodeId) : Box[LDAPNodeInfo] = {
logger.trace("Fetching node info for node id %s".format(nodeId.value))
for {
Expand Down
Expand Up @@ -669,7 +669,9 @@ class AcceptFullInventoryInNodeOu(
, false
, false
, isPolicyServer
, DateTime.now // won't be used on save - dummy value
, ReportingConfiguration(None) // use global schedule
, Seq() //no user properties for now
)

val entry = ldapEntityMapper.nodeToEntry(node)
Expand Down
Expand Up @@ -32,6 +32,7 @@ description: #54-Ubuntu SMP Thu Dec 10 17:23:29 UTC 2009
isSystem: false
isBroken: false
createTimestamp: 20070101000000Z
serializedNodeProperty: {"name":"foo", "value":"bar" }

dn: nodeId=node2,ou=Nodes,cn=rudder-configuration
objectClass: top
Expand Down Expand Up @@ -187,6 +188,7 @@ agentName: Nova
policyServerId:root-policy-server
ipHostNumber: 192.168.56.101
ipHostNumber: 127.0.0.1
environmentVariable: {"name":"SHELL","value":"/bin/sh"}

# Example of a node
dn: nodeId=node2,ou=Nodes,ou=Accepted Inventories,ou=Inventories,cn=rudder-configuration
Expand Down