Project

General

Profile

« Previous | Next » 

Revision f16bbcd3

Added by François ARMAND over 6 years ago

Fixes #11640: Autumn cleaning before 4.3 (ldap-inventory)

View differences:

inventory-api/src/main/scala/com/normation/inventory/domain/DataTypes.scala
* Comparison are really important in Version
*/
final class Version(val value:String) extends Comparable[Version] {
require(nonEmpty(value))
require(!isEmpty(value))
override def compareTo(other:Version) = this.value.compareTo(other.value)
override def toString() = "[%s]".format(value)
inventory-api/src/main/scala/com/normation/inventory/domain/Identifiers.scala
trait Uuid {
val value:String
require(nonEmpty(value), "An UUID can not have a null or empty value (value: %s)".format(value))
require(!isEmpty(value), "An UUID can not have a null or empty value (value: %s)".format(value))
}
case class NodeId(val value:String) extends Uuid with HashcodeCaching
inventory-api/src/main/scala/com/normation/inventory/domain/MemorySize.scala
package com.normation.inventory.domain
import com.normation.exceptions.TechnicalException
import com.normation.utils.HashcodeCaching
......
}
object MemorySize {
import scala.util.control.Exception._
/*
* We should accept:
* - no decimal numbers
......
* It returns a string representation of the value and a string representation of the unit
* to let it be i18n-able
*/
def prettyMo(m:MemorySize) : (String,String) = {
private def prettyMo(m:MemorySize) : (String,String) = {
val x = prettyPrint(m, "B" :: "kB" :: "MB" :: "GB" :: "TB" :: "PB" :: "EB" :: "ZB" :: "YB" :: Nil)
(x._1.bigDecimal.stripTrailingZeros.toPlainString,x._2)
}
......
val pres3 = new java.math.MathContext(3)
def round(m:BigDecimal) : BigDecimal = {
if(m < 1) throw new TechnicalException("Could not round number strictly smaller than one. Seems to be an algo error, check with the dev.")
if(m < 1) throw new IllegalArgumentException("Could not round number strictly smaller than one. Seems to be an algo error, check with the dev.")
else if(m < 1000) m.round(pres3)
else BigDecimal(m.toBigInt)
}
def rec(m:BigDecimal, u:List[String]) : (BigDecimal,String)= u match {
case Nil => throw new TechnicalException("At list one unit have to be provided for the memory pretty printer. Look around in the class where the dev don't use that method correctly.")
case Nil => throw new IllegalArgumentException("At list one unit have to be provided for the memory pretty printer. Look around in the class where the dev don't use that method correctly.")
case h::Nil => //no bigger units, stop here
(round(m),h)
case h::tail =>
inventory-provisioning-web/pom.xml
<version>${commons-io-version}</version>
</dependency>
<!-- Spring Framework Project libraries -->
<!-- Needed to use spring configuration by annotation -->
<dependency>
<groupId>com.normation</groupId>
<artifactId>spring-run-dependencies</artifactId>
<version>${spring-run-dep-version}</version>
<type>pom</type>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
</dependency>
<!-- the slf4j commons-logging replacement -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
inventory-repository/pom.xml
<artifactId>utils</artifactId>
<version>${rudder-version}</version>
</dependency>
<dependency>
<groupId>com.normation</groupId>
<artifactId>historization-api</artifactId>
<version>${rudder-version}</version>
</dependency>
<dependency>
<groupId>com.normation</groupId>
<artifactId>scala-ldap</artifactId>
......
<artifactId>lift-json_${scala-binary-version}</artifactId>
<version>${lift-version}</version>
</dependency>
<dependency>
<groupId>net.liftweb</groupId>
<artifactId>lift-util_${scala-binary-version}</artifactId>
<version>${lift-version}</version>
</dependency>
</dependencies>
</project>
inventory-repository/src/main/scala/com/normation/history/HistoryLog.scala
/*
*************************************************************************************
* Copyright 2011 Normation SAS
*************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*************************************************************************************
*/
package com.normation.history
import org.joda.time.DateTime
/**
* Represent history data.
* The actual data type is let to be defined.
*/
trait HistoryLog[ID,V, T] {
/**
* Id of the history log.
* One log has seceral version for
* only one ID (the couple (ID,version) is unique)
*/
def id:ID
/**
* Date and Time for which the data are saved
* @return
*/
def datetime : DateTime
/**
* History type, should be linked to T
*/
def historyType : String
/**
* Version of the history
* Version must be comparable, but du to
* Inconsistencies between libraries and APIs,
* we are not able to provide a type constrain.
* bigger are newer
* @return
*/
def version:V
/**
* Actual data saved in the history
* @return
*/
def data : T
}
inventory-repository/src/main/scala/com/normation/history/HistoryLogRepository.scala
/*
*************************************************************************************
* Copyright 2011 Normation SAS
*************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*************************************************************************************
*/
package com.normation.history
import net.liftweb.common.Box
import org.joda.time.DateTime
trait WriteOnlyHistoryLogRepository[ID, V, T, HLog <: HistoryLog[ID,V, T]] {
/**
* Save a report and return the ID of the saved report, and
* it's version
* @param historyLog
* @return
*/
def save(id:ID,data:T,datetime:DateTime = DateTime.now) : Box[HLog]
}
trait ReadOnlyHistoryLogRepository[ID, V, T, HLog <: HistoryLog[ID,V, T]] {
/**
* Retrieve all ids known by the repository
*/
def getIds : Box[Seq[ID]]
/**
* Get the list of record for the given ID
* @return
* Failure(message) if an error happened
* Empty if a not specified error happened
* Full(seq) if that id exists. Seq may be empty
* if no version are available.
*/
def getAll(id:ID) : Box[Seq[HLog]]
/**
* Get the last record for the given ID.
* @return
* Failure(message) or Empty if an error happened, or
* if the id does not exists or has no recorded history
* Full(hlog) the last recorded version of hlog
*/
def getLast(id:ID) : Box[HLog]
/**
* Get the last record for the given ID and version.
* @return
* Failure(message) or Empty if an error happened, or
* if the id does not exists or has no such version in
* recorded history
* Full(hlog) the recorded version of hlog
*/
def get(id:ID, version:V) : Box[HLog]
/**
* Return the list of version for ID.
* @return
* Failure(message) if an error happened
* Empty if a not specified error happened
* Full(seq) if that id exists. Seq may be empty
* if no version are available.
* Seq is sorted with last version (most recent) first
* and so ( versions.head > versions.head.head )
*/
def versions(id:ID) : Box[Seq[V]]
}
trait HistoryLogRepository[ID, V, T, HLog <: HistoryLog[ID,V, T]] extends
WriteOnlyHistoryLogRepository[ID, V, T, HLog] with
ReadOnlyHistoryLogRepository[ID, V, T, HLog] {}
inventory-repository/src/main/scala/com/normation/history/impl/FileHistoryLogRepository.scala
/*
*************************************************************************************
* Copyright 2011 Normation SAS
*************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*************************************************************************************
*/
package com.normation.history
package impl
import java.io.File
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import net.liftweb.common._
import net.liftweb.util.ControlHelpers.tryo
import FileHistoryLogRepository._
/**
* A trait that allows to write and read datas of type T
* to/from files
*/
trait FileMarshalling[T] {
def fromFile(in:File) : Box[T]
def toFile(out:File, data: T) : Box[T]
}
trait IdToFilenameConverter[ID] {
/**
* Create the filename from the id, and vice versa
*/
def idToFilename(id:ID) : String
/**
* Get the ID from a path name
*/
def filenameToId(name:String) : ID
}
/**
* An implementation of HistoryLogRepository that save datas in
* FileSystem, using a layout like:
*
* /(d)root_dir/
* |- (d)history_id1
* | |- (f)datetime1
* | |- (f)datetime2
* | `...
* ` (d)history_name2
*
* Any datas type may be used, as long as they can be read/write from
* files
*/
class FileHistoryLogRepository[ID,T](
val rootDir:String,
val marshaller:FileMarshalling[T],
val converter:IdToFilenameConverter[ID]
) extends HistoryLogRepository[ID, DateTime, T, DefaultHLog[ID,T]] {
type HLog = DefaultHLog[ID,T]
//when the the class is instanciated, try to create the directory
//we don't want to catch exception here
root()
//we don't want to catch exception here
private def root() : Box[File] = {
for {
dir <- tryo(new File(rootDir))
isValid <- if(dir.exists && !dir.isDirectory) Failure(s"'${dir.getAbsolutePath}' exists and is not a directory") else Full("OK")
isCreated <- if(!dir.exists) tryo(dir.mkdirs) else Full("OK")
} yield {
dir
}
}
//we don't want to catch exception here
private def idDir(id:ID) = {
for {
r <- root
dir <- tryo(new File(r, converter.idToFilename(id)))
isValid <- if(dir.exists && !dir.isDirectory) Failure(s"'${dir.getAbsolutePath}' and is not a directory") else Full("OK")
isCreated <- if(!dir.exists) tryo(dir.mkdirs) else Full("OK")
} yield {
dir
}
}
//we don't want to catch exception here
private def exists(id:ID) = {
(for{
r <- root
dir <- tryo(new File(r, converter.idToFilename(id)))
} yield dir.exists && dir.isDirectory).getOrElse(false)
}
/**
* Save a report and return the ID of the saved report, and
* it's version
* @param historyLog
* @return
*/
def save(id:ID,data:T,datetime:DateTime = DateTime.now) : Box[HLog] = {
converter.idToFilename(id) match {
case null | "" =>
Failure("History log name can not be null nor empty")
case s if(s.contains(System.getProperty("file.separator"))) =>
Failure(s"UUID can not contains the char '${System.getProperty("file.separator")}'")
case s =>
val hlog = DefaultHLog(id,datetime,data)
for {
i <- idDir(hlog.id)
file <- tryo(new File(i,vToS(hlog.version)))
datas <- tryo(marshaller.toFile(file,hlog.data))
} yield hlog
}
}
/**
* Retrieve all ids known by the repository
*/
def getIds : Box[Seq[ID]] = {
for {
r <- root()
res <- tryo(r.listFiles.collect { case(f) if(f.isDirectory) => converter.filenameToId(f.getName) })
} yield {
res
}
}
/**
* Get the list of all history logs for the given id.
* If reading any logs throws an error, the full result is a
* Failure
*/
def getAll(id:ID) : Box[Seq[HLog]] = {
for {
versions <- this.versions(id)
hlogs <- {
( (Full(Seq()):Box[Seq[HLog]]) /: versions ) { (current, v) =>
for {
seq <- current
hlog <- this.get(id,v)
} yield seq :+ hlog
}
}
} yield hlogs
}
/**
* Get the list of record for the given UUID and version.
* If no version is specified, get the last.
*/
def getLast(id:ID) : Box[HLog] = {
for {
versions <- this.versions(id)
version <- versions.headOption
hlog <- this.get(id,version)
} yield hlog
}
/**
* Get the list of record for the given UUID and version.
* If no version is specified, get the last.
*/
def get(id:ID, version:DateTime) : Box[HLog] = {
for {
i <- idDir(id)
file <- tryo(new File(i,vToS(version)))
data <- marshaller.fromFile(file)
}yield DefaultHLog(id,version,data)
}
/**
* Return the list of version for ID.
* Full(Empty list) or Empty if no version for the given id
* List is sorted with last version (most recent) first
* ( versions.head > versions.head.head )
*/
def versions(id:ID) : Box[Seq[DateTime]] = {
for {
i <- idDir(id)
if(exists(id))
res <- tryo {
i.listFiles.toSeq.map(f => sToV(f.getName)).
filter(_.isDefined).map(_.get).sortWith(_ .compareTo(_) > 0)
}
} yield res
}
}
object FileHistoryLogRepository {
private val formatter = DateTimeFormat.forPattern("YYYY-MM-dd_HH:mm.ss.SSS")
private def vToS(version:DateTime) = formatter.print(version)
private def sToV(version:String):Option[DateTime] = {
try {
Some(formatter.parseDateTime(version))
} catch {
case e:IllegalArgumentException => None
}
}
}
inventory-repository/src/main/scala/com/normation/history/impl/HistoryLogImpl.scala
/*
*************************************************************************************
* Copyright 2011 Normation SAS
*************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*************************************************************************************
*/
package com.normation.history
package impl
import org.joda.time.DateTime
import com.normation.utils.HashcodeCaching
/**
* A version of history log which uses DateTime as
* version number.
*
*/
trait DatedHistoryLog[ID,T] extends HistoryLog[ID, DateTime, T] {
def datetime = version
}
case class DefaultHLog[ID,T](
id : ID,
version: DateTime,
data : T
) extends DatedHistoryLog[ID,T] with HashcodeCaching {
override val datetime = version
override val historyType = "default"
}
inventory-repository/src/main/scala/com/normation/inventory/ldap/core/InventoryDit.scala
import LDAPConstants._
import com.normation.utils.Utils.nonEmpty
import com.normation.inventory.domain._
import net.liftweb.common._
import com.normation.utils.HashcodeCaching
inventory-repository/src/test/scala/com/normation/history/impl/TestFileHistoryLogRepository.scala
/*
*************************************************************************************
* Copyright 2011 Normation SAS
*************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*************************************************************************************
*/
package com.normation.history.impl
import junit.framework.TestSuite
import org.junit._
import org.junit.Assert._
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner
import org.joda.time.DateTime
import org.apache.commons.io.FileUtils
import java.io.File
import net.liftweb.util.ControlHelpers.tryo
import net.liftweb.common._
object StringMarshaller extends FileMarshalling[String] {
//simply read / write file content
override def fromFile(in:File) : Box[String] = tryo(FileUtils.readFileToString(in,"UTF-8"))
override def toFile(out:File, data: String) : Box[String] = tryo {
FileUtils.writeStringToFile(out,data, "UTF-8")
data
}
}
object StringId extends IdToFilenameConverter[String] {
override def idToFilename(id:String) : String = id
override def filenameToId(name:String) : String = name
}
import TestFileHistoryLogRepository._
@RunWith(classOf[BlockJUnit4ClassRunner])
class TestFileHistoryLogRepository {
val repos = new FileHistoryLogRepository(rootDir, StringMarshaller,StringId)
@Test def basicTest {
val id1 = "data1"
assertEquals(Full(List()), repos.getIds.map(_.toList))
assertEquals( _:EmptyBox, repos.versions(id1))
val data1 = "Some data 1\nwith multiple lines"
//save first revision
val data1time1 = DateTime.now()
assertEquals(Full(DefaultHLog(id1, data1time1,data1)), repos.save(id1, data1, data1time1))
//now we have exaclty one id, with one revision, equals to data1time1
assertEquals(Full(List(id1)), repos.getIds.map(_.toList))
assertEquals(Full(List(data1time1)), repos.versions(id1).map(_.toList))
//save second revision
val data1time2 = DateTime.now()
assertEquals(Full(DefaultHLog(id1, data1time2, data1)), repos.save(id1, data1, data1time2))
//now we have exaclty one id1, with two revisions, and head is data1time2
assertEquals(Full(List(id1)), repos.getIds.map(_.toList))
assertEquals(Full(data1time2 :: data1time1 :: Nil), repos.versions(id1).map(_.toList))
}
}
object TestFileHistoryLogRepository {
val rootDir = System.getProperty("java.io.tmpdir") + "/testFileHistoryLogRepo"
def clean {
FileUtils.deleteDirectory(new File(rootDir))
}
@BeforeClass def before = clean
@AfterClass def after = clean
}

Also available in: Unified diff