-
-
Save arosien/6720878 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks Adam!