<commit_before>/*
*************************************************************************************
* 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.rudder.services.servers

import org.joda.time.DateTime
import com.normation.utils.Control.sequence
import com.normation.rudder.domain.nodes._
import scala.collection.mutable.ArrayBuffer
import net.liftweb.common.EmptyBox
import net.liftweb.common.EmptyBox
import com.normation.inventory.domain.NodeId
import com.normation.rudder.services.policies.targetNodeConfiguration
import com.normation.inventory.domain.AgentType
import com.normation.rudder.domain.policies.RuleId
import com.normation.rudder.domain.policies.Rule
import com.normation.utils.User
import com.normation.eventlog.{EventLogNode,EventActor}
import scala.collection._
import com.normation.rudder.domain.servers._
import com.normation.rudder.domain._
import com.normation.rudder.services.servers._
import org.slf4j.{Logger,LoggerFactory}
import com.normation.rudder._
import com.normation.rudder.exceptions._
import com.normation.rudder.services.policies._
import com.normation.rudder.repository._
import com.normation.utils.StringUuidGenerator
import java.nio.channels._
import java.io.File
import java.io.RandomAccessFile
import org.apache.commons.io.FileUtils
import scala.reflect.BeanProperty
import org.slf4j.{Logger,LoggerFactory}
import com.normation.cfclerk.services._
import net.liftweb.common._
import com.normation.cfclerk.domain._
import com.normation.rudder.domain.transporter._
import com.normation.rudder.domain.eventlog._
import com.normation.rudder.services.eventlog._
import com.normation.eventlog._
import com.normation.inventory.domain._
import com.normation.rudder.domain.policies.RuleWithCf3PolicyDraft
import com.normation.exceptions.TechnicalException

/**
 * Implementation of the Node Configuration service
 * It manages the NodeConfiguration content (the cache of the deployed conf)
 * @author <NAME>
 *
 */
