Skip to content

Instantly share code, notes, and snippets.

@arosien
Forked from helena/Topology.scala
Last active December 24, 2015 00:59
Show Gist options
  • Save arosien/6720878 to your computer and use it in GitHub Desktop.
Save arosien/6720878 to your computer and use it in GitHub Desktop.
scalaVersion := "2.10.2"
libraryDependencies ++=
Seq(
"com.typesafe" % "config" % "1.0.2",
"com.typesafe.akka" %% "akka-actor" % "2.2.1",
"org.scalaz" %% "scalaz-core" % "7.0.3")
import java.lang.System.{ currentTimeMillis ⇒ newTimestamp }
import scala.util.Try
import scala.collection.immutable
import scala.collection.JavaConverters._
import akka.actor._
import akka.japi.Util.immutableSeq
import com.typesafe.config._
import scalaz._
import Scalaz._
object stuff {
case class Topology(regions: Set[Region])
// Using NonEmptySet may be excessive, but the in previous impl it explicitly avoided empty sets of dc instances. */
case class Region(name: String, dataCenters: NonEmptySet[DataCenter])
// Using NonEmptySet may be excessive, but the in previous impl it explicitly avoided empty sets of host instances. */
case class DataCenter(id: Id[DataCenter], name: String, nearest: Seq[Nearest], instances: NonEmptySet[String])
// Is it correct that Nearest points to other DataCenters in the Region?
case class Nearest(dataCenter: Id[DataCenter], index: Int)
/** Simple type-safe identifier with Int values. */
case class Id[+A](value: Int)
type NonEmptySet[A] = OneAnd[Set, A]
object NonEmptySet {
def apply[A](head: A, tail: Set[A]): NonEmptySet[A] = OneAnd(head, tail)
}
object Topology {
/** Parses a Topology from a config of the form:
* {{{
* region1: {
* dataCenter1: {
* zone-id: 1
* proximal-to: [2, 3, 4] // list of nearby zone-ids(?)
* instances: [dc1h1, dc1h2]
* }
*
* dataCenter2: {
* zone-id: 2
* proximal-to: [4, 1]
* instances: [dc2h1, dc2h2]
* }
*
* ...
* }
*
* region2: {
* ...
* }
*
* ...
* }}}
*
* TODO: Rather than having dc names as keys, I'd probably push the dc name into a field and make an array named "data-centers".
*/
def apply(config: Config): Topology = Topology(mapElements(config, parseRegion).toSet)
/** Partitions proximal DataCenters per region. */
private def parseRegion(name: String, config: Config): Option[Region] = {
val dataCenters = mapElements(config, parseDataCenter).toSet
Try {
Region(name, NonEmptySet(dataCenters.head, dataCenters.tail)) // goes BOOM if head non-existent
}.toOption
}
private def parseDataCenter(name: String, config: Config): Option[DataCenter] =
Try {
val id = config.getInt("zone-id") // region == zone?
val proximals = immutableSeq(config.getIntList("proximal-to")).toIndexedSeq
val nearest = proximals.zipWithIndex map { case (n, i) ⇒ Nearest(Id(n), i) }
val instances = immutableSeq(config.getStringList("instances")).map { case host if host.nonEmpty ⇒ host }.toSet
DataCenter(Id(id), name, nearest, NonEmptySet(instances.head, instances.tail)) // goes BOOM if head non-existent
}.toOption
/** Map each (key, value) element of the config, collecting all Some values.
*
* TODO: Change f to return scalaz's Validation vs. Option, then sequence to accumulate parse errors.
*/
private def mapElements[A](config: Config, f: (String, Config) => Option[A]): Iterable[A] =
config.root.asScala.flatMap {
case (name, value: ConfigObject) => f(name, value.toConfig)
}
}
}
@helena
Copy link

helena commented Sep 27, 2013

Thanks Adam!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment