Skip to content
This repository has been archived by the owner on Aug 1, 2023. It is now read-only.

Commit

Permalink
Merge pull request tumblr#77 from cburroughs/server-lshw
Browse files Browse the repository at this point in the history
extract server product/vendor information with lshw
  • Loading branch information
bmatheny committed Apr 9, 2013
2 parents 570085e + b67a42e commit 8dcef53
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 12 deletions.
13 changes: 13 additions & 0 deletions app/models/AssetMeta.scala
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,17 @@ object AssetMeta extends Schema with AnormAdapter[AssetMeta] {
// DO NOT USE - Deprecated
val NicAddress = Value(33, "INTERFACE_ADDRESS")
}

// Post enum fields, enum is not safe to extend with new values
object DynamicEnum {
val BaseDescription = AssetMeta.findOrCreateFromName("BASE_DESCRIPTION")
val BaseProduct = AssetMeta.findOrCreateFromName("BASE_PRODUCT")
val BaseVendor = AssetMeta.findOrCreateFromName("BASE_VENDOR")

def getValues(): Seq[AssetMeta] = {
Seq(BaseDescription,BaseProduct,BaseVendor)
}


}
}
35 changes: 33 additions & 2 deletions app/models/LshwHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import lshw._

object LshwHelper extends CommonHelper[LshwRepresentation] {
import AssetMeta.Enum._
import AssetMeta.DynamicEnum._

// TODO: Is this set actually used anywhere?
val managedTags = Set(
CpuCount,
CpuCores,
Expand All @@ -29,7 +31,8 @@ object LshwHelper extends CommonHelper[LshwRepresentation] {
collectCpus(asset, lshw) ++
collectMemory(asset, lshw) ++
collectNics(asset, lshw) ++
collectDisks(asset, lshw)
collectDisks(asset, lshw) ++
collectBase(asset, lshw)
}

def reconstruct(asset: Asset, assetMeta: Seq[MetaWrapper]): Reconstruction = {
Expand All @@ -38,7 +41,8 @@ object LshwHelper extends CommonHelper[LshwRepresentation] {
val (memory,postMemoryMap) = reconstructMemory(postCpuMap)
val (nics,postNicMap) = reconstructNics(postMemoryMap)
val (disks,postDiskMap) = reconstructDisks(postNicMap)
(LshwRepresentation(cpus, memory, nics, disks), postDiskMap.values.flatten.toSeq)
val (base,postBaseMap) = reconstructBase(postDiskMap)
(LshwRepresentation(cpus, memory, nics, disks, base.headOption.getOrElse(ServerBase())), postBaseMap.values.flatten.toSeq)
}

protected def reconstructCpu(meta: Map[Int, Seq[MetaWrapper]]): FilteredSeq[Cpu] = {
Expand Down Expand Up @@ -197,4 +201,31 @@ object LshwHelper extends CommonHelper[LshwRepresentation] {
Seq(diskSummary) ++ physicalDisks
}

protected def reconstructBase(meta: Map[Int, Seq[MetaWrapper]]): FilteredSeq[ServerBase] = {
val baseSeq = meta.get(0).map { seq =>
val baseDescription = amfinder(seq, BaseDescription, _.toString, "")
val baseProduct = amfinder(seq, BaseProduct, _.toString, "")
val baseVendor = amfinder(seq, BaseVendor, _.toString, "")
Seq(ServerBase(baseDescription, baseProduct, baseVendor))
}.getOrElse(Nil)

val filteredMeta = meta.map { case(groupId, metaSeq) =>
val newSeq = filterNot(
metaSeq,
Set(BaseDescription.id, BaseProduct.id, BaseVendor.id)
)
groupId -> newSeq
}
(baseSeq, filteredMeta)
}

protected def collectBase(asset: Asset, lshw: LshwRepresentation): Seq[AssetMetaValue] = {
val base = lshw.base
Seq(
AssetMetaValue(asset, BaseDescription.id, base.description),
AssetMetaValue(asset, BaseProduct.id, base.product),
AssetMetaValue(asset, BaseVendor.id, base.vendor)
)
}

}
28 changes: 28 additions & 0 deletions app/models/lshw/ServerBase.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package models.lshw

import play.api.libs.json._

object ServerBase {
import Json._
implicit object ServerbaseFormat extends Format[ServerBase] {
override def reads(json: JsValue) = ServerBase(
(json \ "DESCRIPTION").as[String],
(json \ "PRODUCT").as[String],
(json \ "VENDOR").as[String]
)
override def writes(serverbase: ServerBase) = JsObject(Seq(
"DESCRIPTION" -> toJson(serverbase.description),
"PRODUCT" -> toJson(serverbase.product),
"VENDOR" -> toJson(serverbase.vendor)
))
}
}

case class ServerBase(
description: String = "", product: String = "", vendor: String = ""
) extends LshwAsset {
import ServerBase._
override def toJsValue() = Json.toJson(this)

def isEmpty(): Boolean = description.isEmpty && product.isEmpty && vendor.isEmpty
}
3 changes: 3 additions & 0 deletions app/models/shared/CommonHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ trait CommonHelper[T] {
protected def finder[T](m: Seq[MetaWrapper], e: AssetMeta.Enum, c: (String => T), d: T): T = {
m.find { _.getMetaId == e.id }.map { i => c(i.getValue) }.getOrElse(d)
}
protected def amfinder[T](m: Seq[MetaWrapper], e: AssetMeta, c: (String => T), d: T): T = {
m.find { _.getMetaId == e.getId }.map { i => c(i.getValue) }.getOrElse(d)
}
protected def seqFinder[T](m: Seq[MetaWrapper], e: AssetMeta.Enum, c: (String => T)): Seq[T] = {
m.filter(_.getMetaId == e.id).map(i => c(i.getValue))
}
Expand Down
13 changes: 9 additions & 4 deletions app/util/LshwRepresentation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,28 @@ import play.api.libs.json._

object LshwRepresentation {
def empty(): LshwRepresentation = {
new LshwRepresentation(Seq(), Seq(), Seq(), Seq())
new LshwRepresentation(Seq(), Seq(), Seq(), Seq(), new ServerBase)
}
implicit object LshwFormat extends Format[LshwRepresentation] {
import Cpu._
import Disk._
import Memory._
import Nic._
import ServerBase._
import Json.toJson
override def reads(json: JsValue) = LshwRepresentation(
(json \ "CPU").as[Seq[Cpu]],
(json \ "MEMORY").as[Seq[Memory]],
(json \ "NIC").as[Seq[Nic]],
(json \ "DISK").as[Seq[Disk]]
(json \ "DISK").as[Seq[Disk]],
(json \ "BASE").as[ServerBase]
)
override def writes(lshw: LshwRepresentation) = JsObject(Seq(
"CPU" -> toJson(lshw.cpus),
"MEMORY" -> toJson(lshw.memory),
"NIC" -> toJson(lshw.nics),
"DISK" -> toJson(lshw.disks)
"DISK" -> toJson(lshw.disks),
"BASE" -> toJson(lshw.base)
))
}
}
Expand All @@ -32,7 +35,9 @@ case class LshwRepresentation(
cpus: Seq[Cpu],
memory: Seq[Memory],
nics: Seq[Nic],
disks: Seq[Disk]
disks: Seq[Disk],
base: ServerBase

) {
def cpuCount: Int = cpus.size
def hasHyperthreadingEnabled: Boolean = (cpuThreadCount > cpuCoreCount)
Expand Down
22 changes: 21 additions & 1 deletion app/util/parsers/LshwParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ class LshwParser(txt: String) extends CommonParser[LshwRepresentation](txt) {
return Left(e)
}
val rep = try {
getCoreNodes(xml).foldLeft(LshwRepresentation(Nil,Nil,Nil,Nil)) { case (holder,node) =>
val base = getBaseInfo(xml)
getCoreNodes(xml).foldLeft(LshwRepresentation(Nil,Nil,Nil,Nil,base)) { case (holder,node) =>
matcher(node) match {
case c: Cpu => holder.copy(cpus = c +: holder.cpus)
case m: Memory => holder.copy(memory = m.copy(bank = holder.memory.size) +: holder.memory)
Expand Down Expand Up @@ -153,6 +154,25 @@ class LshwParser(txt: String) extends CommonParser[LshwRepresentation](txt) {
core \\ "node"
}

protected def getBaseInfo(elem: Elem): ServerBase = {
// Note that this does not uniquely identify the root of the lshw
// xml, but is only a sanity check that this is being called on
// the correct element
if ((elem \ "@class" text).toString == "system") {
val asset = getAsset(elem)
ServerBase(asset.description, asset.product, asset.vendor)
}
// To spice things up, sometimes we get <list>$everything</list>
// instead of just $everything
else if (((elem \ "node") \ "@class" text) == "system") {
val asset = getAsset(elem \ "node")
ServerBase(asset.description, asset.product, asset.vendor)
}
else {
throw MalformedAttributeException("Expected root class=system node attribute")
}
}

protected def settingsMap(n: NodeSeq): Map[String,String] = {
n.foldLeft(Map[String,String]()) { case(r,setting) =>
r ++ Map((setting \ "@id" text) -> (setting \ "@value" text))
Expand Down
25 changes: 25 additions & 0 deletions app/views/asset/show_hwdetails.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@
<!-- Start Hardware Details -->
<div class="tab-pane" id="hardware-details">
@if(aa.lshw.cpuCount > 0) {
@if(!aa.lshw.base.isEmpty) {
<h4>Server Base <small>Collected Information About the server itself</small></h4>
<table class="table table-bordered table-hover table-condensed">
<thead>
<tr>
<th>Field</th><th>Value</th>
</tr>
</thead>
<tbody>
<!-- Description is not displayed because it is rarely interesting (usually something like "system") -->
<!--
<tr>
<td>Description</td><td>@aa.lshw.base.description</td>
</tr>
-->
<tr>
<td>Product</td><td>@aa.lshw.base.product</td>
</tr>
<tr>
<td>Vendor</td><td>@aa.lshw.base.vendor</td>
</tr>
</tbody>
</table>
}

<h4>Network Interfaces <small>Collected NIC Information</small></h4>
<table class="table table-bordered table-hover table-condensed">
<thead>
Expand Down
15 changes: 15 additions & 0 deletions app/views/asset/show_overview.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,22 @@ <h3>Hardware Summary</h3>
</tr>
</thead>
<tbody>
@if(!aa.lshw.base.isEmpty) {
<tr>
<th colspan="3">Server Base</th>
</tr>
<tr>
<td></td>
<td>Product</td>
<td>@aa.lshw.base.product</td>
</tr>
<tr>
<td></td>
<td>Vendor</td>
<td>@aa.lshw.base.vendor</td>
</tr>
<tr>
}
<th colspan="3">CPU</th>
</tr>
<tr>
Expand Down
1 change: 1 addition & 0 deletions app/views/resources/index.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ <h1>Asset Search</h1>
case "POOL" => optionDisplay(asset, 0, idx)
case "PRIMARY_ROLE" => optionDisplay(asset, 0, idx)
case "SECONDARY_ROLE" => optionDisplay(asset, 0, idx)
case "BASE_VENDOR" => optionDisplay(asset, 0, idx)
case _ => basicFormatter(asset, idx)
}
}
Expand Down
7 changes: 7 additions & 0 deletions conf/evolutions/collins/11.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# --- Include new common server base fields

# --- !Ups

INSERT INTO asset_meta VALUES (0, 'BASE_PRODUCT', 1, 'Base Product', 'Formal product model designation', 1);
INSERT INTO asset_meta VALUES (0, 'BASE_VENDOR', 1, 'Base Vendor', 'Who made your spiffy computer?', 1);
INSERT INTO asset_meta VALUES (0, 'BASE_DESCRIPTION', -1, 'Base Description', 'How does your computer introduce itself?', 1);
6 changes: 2 additions & 4 deletions test/models/CommonHelperSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,10 @@ trait CommonHelperSpec[REP] extends test.ResourceFinder {

def metaValue2metaWrapper(mvs: Seq[AssetMetaValue]): Seq[MetaWrapper] = {
val enums = AssetMeta.Enum.values.toSeq
val dvals = AssetMeta.DynamicEnum.getValues
mvs.map { mv =>
val mid = mv.asset_meta_id
val meta = enums.find { e => e.id == mid }.map { e =>
AssetMeta(e.toString, -1, e.toString, e.toString, mv.asset_meta_id)
}.getOrElse(throw new Exception("Found unhandled AssetMeta"))
MetaWrapper(meta, mv)
MetaWrapper(AssetMeta.findById(mv.asset_meta_id).get, mv)
}
}
}
18 changes: 17 additions & 1 deletion test/util/parsers/LshwParserSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ class LshwParserSpec extends mutable.Specification {
rep.has10GbNic must beTrue
rep.macAddresses must have length 3
rep.macAddresses must beNonEmptyStringSeq

rep.base.product mustEqual "PowerEdge C6105 (N/A)"
rep.base.vendor mustEqual "Winbond Electronics"
}
} // with a 10-gig card

Expand Down Expand Up @@ -159,6 +162,9 @@ class LshwParserSpec extends mutable.Specification {
rep.has10GbNic must beFalse
rep.macAddresses must have length 4
rep.macAddresses must beNonEmptyStringSeq

rep.base.product mustEqual "X8DTN"
rep.base.vendor mustEqual "Supermicro"
}
} // B.02.12 format

Expand Down Expand Up @@ -237,6 +243,9 @@ class LshwParserSpec extends mutable.Specification {
rep.has10GbNic must beFalse
rep.macAddresses must have length 2
rep.macAddresses must beNonEmptyStringSeq

rep.base.product mustEqual "PowerEdge C6105 (N/A)"
rep.base.vendor mustEqual "Dell Inc."
}
}
}
Expand Down Expand Up @@ -351,7 +360,10 @@ class LshwParserSpec extends mutable.Specification {
rep.hasGbNic must beTrue
rep.has10GbNic must beFalse
rep.macAddresses must have length 4
rep.macAddresses must beNonEmptyStringSeq
rep.macAddresses must beNonEmptyStringSeq

rep.base.product mustEqual "PowerEdge R620 ()"
rep.base.vendor mustEqual "Winbond Electronics"
}
}
}
Expand All @@ -376,6 +388,10 @@ class LshwParserSpec extends mutable.Specification {
rep.has10GbNic must beFalse
rep.macAddresses must have length 2
rep.macAddresses must beNonEmptyStringSeq

rep.base.description mustEqual "Multi-system"
rep.base.product mustEqual "ProLiant SL335s G7"
rep.base.vendor mustEqual "HP"
}
}

Expand Down

0 comments on commit 8dcef53

Please sign in to comment.