class NodeConfigurationServiceImpl(
    policyTranslator    : TemplateWriter, 
    repository          : NodeConfigurationRepository,
    techniqueRepository : TechniqueRepository,
    lockFolder          : String
    ) extends NodeConfigurationService {

  val LOGGER = LoggerFactory.getLogger(classOf[NodeConfigurationServiceImpl])

  LOGGER.info("Creating lock folder at {}", lockFolder)
  createLockFolder()
  val fileLock = new File(lockFolder , "updateNodes.lock");
  val channel = new RandomAccessFile(fileLock, "rw").getChannel();
  var lock = channel.tryLock(); // we want an early failure
  lock.release();
  
  private def createLockFolder() {
    FileUtils.forceMkdir(new File(lockFolder))
  }

  private[this] def inLock[T]( block: => T ) : T = {
    try {
      lock = channel.tryLock();
      block
    } catch {
      // File is already locked in this thread or virtual server
      case ex : OverlappingFileLockException => LOGGER.debug("Concurrent acces for method %s".format(Thread.currentThread.getStackTrace()(0).getMethodName))
      throw ex
    } finally {
      // Release the lock
      lock.release();
    }
  }
  
  def findNode(nodeId: NodeId) : Option[NodeConfiguration] = {
    inLock {
      repository.findNodeConfiguration(nodeId) match {
        case Full(node) => Some(node)
        case Empty => None
        case f:Failure => throw new TechnicalException("Error when trying to retrieve node %s. Message was %s".format(nodeId,f.messageChain))
      }
    }
  }

  def getMultipleNodeConfigurations(ids : Seq[NodeId]) : Set[NodeConfiguration] = {
    inLock {
      repository.getMultipleNodeConfigurations(ids) match {
        case Full(nodes) => nodes
        case Empty => Set()
        case Failure(m,_,_) => throw new TechnicalException("Error when trying to retrieve node %s. Message was %s".format(ids,m))
      }
    }
  }
  
  /**
   * Find all servers
   * @return
   */
  def getAllNodeConfigurations() : Map[String, NodeConfiguration] = {
    inLock {
      repository.getAll() match {
        case Full(map) => map
        case Empty => Map()
        case Failure(m,_,_) => throw new TechnicalException("Error when trying to retrieve all nodes. Message was %s".format(m))
      }
    }
  }

  
  /**
   * Update a node configuration using a targetNodeConfiguration :
   * update the directives and the node context, as well as the agentsName 
   * (well, every fields actually)
   * @param target
   * @return
   */
  def updateNodeConfiguration(target : targetNodeConfiguration) : Box[NodeConfiguration] = {
    //create a new node configuration based on "target" datas
    def createNodeConfiguration() : NodeConfiguration = {
      //we need to decide if it's a root node config or a simple one
      if(target.nodeInfo.isPolicyServer) {
          new RootNodeConfiguration(
            target.nodeInfo.id.value,
            Seq(),
            Seq(),
            false,
            // when creating a nodeconfiguration, there is no current
            currentMinimalNodeConfig = new MinimalNodeConfig( 
                "", "", Seq(), "", ""
            ),
            targetMinimalNodeConfig = new MinimalNodeConfig(
                target.nodeInfo.name ,
                target.nodeInfo.hostname,
                target.nodeInfo.agentsName,
                target.nodeInfo.policyServerId.value,
                target.nodeInfo.localAdministratorAccountName                   
            ),
            None,
            Map(),
            target.nodeContext
          )
      } else {
          new SimpleNodeConfiguration(
            target.nodeInfo.id.value,
            Seq(),
            Seq(),
            false,
            // when creating a nodeconfiguration, there is no current
            currentMinimalNodeConfig = new MinimalNodeConfig( 
                "", "", Seq(), "", ""
            ),
            targetMinimalNodeConfig = new MinimalNodeConfig(
                target.nodeInfo.name ,<commit_msg>Ref #1971 root node configuration update now set isPolicyServer to true<commit_after>/*
*************************************************************************************
* 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.rudder.services.servers

import org.joda.time.DateTime
import com.normation.utils.Control.sequence
import com.normation.rudder.domain.nodes._
import scala.collection.mutable.ArrayBuffer
import net.liftweb.common.EmptyBox
import net.liftweb.common.EmptyBox
import com.normation.inventory.domain.NodeId
import com.normation.rudder.services.policies.targetNodeConfiguration
import com.normation.inventory.domain.AgentType
import com.normation.rudder.domain.policies.RuleId
import com.normation.rudder.domain.policies.Rule
import com.normation.utils.User
import com.normation.eventlog.{EventLogNode,EventActor}
import scala.collection._
import com.normation.rudder.domain.servers._
import com.normation.rudder.domain._
import com.normation.rudder.services.servers._
import org.slf4j.{Logger,LoggerFactory}
import com.normation.rudder._
import com.normation.rudder.exceptions._
import com.normation.rudder.services.policies._
import com.normation.rudder.repository._
import com.normation.utils.StringUuidGenerator
import java.nio.channels._
import java.io.File
import java.io.RandomAccessFile
import org.apache.commons.io.FileUtils
import scala.reflect.BeanProperty
import org.slf4j.{Logger,LoggerFactory}
import com.normation.cfclerk.services._
import net.liftweb.common._
import com.normation.cfclerk.domain._
import com.normation.rudder.domain.transporter._
import com.normation.rudder.domain.eventlog._
import com.normation.rudder.services.eventlog._
import com.normation.eventlog._
import com.normation.inventory.domain._
import com.normation.rudder.domain.policies.RuleWithCf3PolicyDraft
import com.normation.exceptions.TechnicalException

/**
 * Implementation of the Node Configuration service
 * It manages the NodeConfiguration content (the cache of the deployed conf)
 * @author <NAME>
 *
 */
class NodeConfigurationServiceImpl(
    policyTranslator    : TemplateWriter, 
    repository          : NodeConfigurationRepository,
    techniqueRepository : TechniqueRepository,
    lockFolder          : String
    ) extends NodeConfigurationService {

  val LOGGER = LoggerFactory.getLogger(classOf[NodeConfigurationServiceImpl])

  LOGGER.info("Creating lock folder at {}", lockFolder)
  createLockFolder()
  val fileLock = new File(lockFolder , "updateNodes.lock");
  val channel = new RandomAccessFile(fileLock, "rw").getChannel();
  var lock = channel.tryLock(); // we want an early failure
  lock.release();
  
  private def createLockFolder() {
    FileUtils.forceMkdir(new File(lockFolder))
  }

  private[this] def inLock[T]( block: => T ) : T = {
    try {
      lock = channel.tryLock();
      block
    } catch {
      // File is already locked in this thread or virtual server
      case ex : OverlappingFileLockException => LOGGER.debug("Concurrent acces for method %s".format(Thread.currentThread.getStackTrace()(0).getMethodName))
      throw ex
    } finally {
      // Release the lock
      lock.release();
    }
  }
  
  def findNode(nodeId: NodeId) : Option[NodeConfiguration] = {
    inLock {
      repository.findNodeConfiguration(nodeId) match {
        case Full(node) => Some(node)
        case Empty => None
        case f:Failure => throw new TechnicalException("Error when trying to retrieve node %s. Message was %s".format(nodeId,f.messageChain))
      }
    }
  }

  def getMultipleNodeConfigurations(ids : Seq[NodeId]) : Set[NodeConfiguration] = {
    inLock {
      repository.getMultipleNodeConfigurations(ids) match {
        case Full(nodes) => nodes
        case Empty => Set()
        case Failure(m,_,_) => throw new TechnicalException("Error when trying to retrieve node %s. Message was %s".format(ids,m))
      }
    }
  }
  
  /**
   * Find all servers
   * @return
   */
  def getAllNodeConfigurations() : Map[String, NodeConfiguration] = {
    inLock {
      repository.getAll() match {
        case Full(map) => map
        case Empty => Map()
        case Failure(m,_,_) => throw new TechnicalException("Error when trying to retrieve all nodes. Message was %s".format(m))
      }
    }
  }

  
  /**
   * Update a node configuration using a targetNodeConfiguration :
   * update the directives and the node context, as well as the agentsName 
   * (well, every fields actually)
   * @param target
   * @return
   */
  def updateNodeConfiguration(target : targetNodeConfiguration) : Box[NodeConfiguration] = {
    //create a new node configuration based on "target" datas
    def createNodeConfiguration() : NodeConfiguration = {
      //we need to decide if it's a root node config or a simple one
      if(target.nodeInfo.isPolicyServer) {
          new RootNodeConfiguration(
            target.nodeInfo.id.value,
            Seq(),
            Seq(),
            true,
            // when creating a nodeconfiguration, there is no current
            currentMinimalNodeConfig = new MinimalNodeConfig( 
                "", "", Seq(), "", ""
            ),
            targetMinimalNodeConfig = new MinimalNodeConfig(
                target.nodeInfo.name ,
                target.nodeInfo.hostname,
                target.nodeInfo.agentsName,
                target.nodeInfo.policyServerId.value,
                target.nodeInfo.localAdministratorAccountName                   
            ),
            None,
            Map(),
            target.nodeContext
          )
      } else {
          new SimpleNodeConfiguration(
            target.nodeInfo.id.value,
            Seq(),
            Seq(),
            false,
            // when creating a nodeconfiguration, there is no current
            currentMinimalNodeConfig = new MinimalNodeConfig( 
                "", "", Seq(), "", ""
            ),
            targetMinimalNodeConfig = new MinimalNodeConfig(
                target.nodeInfo.name ,
{'fraction_non_alphanumeric': 0.11859892122261437, 'fraction_numerical': 0.00253046547246454, 'mean_word_length': 3.2847360912981456, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2282653', 'n_tokens_mistral': 4007, 'n_tokens_neox': 3899, 'n_words': 1194}