From 9c36ff4f9e43857e2f73ebadad9942d85017e5c0 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Mon, 3 Sep 2018 23:40:35 -0700 Subject: Move platform back into init and use a configurable DNS discovery --- src/main/resources/reference.conf | 5 +++ src/main/scala/xyz/driver/core/Platform.scala | 35 ---------------- .../driver/core/discovery/CanDiscoverService.scala | 11 ----- .../scala/xyz/driver/core/init/CloudServices.scala | 47 ++++++++++++---------- src/main/scala/xyz/driver/core/init/Platform.scala | 31 ++++++++++++++ .../xyz/driver/core/messaging/ReportingBus.scala | 34 ++++++++++++++++ .../scala/xyz/driver/core/rest/DnsDiscovery.scala | 11 +++++ .../core/rest/HttpRestServiceTransport.scala | 4 +- .../xyz/driver/core/rest/ServiceDescriptor.scala | 16 ++++++++ 9 files changed, 124 insertions(+), 70 deletions(-) delete mode 100644 src/main/scala/xyz/driver/core/Platform.scala delete mode 100644 src/main/scala/xyz/driver/core/discovery/CanDiscoverService.scala create mode 100644 src/main/scala/xyz/driver/core/init/Platform.scala create mode 100644 src/main/scala/xyz/driver/core/messaging/ReportingBus.scala create mode 100644 src/main/scala/xyz/driver/core/rest/DnsDiscovery.scala create mode 100644 src/main/scala/xyz/driver/core/rest/ServiceDescriptor.scala diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index 608262a..01dba3c 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -19,6 +19,11 @@ application { ] } +services.dev-overrides = [ + // {"service1": "http://localhost:8080"}, + // {"service2": "https://stable.sand.driver.network"} +] + # Settings about the auto-generated REST API documentation. swagger { diff --git a/src/main/scala/xyz/driver/core/Platform.scala b/src/main/scala/xyz/driver/core/Platform.scala deleted file mode 100644 index aa7e711..0000000 --- a/src/main/scala/xyz/driver/core/Platform.scala +++ /dev/null @@ -1,35 +0,0 @@ -package xyz.driver.core -import java.nio.file.{Files, Path, Paths} - -import com.google.auth.oauth2.ServiceAccountCredentials - -sealed trait Platform { - def isKubernetes: Boolean -} - -object Platform { - case class GoogleCloud(keyfile: Path, namespace: String) extends Platform { - def credentials: ServiceAccountCredentials = ServiceAccountCredentials.fromStream( - Files.newInputStream(keyfile) - ) - def project: String = credentials.getProjectId - override def isKubernetes = true - } - // case object AliCloud extends Platform - case object Dev extends Platform { - override def isKubernetes: Boolean = false - } - - lazy val fromEnv: Platform = { - def isGoogle = sys.env.get("GOOGLE_APPLICATION_CREDENTIALS").map { value => - val keyfile = Paths.get(value) - require(Files.isReadable(keyfile), s"Google credentials file $value is not readable.") - val namespace = sys.env.getOrElse("SERVICE_NAMESPACE", sys.error("Namespace not set")) - GoogleCloud(keyfile, namespace) - } - isGoogle.getOrElse(Dev) - } - - def current: Platform = fromEnv - -} diff --git a/src/main/scala/xyz/driver/core/discovery/CanDiscoverService.scala b/src/main/scala/xyz/driver/core/discovery/CanDiscoverService.scala deleted file mode 100644 index 8711332..0000000 --- a/src/main/scala/xyz/driver/core/discovery/CanDiscoverService.scala +++ /dev/null @@ -1,11 +0,0 @@ -package xyz.driver.core -package discovery - -import scala.annotation.implicitNotFound - -@implicitNotFound( - "Don't know how to communicate with service ${Service}. Make sure an implicit CanDiscoverService is" + - "available. A good place to put one is in the service's companion object.") -trait CanDiscoverService[Service] { - def discover(platform: Platform): Service -} diff --git a/src/main/scala/xyz/driver/core/init/CloudServices.scala b/src/main/scala/xyz/driver/core/init/CloudServices.scala index 9f4ab5c..c492add 100644 --- a/src/main/scala/xyz/driver/core/init/CloudServices.scala +++ b/src/main/scala/xyz/driver/core/init/CloudServices.scala @@ -3,12 +3,14 @@ package init import java.nio.file.Paths -import xyz.driver.core.discovery.CanDiscoverService +import com.typesafe.config.ConfigValueType import xyz.driver.core.messaging.{GoogleBus, QueueBus, StreamBus} import xyz.driver.core.reporting._ import xyz.driver.core.reporting.ScalaLoggerLike.defaultScalaLogger +import xyz.driver.core.rest.{DnsDiscovery, ServiceDescriptor} import xyz.driver.core.storage.{BlobStorage, FileSystemBlobStorage, GcsBlobStorage} +import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext /** Mixin trait that provides essential cloud utilities. */ @@ -21,28 +23,29 @@ trait CloudServices extends AkkaBootable { self => /** Service discovery for the current platform. * - * Define a service trait and companion object: - * {{{ - * trait MyService { - * def call(): Int - * } - * object MyService { - * implicit val isDiscoverable = new xyz.driver.core.discovery.CanDiscoverService[MyService] { - * def discover(p: xyz.driver.core.Platform): MyService = new MyService { - * def call() = 42 - * } - * } - * } - * }}} - * - * Then discover and use it: - * {{{ - * discover[MyService].call() - * }}} - * - * @group utilities */ - def discover[A](implicit cds: CanDiscoverService[A]): A = cds.discover(platform) + private lazy val discovery = { + def getOverrides(): Map[String, String] = + (for { + obj <- config.getObjectList("services.dev-overrides").asScala + entry <- obj.entrySet().asScala + } yield { + val tpe = entry.getValue.valueType() + require( + tpe == ConfigValueType.STRING, + s"URL override for '${entry.getKey}' must be a " + + s"string. Found '${entry.getValue.unwrapped}', which is of type $tpe.") + entry.getKey -> entry.getValue.unwrapped.toString + }).toMap + + val overrides = platform match { + case Platform.Dev => getOverrides() + case _ => Map.empty[String, String] // TODO we may want to provide a way to override deployed services as well + } + new DnsDiscovery(clientTransport, overrides) + } + + def discover[A: ServiceDescriptor]: A = discovery.discover[A] /* TODO: this reporter uses the platform to determine if JSON logging should be enabled. * Since the default logger uses slf4j, its settings must be specified before a logger diff --git a/src/main/scala/xyz/driver/core/init/Platform.scala b/src/main/scala/xyz/driver/core/init/Platform.scala new file mode 100644 index 0000000..2daa2c8 --- /dev/null +++ b/src/main/scala/xyz/driver/core/init/Platform.scala @@ -0,0 +1,31 @@ +package xyz.driver.core +package init + +import java.nio.file.{Files, Path, Paths} + +import com.google.auth.oauth2.ServiceAccountCredentials + +sealed trait Platform +object Platform { + case class GoogleCloud(keyfile: Path, namespace: String) extends Platform { + def credentials: ServiceAccountCredentials = ServiceAccountCredentials.fromStream( + Files.newInputStream(keyfile) + ) + def project: String = credentials.getProjectId + } + // case object AliCloud extends Platform + case object Dev extends Platform + + lazy val fromEnv: Platform = { + def isGoogle = sys.env.get("GOOGLE_APPLICATION_CREDENTIALS").map { value => + val keyfile = Paths.get(value) + require(Files.isReadable(keyfile), s"Google credentials file $value is not readable.") + val namespace = sys.env.getOrElse("SERVICE_NAMESPACE", sys.error("Namespace not set")) + GoogleCloud(keyfile, namespace) + } + isGoogle.getOrElse(Dev) + } + + def current: Platform = fromEnv + +} diff --git a/src/main/scala/xyz/driver/core/messaging/ReportingBus.scala b/src/main/scala/xyz/driver/core/messaging/ReportingBus.scala new file mode 100644 index 0000000..74038e4 --- /dev/null +++ b/src/main/scala/xyz/driver/core/messaging/ReportingBus.scala @@ -0,0 +1,34 @@ +package xyz.driver.core.messaging + +import xyz.driver.core.reporting.{Reporter, SpanContext} + +import scala.concurrent.Future +import scala.language.higherKinds + +trait ReportingBus extends Bus { + + def reporter: Reporter + + trait TracedMessage[A] extends BasicMessage[A] { self: Message[A] => + def spanContext: SpanContext + } + + type Message[A] <: TracedMessage[A] + + abstract override def publishMessages[A](topic: Topic[A], messages: Seq[A]): Future[Unit] = { + super.publishMessages(topic, messages) + } + + abstract override def fetchMessages[A]( + topic: Topic[A], + config: SubscriptionConfig, + maxMessages: Int): Future[Seq[Message[A]]] = { + super.fetchMessages(topic, config, maxMessages) + } + +} + +trait Topic2 +trait Bus2 { + def publishMessage[A](topic: Topic2, message: A) +} diff --git a/src/main/scala/xyz/driver/core/rest/DnsDiscovery.scala b/src/main/scala/xyz/driver/core/rest/DnsDiscovery.scala new file mode 100644 index 0000000..38880d4 --- /dev/null +++ b/src/main/scala/xyz/driver/core/rest/DnsDiscovery.scala @@ -0,0 +1,11 @@ +package xyz.driver.core +package rest + +class DnsDiscovery(transport: HttpRestServiceTransport, overrides: Map[String, String]) { + + def discover[A](implicit descriptor: ServiceDescriptor[A]): A = { + val url = overrides.getOrElse(descriptor.name, s"https://{descriptor.name}") + descriptor.connect(transport, url) + } + +} diff --git a/src/main/scala/xyz/driver/core/rest/HttpRestServiceTransport.scala b/src/main/scala/xyz/driver/core/rest/HttpRestServiceTransport.scala index e60998f..e31635b 100644 --- a/src/main/scala/xyz/driver/core/rest/HttpRestServiceTransport.scala +++ b/src/main/scala/xyz/driver/core/rest/HttpRestServiceTransport.scala @@ -17,8 +17,8 @@ import scala.util.{Failure, Success} class HttpRestServiceTransport( applicationName: Name[App], applicationVersion: String, - actorSystem: ActorSystem, - executionContext: ExecutionContext, + val actorSystem: ActorSystem, + val executionContext: ExecutionContext, reporter: Reporter) extends ServiceTransport { diff --git a/src/main/scala/xyz/driver/core/rest/ServiceDescriptor.scala b/src/main/scala/xyz/driver/core/rest/ServiceDescriptor.scala new file mode 100644 index 0000000..646fae8 --- /dev/null +++ b/src/main/scala/xyz/driver/core/rest/ServiceDescriptor.scala @@ -0,0 +1,16 @@ +package xyz.driver.core +package rest +import scala.annotation.implicitNotFound + +@implicitNotFound( + "Don't know how to communicate with service ${S}. Make sure an implicit ServiceDescriptor is" + + "available. A good place to put one is in the service's companion object.") +trait ServiceDescriptor[S] { + + /** The service's name. Must be unique among all services. */ + def name: String + + /** Get an instance of the service. */ + def connect(transport: HttpRestServiceTransport, url: String): S + +} -- cgit v1.2.3