├── project ├── build.properties ├── plugins.sbt └── Dependencies.scala ├── version.sbt ├── NOTICE ├── .travis.yml ├── service-locator-dns ├── build.sbt └── src │ ├── main │ ├── scala │ │ └── com │ │ │ └── lightbend │ │ │ └── dns │ │ │ └── locator │ │ │ ├── Settings.scala │ │ │ └── ServiceLocator.scala │ └── resources │ │ └── reference.conf │ └── test │ └── scala │ └── com │ └── lightbend │ └── dns │ └── locator │ └── ServiceLocatorSpec.scala ├── lagom13-java-service-locator-dns ├── build.sbt └── src │ ├── main │ ├── resources │ │ └── reference.conf │ └── scala │ │ └── com │ │ └── lightbend │ │ └── lagom │ │ └── javadsl │ │ └── dns │ │ ├── ServiceLocatorModule.scala │ │ └── DnsServiceLocator.scala │ └── test │ └── scala │ └── com │ └── lightbend │ └── lagom │ └── javadsl │ └── dns │ └── DnsServiceLocatorSpec.scala ├── lagom14-java-service-locator-dns ├── build.sbt └── src │ ├── main │ ├── resources │ │ └── reference.conf │ └── scala │ │ └── com │ │ └── lightbend │ │ └── lagom │ │ └── javadsl │ │ └── dns │ │ ├── ServiceLocatorModule.scala │ │ └── DnsServiceLocator.scala │ └── test │ └── scala │ └── com │ └── lightbend │ └── lagom │ └── javadsl │ └── dns │ └── DnsServiceLocatorSpec.scala ├── lagom13-scala-service-locator-dns ├── build.sbt └── src │ ├── main │ └── scala │ │ └── com │ │ └── lightbend │ │ └── lagom │ │ └── scaladsl │ │ └── dns │ │ ├── DnsServiceLocatorComponents.scala │ │ └── DnsServiceLocator.scala │ └── test │ └── scala │ └── com │ └── lightbend │ └── lagom │ └── scaladsl │ └── dns │ └── DnsServiceLocatorSpec.scala ├── lagom14-scala-service-locator-dns ├── build.sbt └── src │ ├── main │ └── scala │ │ └── com │ │ └── lightbend │ │ └── lagom │ │ └── scaladsl │ │ └── dns │ │ ├── DnsServiceLocatorComponents.scala │ │ └── DnsServiceLocator.scala │ └── test │ └── scala │ └── com │ └── lightbend │ └── lagom │ └── scaladsl │ └── dns │ └── DnsServiceLocatorSpec.scala ├── .gitignore ├── LICENSE ├── README.md └── CONTRIBUTING.md /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.0.2 2 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "2.3.1-SNAPSHOT" 2 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013-2014 Typesafe Inc. -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | jdk: 3 | - oraclejdk8 4 | script: sbt +test 5 | -------------------------------------------------------------------------------- /service-locator-dns/build.sbt: -------------------------------------------------------------------------------- 1 | name := "service-locator-dns" 2 | 3 | resolvers += Resolver.hajile 4 | 5 | libraryDependencies ++= Seq( 6 | Library.akkaDns, 7 | Library.akkaTestkit % "test", 8 | Library.scalaTest % "test" 9 | ) -------------------------------------------------------------------------------- /lagom13-java-service-locator-dns/build.sbt: -------------------------------------------------------------------------------- 1 | name := "lagom13-java-service-locator-dns" 2 | 3 | libraryDependencies ++= Seq( 4 | Library.akkaDns, 5 | Library.lagom13JavaClient, 6 | Library.akkaTestkit % "test", 7 | Library.scalaTest % "test" 8 | ) 9 | 10 | resolvers += Resolver.hajile 11 | -------------------------------------------------------------------------------- /lagom14-java-service-locator-dns/build.sbt: -------------------------------------------------------------------------------- 1 | name := "lagom14-java-service-locator-dns" 2 | 3 | libraryDependencies ++= Seq( 4 | Library.akkaDns, 5 | Library.lagom14JavaClient, 6 | Library.akkaTestkit % "test", 7 | Library.scalaTest % "test" 8 | ) 9 | 10 | resolvers += Resolver.hajile 11 | -------------------------------------------------------------------------------- /lagom13-scala-service-locator-dns/build.sbt: -------------------------------------------------------------------------------- 1 | name := "lagom13-scala-service-locator-dns" 2 | 3 | libraryDependencies ++= Seq( 4 | Library.lagom13ScalaClient, 5 | Library.akkaTestkit % "test", 6 | Library.lagom13ScalaServer % "test", 7 | Library.scalaTest % "test" 8 | ) 9 | 10 | resolvers += Resolver.hajile 11 | -------------------------------------------------------------------------------- /lagom14-scala-service-locator-dns/build.sbt: -------------------------------------------------------------------------------- 1 | name := "lagom14-scala-service-locator-dns" 2 | 3 | libraryDependencies ++= Seq( 4 | Library.lagom14ScalaClient, 5 | Library.akkaTestkit % "test", 6 | Library.lagom14ScalaServer % "test", 7 | Library.scalaTest % "test" 8 | ) 9 | 10 | resolvers += Resolver.hajile 11 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn 2 | 3 | resolvers += Resolver.typesafeRepo("releases") 4 | 5 | addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.0") 6 | addSbtPlugin("de.heikoseeberger" % "sbt-header" % "3.0.2") 7 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0") 8 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.6") 9 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.0") -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # sbt 2 | lib_managed 3 | project/project 4 | target 5 | 6 | # Worksheets (Eclipse or IntelliJ) 7 | *.sc 8 | 9 | # Eclipse 10 | .cache* 11 | .classpath 12 | .project 13 | .scala_dependencies 14 | .settings 15 | .target 16 | .worksheet 17 | 18 | # IntelliJ 19 | .idea 20 | 21 | # ENSIME 22 | .ensime 23 | .ensime_lucene 24 | 25 | # Mac 26 | .DS_Store 27 | 28 | # Akka Persistence 29 | journal 30 | snapshots 31 | 32 | # Log files 33 | *.log 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is licensed under the Apache 2 license, quoted below. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with 4 | the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. 5 | 6 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 7 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific 8 | language governing permissions and limitations under the License. -------------------------------------------------------------------------------- /lagom13-java-service-locator-dns/src/main/resources/reference.conf: -------------------------------------------------------------------------------- 1 | # Copyright © 2016 Lightbend, Inc. All rights reserved. 2 | # No information contained herein may be reproduced or transmitted in any form 3 | # or by any means without the express written permission of Typesafe, Inc. 4 | 5 | # This is the reference config file that contains all the default settings. 6 | # Make your edits/overrides in your application.conf. 7 | 8 | 9 | # Enables the `ServiceLocatorModule` to register the `DnsServiceLocator`. 10 | # The `DnsServiceLocator` implements Lagom's ServiceLocator 11 | # by using DNS-SRV lookups. 12 | play.modules.enabled += "com.lightbend.lagom.javadsl.dns.ServiceLocatorModule" 13 | -------------------------------------------------------------------------------- /lagom14-java-service-locator-dns/src/main/resources/reference.conf: -------------------------------------------------------------------------------- 1 | # Copyright © 2016 Lightbend, Inc. All rights reserved. 2 | # No information contained herein may be reproduced or transmitted in any form 3 | # or by any means without the express written permission of Typesafe, Inc. 4 | 5 | # This is the reference config file that contains all the default settings. 6 | # Make your edits/overrides in your application.conf. 7 | 8 | 9 | # Enables the `ServiceLocatorModule` to register the `DnsServiceLocator`. 10 | # The `DnsServiceLocator` implements Lagom's ServiceLocator 11 | # by using DNS-SRV lookups. 12 | play.modules.enabled += "com.lightbend.lagom.javadsl.dns.ServiceLocatorModule" 13 | -------------------------------------------------------------------------------- /project/Dependencies.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import sbt.Keys._ 3 | import xerial.sbt.Sonatype.SonatypeKeys._ 4 | 5 | object Version { 6 | // OSS 7 | val akka = "2.4.20" 8 | val akkaDns = "2.4.2" 9 | val lagom13 = "1.3.10" 10 | val lagom14 = "1.4.0" 11 | val scala212 = "2.12.3" 12 | val scala211 = "2.11.11" 13 | val scalaTest = "3.0.1" 14 | } 15 | 16 | object Library { 17 | // OSS 18 | val akkaDns = "ru.smslv.akka" %% "akka-dns" % Version.akkaDns 19 | val akkaTestkit = "com.typesafe.akka" %% "akka-testkit" % Version.akka 20 | val lagom13JavaClient = "com.lightbend.lagom" %% "lagom-javadsl-client" % Version.lagom13 21 | val lagom14JavaClient = "com.lightbend.lagom" %% "lagom-javadsl-client" % Version.lagom14 22 | val lagom13ScalaClient = "com.lightbend.lagom" %% "lagom-scaladsl-client" % Version.lagom13 23 | val lagom13ScalaServer = "com.lightbend.lagom" %% "lagom-scaladsl-server" % Version.lagom13 24 | val lagom14ScalaClient = "com.lightbend.lagom" %% "lagom-scaladsl-client" % Version.lagom14 25 | val lagom14ScalaServer = "com.lightbend.lagom" %% "lagom-scaladsl-server" % Version.lagom14 26 | val scalaTest = "org.scalatest" %% "scalatest" % Version.scalaTest 27 | } 28 | 29 | object Resolver { 30 | val hajile = sbt.Resolver.bintrayRepo("hajile", "maven") 31 | } 32 | -------------------------------------------------------------------------------- /lagom13-scala-service-locator-dns/src/main/scala/com/lightbend/lagom/scaladsl/dns/DnsServiceLocatorComponents.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Lightbend, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.lightbend.lagom.scaladsl.dns 18 | 19 | import akka.actor.ActorRef 20 | import com.lightbend.dns.locator.{ ServiceLocator => ServiceLocatorService } 21 | import com.lightbend.lagom.scaladsl.client.CircuitBreakerComponents 22 | 23 | /** 24 | * Provides the DNS service locator. 25 | */ 26 | trait DnsServiceLocatorComponents extends CircuitBreakerComponents { 27 | def serviceLocatorService: ActorRef = 28 | actorSystem.actorOf(ServiceLocatorService.props, ServiceLocatorService.Name) 29 | 30 | lazy val serviceLocator: DnsServiceLocator = 31 | new DnsServiceLocator(serviceLocatorService, actorSystem, circuitBreakers, executionContext) 32 | } 33 | -------------------------------------------------------------------------------- /lagom14-scala-service-locator-dns/src/main/scala/com/lightbend/lagom/scaladsl/dns/DnsServiceLocatorComponents.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Lightbend, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.lightbend.lagom.scaladsl.dns 18 | 19 | import akka.actor.ActorRef 20 | import com.lightbend.dns.locator.{ ServiceLocator => ServiceLocatorService } 21 | import com.lightbend.lagom.scaladsl.client.CircuitBreakerComponents 22 | 23 | /** 24 | * Provides the DNS service locator. 25 | */ 26 | trait DnsServiceLocatorComponents extends CircuitBreakerComponents { 27 | def serviceLocatorService: ActorRef = 28 | actorSystem.actorOf(ServiceLocatorService.props, ServiceLocatorService.Name) 29 | 30 | lazy val serviceLocator: DnsServiceLocator = 31 | new DnsServiceLocator(serviceLocatorService, actorSystem, circuitBreakersPanel, executionContext) 32 | } 33 | -------------------------------------------------------------------------------- /lagom13-java-service-locator-dns/src/main/scala/com/lightbend/lagom/javadsl/dns/ServiceLocatorModule.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Lightbend, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.lightbend.lagom.javadsl.dns 18 | 19 | import com.lightbend.lagom.javadsl.api.ServiceLocator 20 | import play.api.{ Configuration, Environment, Mode } 21 | import play.api.inject.{ Binding, Module } 22 | import javax.inject.Singleton 23 | 24 | import play.api.libs.concurrent.Akka 25 | import com.lightbend.dns.locator.{ ServiceLocator => ServiceLocatorService } 26 | 27 | /** 28 | * This module binds the ServiceLocator interface from Lagom to the `DnsServiceLocator` 29 | * The `DnsServiceLocator` is only bound if the application has been started in `Prod` mode. 30 | * In `Dev` mode the embedded service locator of Lagom is used. 31 | */ 32 | class ServiceLocatorModule extends Module { 33 | 34 | override def bindings(environment: Environment, configuration: Configuration): Seq[Binding[_]] = 35 | if (environment.mode == Mode.Prod) 36 | Seq( 37 | bind[ServiceLocator].to[DnsServiceLocator].in[Singleton], 38 | Akka.bindingOf[ServiceLocatorService]("ServiceLocatorService")) 39 | else 40 | Seq.empty 41 | } 42 | -------------------------------------------------------------------------------- /lagom14-java-service-locator-dns/src/main/scala/com/lightbend/lagom/javadsl/dns/ServiceLocatorModule.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Lightbend, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.lightbend.lagom.javadsl.dns 18 | 19 | import com.lightbend.lagom.javadsl.api.ServiceLocator 20 | import play.api.{ Configuration, Environment, Mode } 21 | import play.api.inject.{ Binding, Module } 22 | import javax.inject.Singleton 23 | 24 | import play.api.libs.concurrent.Akka 25 | import com.lightbend.dns.locator.{ ServiceLocator => ServiceLocatorService } 26 | 27 | /** 28 | * This module binds the ServiceLocator interface from Lagom to the `DnsServiceLocator` 29 | * The `DnsServiceLocator` is only bound if the application has been started in `Prod` mode. 30 | * In `Dev` mode the embedded service locator of Lagom is used. 31 | */ 32 | class ServiceLocatorModule extends Module { 33 | 34 | override def bindings(environment: Environment, configuration: Configuration): Seq[Binding[_]] = 35 | if (environment.mode == Mode.Prod) 36 | Seq( 37 | bind[ServiceLocator].to[DnsServiceLocator].in[Singleton], 38 | Akka.bindingOf[ServiceLocatorService]("ServiceLocatorService")) 39 | else 40 | Seq.empty 41 | } 42 | -------------------------------------------------------------------------------- /lagom13-scala-service-locator-dns/src/main/scala/com/lightbend/lagom/scaladsl/dns/DnsServiceLocator.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Lightbend, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.lightbend.lagom.scaladsl.dns 18 | 19 | import java.net.URI 20 | 21 | import akka.actor.{ ActorRef, ActorSystem } 22 | import akka.pattern.ask 23 | import com.lightbend.lagom.scaladsl.api.Descriptor 24 | import com.lightbend.dns.locator.{ Settings, ServiceLocator => ServiceLocatorService } 25 | import com.lightbend.lagom.internal.client.CircuitBreakers 26 | import com.lightbend.lagom.scaladsl.client.CircuitBreakingServiceLocator 27 | 28 | import scala.concurrent.{ ExecutionContext, Future } 29 | 30 | /** 31 | * DnsServiceLocator implements Lagom's ServiceLocator by using the DNS Service Locator service, which is an actor. 32 | */ 33 | class DnsServiceLocator( 34 | serviceLocatorService: ActorRef, 35 | system: ActorSystem, 36 | circuitBreakers: CircuitBreakers, 37 | implicit val ec: ExecutionContext) extends CircuitBreakingServiceLocator(circuitBreakers) { 38 | 39 | val settings = Settings(system) 40 | 41 | override def locate(name: String, serviceCall: Descriptor.Call[_, _]): Future[Option[URI]] = 42 | serviceLocatorService 43 | .ask(ServiceLocatorService.GetAddress(name))(settings.resolveTimeout1 + settings.resolveTimeout1 + settings.resolveTimeout2) 44 | .mapTo[ServiceLocatorService.Addresses] 45 | .map { 46 | case ServiceLocatorService.Addresses(addresses) => 47 | addresses 48 | .headOption 49 | .map(sa => new URI(sa.protocol, null, sa.host, sa.port, null, null, null)) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lagom14-scala-service-locator-dns/src/main/scala/com/lightbend/lagom/scaladsl/dns/DnsServiceLocator.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Lightbend, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.lightbend.lagom.scaladsl.dns 18 | 19 | import java.net.URI 20 | 21 | import akka.actor.{ ActorRef, ActorSystem } 22 | import akka.pattern.ask 23 | import com.lightbend.lagom.scaladsl.api.Descriptor 24 | import com.lightbend.dns.locator.{ Settings, ServiceLocator => ServiceLocatorService } 25 | import com.lightbend.lagom.scaladsl.client.CircuitBreakersPanel 26 | import com.lightbend.lagom.scaladsl.client.CircuitBreakingServiceLocator 27 | 28 | import scala.concurrent.{ ExecutionContext, Future } 29 | 30 | /** 31 | * DnsServiceLocator implements Lagom's ServiceLocator by using the DNS Service Locator service, which is an actor. 32 | */ 33 | class DnsServiceLocator( 34 | serviceLocatorService: ActorRef, 35 | system: ActorSystem, 36 | circuitBreakers: CircuitBreakersPanel, 37 | implicit val ec: ExecutionContext) extends CircuitBreakingServiceLocator(circuitBreakers) { 38 | 39 | val settings = Settings(system) 40 | 41 | override def locate(name: String, serviceCall: Descriptor.Call[_, _]): Future[Option[URI]] = 42 | serviceLocatorService 43 | .ask(ServiceLocatorService.GetAddress(name))(settings.resolveTimeout1 + settings.resolveTimeout1 + settings.resolveTimeout2) 44 | .mapTo[ServiceLocatorService.Addresses] 45 | .map { 46 | case ServiceLocatorService.Addresses(addresses) => 47 | addresses 48 | .headOption 49 | .map(sa => new URI(sa.protocol, null, sa.host, sa.port, null, null, null)) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /service-locator-dns/src/main/scala/com/lightbend/dns/locator/Settings.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Lightbend, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.lightbend.dns.locator 18 | 19 | import akka.actor.{ Actor, ExtendedActorSystem, Extension, ExtensionKey } 20 | 21 | import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS } 22 | import com.typesafe.config.Config 23 | 24 | import scala.util.matching.Regex 25 | import scala.collection.JavaConversions._ 26 | 27 | object Settings extends ExtensionKey[Settings] 28 | 29 | /** 30 | * Settings for the service locator. 31 | */ 32 | class Settings(system: ExtendedActorSystem) extends Extension { 33 | val nameTranslators: Seq[(Regex, String)] = 34 | serviceLocatorDns 35 | .getObjectList("name-translators") 36 | .toList 37 | .flatMap(_.toMap.map { 38 | case (k, v) => k.r -> v.unwrapped().toString 39 | }) 40 | 41 | val srvTranslators: Seq[(Regex, String)] = 42 | serviceLocatorDns 43 | .getObjectList("srv-translators") 44 | .toList 45 | .flatMap(_.toMap.map { 46 | case (k, v) => k.r -> v.unwrapped().toString 47 | }) 48 | 49 | val resolveTimeout1: FiniteDuration = 50 | duration(serviceLocatorDns, "resolve-timeout1") 51 | 52 | val resolveTimeout2: FiniteDuration = 53 | duration(serviceLocatorDns, "resolve-timeout2") 54 | 55 | private lazy val config = system.settings.config 56 | private lazy val serviceLocatorDns = config.getConfig("service-locator-dns") 57 | 58 | private def duration(config: Config, key: String): FiniteDuration = 59 | Duration(config.getDuration(key, MILLISECONDS), MILLISECONDS) 60 | } 61 | 62 | trait ActorSettings { 63 | this: Actor => 64 | 65 | protected val settings: Settings = 66 | Settings(context.system) 67 | } 68 | -------------------------------------------------------------------------------- /lagom13-java-service-locator-dns/src/main/scala/com/lightbend/lagom/javadsl/dns/DnsServiceLocator.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Lightbend, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.lightbend.lagom.javadsl.dns 18 | 19 | import java.net.URI 20 | import java.util.Optional 21 | import java.util.concurrent.CompletionStage 22 | import javax.inject.{ Inject, Named } 23 | 24 | import akka.actor.{ ActorRef, ActorSystem } 25 | import akka.pattern.ask 26 | import com.lightbend.lagom.javadsl.api.Descriptor 27 | import com.lightbend.dns.locator.{ Settings, ServiceLocator => ServiceLocatorService } 28 | import com.lightbend.lagom.internal.client.CircuitBreakers 29 | import com.lightbend.lagom.javadsl.client.CircuitBreakingServiceLocator 30 | 31 | import scala.compat.java8.FutureConverters._ 32 | import scala.compat.java8.OptionConverters._ 33 | import scala.concurrent.{ ExecutionContext, Future } 34 | 35 | /** 36 | * DnsServiceLocator implements Lagom's ServiceLocator by using the DNS Service Locator service, which is an actor. 37 | */ 38 | class DnsServiceLocator @Inject() ( 39 | @Named("ServiceLocatorService") serviceLocatorService: ActorRef, 40 | system: ActorSystem, 41 | circuitBreakers: CircuitBreakers, 42 | implicit val ec: ExecutionContext) extends CircuitBreakingServiceLocator(circuitBreakers) { 43 | 44 | val settings = Settings(system) 45 | 46 | private def locateAsScala(name: String): Future[Option[URI]] = 47 | serviceLocatorService 48 | .ask(ServiceLocatorService.GetAddress(name))(settings.resolveTimeout1 + settings.resolveTimeout1 + settings.resolveTimeout2) 49 | .mapTo[ServiceLocatorService.Addresses] 50 | .map { 51 | case ServiceLocatorService.Addresses(addresses) => 52 | addresses 53 | .headOption 54 | .map(sa => new URI(sa.protocol, null, sa.host, sa.port, null, null, null)) 55 | } 56 | 57 | override def locate(name: String, serviceCall: Descriptor.Call[_, _]): CompletionStage[Optional[URI]] = 58 | locateAsScala(name).map(_.asJava).toJava 59 | } 60 | -------------------------------------------------------------------------------- /lagom14-java-service-locator-dns/src/main/scala/com/lightbend/lagom/javadsl/dns/DnsServiceLocator.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Lightbend, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.lightbend.lagom.javadsl.dns 18 | 19 | import java.net.URI 20 | import java.util.Optional 21 | import java.util.concurrent.CompletionStage 22 | import javax.inject.{ Inject, Named } 23 | 24 | import akka.actor.{ ActorRef, ActorSystem } 25 | import akka.pattern.ask 26 | import com.lightbend.lagom.javadsl.api.Descriptor 27 | import com.lightbend.dns.locator.{ Settings, ServiceLocator => ServiceLocatorService } 28 | import com.lightbend.lagom.javadsl.client.CircuitBreakersPanel 29 | import com.lightbend.lagom.javadsl.client.CircuitBreakingServiceLocator 30 | 31 | import scala.compat.java8.FutureConverters._ 32 | import scala.compat.java8.OptionConverters._ 33 | import scala.concurrent.{ ExecutionContext, Future } 34 | 35 | /** 36 | * DnsServiceLocator implements Lagom's ServiceLocator by using the DNS Service Locator service, which is an actor. 37 | */ 38 | class DnsServiceLocator @Inject() ( 39 | @Named("ServiceLocatorService") serviceLocatorService: ActorRef, 40 | system: ActorSystem, 41 | circuitBreakers: CircuitBreakersPanel, 42 | implicit val ec: ExecutionContext) extends CircuitBreakingServiceLocator(circuitBreakers) { 43 | 44 | val settings = Settings(system) 45 | 46 | private def locateAsScala(name: String): Future[Option[URI]] = 47 | serviceLocatorService 48 | .ask(ServiceLocatorService.GetAddress(name))(settings.resolveTimeout1 + settings.resolveTimeout1 + settings.resolveTimeout2) 49 | .mapTo[ServiceLocatorService.Addresses] 50 | .map { 51 | case ServiceLocatorService.Addresses(addresses) => 52 | addresses 53 | .headOption 54 | .map(sa => new URI(sa.protocol, null, sa.host, sa.port, null, null, null)) 55 | } 56 | 57 | override def locate(name: String, serviceCall: Descriptor.Call[_, _]): CompletionStage[Optional[URI]] = 58 | locateAsScala(name).map(_.asJava).toJava 59 | } 60 | -------------------------------------------------------------------------------- /lagom13-java-service-locator-dns/src/test/scala/com/lightbend/lagom/javadsl/dns/DnsServiceLocatorSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Lightbend, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.lightbend.lagom.javadsl.dns 18 | 19 | import java.net.URI 20 | 21 | import akka.actor.{ ActorRef, ActorSystem } 22 | import akka.testkit.TestProbe 23 | import com.lightbend.lagom.javadsl.api.Descriptor 24 | import com.typesafe.config.ConfigFactory 25 | import org.scalatest.concurrent.ScalaFutures 26 | import org.scalatest.{ BeforeAndAfterAll, Matchers, WordSpec } 27 | import play.api.inject.guice.GuiceApplicationBuilder 28 | 29 | import scala.compat.java8.FutureConverters._ 30 | import scala.compat.java8.OptionConverters._ 31 | import play.api.inject.bind 32 | import com.lightbend.dns.locator.{ ServiceLocator => ServiceLocatorService } 33 | 34 | import scala.concurrent.ExecutionContextExecutor 35 | 36 | class DnsServiceLocatorSpec extends WordSpec with Matchers with BeforeAndAfterAll with ScalaFutures { 37 | 38 | implicit val system: ActorSystem = ActorSystem("ServiceLocatorSpec", ConfigFactory.load()) 39 | implicit val dispatcher: ExecutionContextExecutor = system.dispatcher 40 | 41 | "The DNS service locator" should { 42 | 43 | "be able to look up a known service" in { 44 | 45 | val dnsServiceLocator = TestProbe() 46 | 47 | // GuiceApplicationBuilder uses the enabled modules from the `reference.conf` 48 | val app = new GuiceApplicationBuilder() 49 | .disable(classOf[ServiceLocatorModule]) 50 | .bindings(bind[ActorRef].qualifiedWith("ServiceLocatorService").to(dnsServiceLocator.ref)) 51 | .build() 52 | 53 | val serviceLocator = app.injector.instanceOf[DnsServiceLocator] 54 | val service = serviceLocator.locate("some-service", Descriptor.Call.NONE).toScala.map(_.asScala) 55 | 56 | dnsServiceLocator.expectMsg(ServiceLocatorService.GetAddress("some-service")) 57 | dnsServiceLocator.sender() ! ServiceLocatorService.Addresses(List(ServiceLocatorService.ServiceAddress("http", "localhost", "127.0.0.1", 9000))) 58 | 59 | service.futureValue shouldBe Some(new URI("http://127.0.0.1:9000")) 60 | } 61 | 62 | "be able to look up an unknown service" in { 63 | 64 | val dnsServiceLocator = TestProbe() 65 | 66 | // GuiceApplicationBuilder uses the enabled modules from the `reference.conf` 67 | val app = new GuiceApplicationBuilder() 68 | .disable(classOf[ServiceLocatorModule]) 69 | .bindings(bind[ActorRef].qualifiedWith("ServiceLocatorService").to(dnsServiceLocator.ref)) 70 | .build() 71 | 72 | val serviceLocator = app.injector.instanceOf[DnsServiceLocator] 73 | val service = serviceLocator.locate("some-service", Descriptor.Call.NONE).toScala.map(_.asScala) 74 | 75 | dnsServiceLocator.expectMsg(ServiceLocatorService.GetAddress("some-service")) 76 | dnsServiceLocator.sender() ! ServiceLocatorService.Addresses(List.empty) 77 | 78 | service.futureValue shouldBe None 79 | } 80 | } 81 | 82 | override protected def afterAll(): Unit = { 83 | system.terminate() 84 | super.afterAll() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lagom14-java-service-locator-dns/src/test/scala/com/lightbend/lagom/javadsl/dns/DnsServiceLocatorSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Lightbend, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.lightbend.lagom.javadsl.dns 18 | 19 | import java.net.URI 20 | 21 | import akka.actor.{ ActorRef, ActorSystem } 22 | import akka.testkit.TestProbe 23 | import com.lightbend.lagom.javadsl.api.Descriptor 24 | import com.typesafe.config.ConfigFactory 25 | import org.scalatest.concurrent.ScalaFutures 26 | import org.scalatest.{ BeforeAndAfterAll, Matchers, WordSpec } 27 | import play.api.inject.guice.GuiceApplicationBuilder 28 | 29 | import scala.compat.java8.FutureConverters._ 30 | import scala.compat.java8.OptionConverters._ 31 | import play.api.inject.bind 32 | import com.lightbend.dns.locator.{ ServiceLocator => ServiceLocatorService } 33 | 34 | import scala.concurrent.ExecutionContextExecutor 35 | 36 | class DnsServiceLocatorSpec extends WordSpec with Matchers with BeforeAndAfterAll with ScalaFutures { 37 | 38 | implicit val system: ActorSystem = ActorSystem("ServiceLocatorSpec", ConfigFactory.load()) 39 | implicit val dispatcher: ExecutionContextExecutor = system.dispatcher 40 | 41 | "The DNS service locator" should { 42 | 43 | "be able to look up a known service" in { 44 | 45 | val dnsServiceLocator = TestProbe() 46 | 47 | // GuiceApplicationBuilder uses the enabled modules from the `reference.conf` 48 | val app = new GuiceApplicationBuilder() 49 | .disable(classOf[ServiceLocatorModule]) 50 | .bindings(bind[ActorRef].qualifiedWith("ServiceLocatorService").to(dnsServiceLocator.ref)) 51 | .build() 52 | 53 | val serviceLocator = app.injector.instanceOf[DnsServiceLocator] 54 | val service = serviceLocator.locate("some-service", Descriptor.Call.NONE).toScala.map(_.asScala) 55 | 56 | dnsServiceLocator.expectMsg(ServiceLocatorService.GetAddress("some-service")) 57 | dnsServiceLocator.sender() ! ServiceLocatorService.Addresses(List(ServiceLocatorService.ServiceAddress("http", "localhost", "127.0.0.1", 9000))) 58 | 59 | service.futureValue shouldBe Some(new URI("http://127.0.0.1:9000")) 60 | } 61 | 62 | "be able to look up an unknown service" in { 63 | 64 | val dnsServiceLocator = TestProbe() 65 | 66 | // GuiceApplicationBuilder uses the enabled modules from the `reference.conf` 67 | val app = new GuiceApplicationBuilder() 68 | .disable(classOf[ServiceLocatorModule]) 69 | .bindings(bind[ActorRef].qualifiedWith("ServiceLocatorService").to(dnsServiceLocator.ref)) 70 | .build() 71 | 72 | val serviceLocator = app.injector.instanceOf[DnsServiceLocator] 73 | val service = serviceLocator.locate("some-service", Descriptor.Call.NONE).toScala.map(_.asScala) 74 | 75 | dnsServiceLocator.expectMsg(ServiceLocatorService.GetAddress("some-service")) 76 | dnsServiceLocator.sender() ! ServiceLocatorService.Addresses(List.empty) 77 | 78 | service.futureValue shouldBe None 79 | } 80 | } 81 | 82 | override protected def afterAll(): Unit = { 83 | system.terminate() 84 | super.afterAll() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /service-locator-dns/src/main/resources/reference.conf: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Lightbend, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | service-locator-dns { 16 | # A list of translators - their order is significant. Translates a service name passed to the 17 | # service locator into a name that will be used for DNS SRV resolution. Only the first match 18 | # will be used. This can be easily overridden by providing a SERVICE_LOCATOR_DNS_NAME_TRANSLATORS 19 | # environment variable. 20 | # 21 | # The default translator below should be all that is required for a DC/OS or Mesos/Marathon 22 | # environment. Kubernetes DNS SRV records take the form: 23 | # 24 | # _my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster.local 25 | # 26 | # ...which implies that your service name should provide the port name. For example with 27 | # Cassandra where there are typically 3 ports ("native" => 9042, "rpc" => 9160 and "storage" => 7200), 28 | # your service name might use a hyphen as a delim for the namespace, service name and port name e.g. 29 | # "customers-cassandra-native". In this case your translator would look like: 30 | # 31 | # "(.*)-(.*)-(.*)" = "_$3._tcp.$2.$1.svc.cluster.local" 32 | # 33 | # You may also want to encode the service's protocol into its name given that the caller 34 | # that is interested in the service location will typically know whether it will be tcp or udp 35 | # (or anything else). Taking the above example, you might then have "customers-cassandra-native-tcp" 36 | # and translate these four components. 37 | # 38 | # You can of course have multiple translators though and statically declare the translations as 39 | # your service's configuration (you'll be supplying environment specific configuration quite typically 40 | # anyway...), and thus keep your service names nice and clean. 41 | # 42 | # By default though, we don't translate much i.e. we let it all pass through and put the onus on the 43 | # client to specify the right service name. 44 | name-translators = [ 45 | { 46 | "^.*$" = "$0" 47 | } 48 | ] 49 | name-translators = ${?SERVICE_LOCATOR_DNS_NAME_TRANSLATORS} 50 | 51 | # A list of translators - their order is significant. Translates the SRV lookup name for downstream processing by 52 | # the library. The name will initially be looked up (after being passed through name-translators), and then the 53 | # result will be mapped according to this translation table. 54 | # 55 | # For example, this entry will rewrite any queries starting with _api._tcp and ensuring they become _api._http 56 | # 57 | # "^_api[.]_tcp[.](.+)$" = "_api.http.$1", 58 | # 59 | # By default, we don't transluate i.e. we let it all pass through. 60 | srv-translators = [ 61 | { 62 | "^.*$" = "$0" 63 | } 64 | ] 65 | srv-translators = ${?SERVICE_LOCATOR_DNS_SRV_TRANSLATORS} 66 | 67 | # The amount of time to wait for a DNS resolution to occur for the first and second lookups of a given 68 | # name. 69 | resolve-timeout1 = 1 second 70 | 71 | # The amount of time to wait for a DNS resolution to occur for the third lookup of a given 72 | # name. 73 | resolve-timeout2 = 2 seconds 74 | } 75 | -------------------------------------------------------------------------------- /lagom14-scala-service-locator-dns/src/test/scala/com/lightbend/lagom/scaladsl/dns/DnsServiceLocatorSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Lightbend, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.lightbend.lagom.scaladsl.dns 18 | 19 | import java.net.URI 20 | 21 | import akka.actor.{ ActorRef, ActorSystem } 22 | import akka.stream.{ ActorMaterializer, Materializer } 23 | import akka.testkit.TestProbe 24 | import com.typesafe.config.ConfigFactory 25 | import org.scalatest.concurrent.ScalaFutures 26 | import org.scalatest._ 27 | import com.lightbend.dns.locator.{ ServiceLocator => ServiceLocatorService } 28 | import com.lightbend.lagom.internal.client.{ CircuitBreakerConfig, CircuitBreakerMetricsProviderImpl, CircuitBreakers } 29 | import com.lightbend.lagom.internal.spi.CircuitBreakerMetricsProvider 30 | import com.lightbend.lagom.scaladsl.api.{ Descriptor, Service } 31 | import com.lightbend.lagom.scaladsl.client.CircuitBreakersPanel 32 | import com.lightbend.lagom.scaladsl.server.{ LagomApplication, LagomApplicationContext } 33 | import play.api.libs.ws.ahc.AhcWSComponents 34 | import play.api.routing.Router 35 | 36 | import scala.concurrent.ExecutionContext 37 | 38 | object DnsServiceLocatorSpec { 39 | class DummyService extends Service { 40 | override def descriptor: Descriptor = { 41 | import Service._ 42 | named("dummy") 43 | } 44 | } 45 | } 46 | 47 | class DnsServiceLocatorSpec extends WordSpec with Matchers with BeforeAndAfterAll with ScalaFutures { 48 | 49 | implicit val system: ActorSystem = ActorSystem("ServiceLocatorSpec", ConfigFactory.load()) 50 | implicit val mat: Materializer = ActorMaterializer.create(system) 51 | 52 | val app = new LagomApplication(LagomApplicationContext.Test) with AhcWSComponents with DnsServiceLocatorComponents { 53 | override lazy val lagomServer = serverFor[DnsServiceLocatorSpec.DummyService](new DnsServiceLocatorSpec.DummyService()) 54 | override lazy val actorSystem: ActorSystem = system 55 | override lazy val materializer: Materializer = mat 56 | override lazy val executionContext: ExecutionContext = actorSystem.dispatcher 57 | override lazy val router: Router = Router.empty 58 | val dnsServiceLocator = TestProbe() 59 | override lazy val serviceLocatorService: ActorRef = dnsServiceLocator.ref 60 | } 61 | 62 | "The DNS service locator" should { 63 | 64 | "be able to look up a known service" in { 65 | 66 | val service = app.serviceLocator.locate("some-service") 67 | 68 | app.dnsServiceLocator.expectMsg(ServiceLocatorService.GetAddress("some-service")) 69 | app.dnsServiceLocator.sender() ! ServiceLocatorService.Addresses(List(ServiceLocatorService.ServiceAddress("http", "localhost", "127.0.0.1", 9000))) 70 | 71 | service.futureValue shouldBe Some(new URI("http://127.0.0.1:9000")) 72 | } 73 | 74 | "be able to look up an unknown service" in { 75 | 76 | val service = app.serviceLocator.locate("some-service") 77 | 78 | app.dnsServiceLocator.expectMsg(ServiceLocatorService.GetAddress("some-service")) 79 | app.dnsServiceLocator.sender() ! ServiceLocatorService.Addresses(List.empty) 80 | 81 | service.futureValue shouldBe None 82 | } 83 | } 84 | 85 | override protected def afterAll(): Unit = { 86 | system.terminate() 87 | super.afterAll() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lagom13-scala-service-locator-dns/src/test/scala/com/lightbend/lagom/scaladsl/dns/DnsServiceLocatorSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Lightbend, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.lightbend.lagom.scaladsl.dns 18 | 19 | import java.net.URI 20 | 21 | import akka.actor.{ ActorRef, ActorSystem } 22 | import akka.stream.{ ActorMaterializer, Materializer } 23 | import akka.testkit.TestProbe 24 | import com.typesafe.config.ConfigFactory 25 | import org.scalatest.concurrent.ScalaFutures 26 | import org.scalatest._ 27 | import com.lightbend.dns.locator.{ ServiceLocator => ServiceLocatorService } 28 | import com.lightbend.lagom.internal.client.{ CircuitBreakerConfig, CircuitBreakerMetricsProviderImpl, CircuitBreakers } 29 | import com.lightbend.lagom.internal.spi.CircuitBreakerMetricsProvider 30 | import com.lightbend.lagom.scaladsl.api.Service.named 31 | import com.lightbend.lagom.scaladsl.api.{ Descriptor, Service } 32 | import com.lightbend.lagom.scaladsl.dns 33 | import com.lightbend.lagom.scaladsl.server.{ LagomApplication, LagomApplicationContext } 34 | import play.api.libs.ws.ahc.AhcWSComponents 35 | import play.api.routing.Router 36 | 37 | import scala.concurrent.ExecutionContext 38 | 39 | object DnsServiceLocatorSpec { 40 | class DummyService extends Service { 41 | override def descriptor: Descriptor = { 42 | import Service._ 43 | named("dummy") 44 | } 45 | } 46 | } 47 | 48 | class DnsServiceLocatorSpec extends WordSpec with Matchers with BeforeAndAfterAll with ScalaFutures { 49 | 50 | implicit val system: ActorSystem = ActorSystem("ServiceLocatorSpec", ConfigFactory.load()) 51 | implicit val mat: Materializer = ActorMaterializer.create(system) 52 | 53 | val app = new LagomApplication(LagomApplicationContext.Test) with AhcWSComponents with DnsServiceLocatorComponents { 54 | override lazy val lagomServer = serverFor[DnsServiceLocatorSpec.DummyService](new dns.DnsServiceLocatorSpec.DummyService()) 55 | override lazy val actorSystem: ActorSystem = system 56 | override lazy val materializer: Materializer = mat 57 | override lazy val executionContext: ExecutionContext = actorSystem.dispatcher 58 | override lazy val router: Router = Router.empty 59 | override lazy val circuitBreakerMetricsProvider: CircuitBreakerMetricsProvider = new CircuitBreakerMetricsProviderImpl(actorSystem) 60 | override lazy val circuitBreakerConfig: CircuitBreakerConfig = new CircuitBreakerConfig(configuration) 61 | override lazy val circuitBreakers: CircuitBreakers = new CircuitBreakers(actorSystem, circuitBreakerConfig, circuitBreakerMetricsProvider) 62 | val dnsServiceLocator = TestProbe() 63 | override lazy val serviceLocatorService: ActorRef = dnsServiceLocator.ref 64 | } 65 | 66 | "The DNS service locator" should { 67 | 68 | "be able to look up a known service" in { 69 | 70 | val service = app.serviceLocator.locate("some-service") 71 | 72 | app.dnsServiceLocator.expectMsg(ServiceLocatorService.GetAddress("some-service")) 73 | app.dnsServiceLocator.sender() ! ServiceLocatorService.Addresses(List(ServiceLocatorService.ServiceAddress("http", "localhost", "127.0.0.1", 9000))) 74 | 75 | service.futureValue shouldBe Some(new URI("http://127.0.0.1:9000")) 76 | } 77 | 78 | "be able to look up an unknown service" in { 79 | 80 | val service = app.serviceLocator.locate("some-service") 81 | 82 | app.dnsServiceLocator.expectMsg(ServiceLocatorService.GetAddress("some-service")) 83 | app.dnsServiceLocator.sender() ! ServiceLocatorService.Addresses(List.empty) 84 | 85 | service.futureValue shouldBe None 86 | } 87 | } 88 | 89 | override protected def afterAll(): Unit = { 90 | system.terminate() 91 | super.afterAll() 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecation notice 2 | 3 | This project has been deprecated in favor of Lightbend Orchestration 4 | 5 | https://developer.lightbend.com/docs/lightbend-orchestration/latest/index.html 6 | 7 | 8 | # Service Locator DNS 9 | 10 | [![Build Status](https://api.travis-ci.org/lightbend/service-locator-dns.png?branch=master)](https://travis-ci.org/lightbend/service-locator-dns) 11 | 12 | ## Motivation 13 | 14 | DNS SRV is specification that describes how to locate services over the DNS protocol. Service discovery backends commonly offer DNS SRV as a protocol, and so this project leverages that capability. 15 | 16 | ## Introduction 17 | 18 | [DNS SRV](https://tools.ietf.org/html/rfc2782) lookups for [Akka](http://akka.io/) and [Lagom](http://www.lagomframework.com/) which therefore includes [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Kubernetes](http://kubernetes.io/) and [Consul](https://www.consul.io/) usage. This project provides two libraries: 19 | 20 | * a pure-Akka based service locator for locating services by name and returning their hosts and ports; and 21 | * a Lagom service locator utilizing the Akka one. 22 | 23 | This project uses the wonderful [akka-dns](https://github.com/ilya-epifanov/akka-dns) project in order to offer non-blocking DNS lookups for SRV records. 24 | 25 | ## Usage 26 | 27 | The service locator is written using Akka and can be used directly as via its Actor. Alternatively Lagom can be configured to use this project's service locator with no additional coding for your project. 28 | 29 | ### Pure Akka usage 30 | 31 | You'll need a resolver for akka-dns: 32 | 33 | ```scala 34 | resolvers += bintrayRepo("hajile", "maven") 35 | ``` 36 | 37 | For pure Akka based usage (without Lagom): 38 | 39 | ```scala 40 | libraryDependencies += "com.lightbend" %% "service-locator-dns" % "2.3.0" 41 | ``` 42 | 43 | An example: 44 | 45 | ```scala 46 | // Requisite import 47 | import com.lightbend.dns.locator.ServiceLocator 48 | 49 | // Create a service locator 50 | val serviceLocator = system.actorOf(ServiceLocator.props, ServiceLocator.Name) 51 | 52 | // Send a request to get addresses. Expect a `ServiceLocator.Addresses` reply. 53 | serviceLocator ! ServiceLocator.GetAddresses("_some-service._tcp.marathon.mesos") 54 | ``` 55 | 56 | ### Lagom Java usage 57 | 58 | Alternatively, when using from Lagom and Java: 59 | 60 | ```scala 61 | libraryDependencies += "com.lightbend" %% "lagom13-java-service-locator-dns" % "2.3.0" 62 | ``` 63 | 64 | Simply adding the above dependency to a Lagom project should be sufficient. There is a `ServiceLocatorModule` that will be automatically discovered by the Lagom environment. All of your Lagom service locator calls will automatically route via the service locator implementation that this project provides. 65 | 66 | ### Lagom Scala usage 67 | 68 | Alternatively, when using from Lagom and Scala: 69 | 70 | ```scala 71 | libraryDependencies += "com.lightbend" %% "lagom13-scala-service-locator-dns" % "2.3.0" 72 | ``` 73 | 74 | When declaring your Lagom application you will also be required to mix in the `com.lightbend.lagom.scaladsl.dns.DnsServiceLocatorComponents` trait: 75 | 76 | ```scala 77 | import com.lightbend.lagom.scaladsl.dns.DnsServiceLocatorComponents 78 | ... 79 | 80 | class LagomLoader extends LagomApplicationLoader { 81 | override def load(context: LagomApplicationContext) = 82 | new MyLagomApplication(context) with DnsServiceLocatorComponents 83 | ... 84 | ``` 85 | 86 | ## Advanced configuration 87 | 88 | Your platform may require differing "transformers" that will translate a service name 89 | into a DNS SRV name to be looked up. Here is the Typesafe config declaration to be 90 | considered in its entirety. 91 | 92 | ``` 93 | service-locator-dns { 94 | # A list of translators - their order is significant. Translates a service name passed to the 95 | # service locator into a name that will be used for DNS SRV resolution. Only the first match 96 | # will be used. This can be easily overridden by providing a SERVICE_LOCATOR_DNS_NAME_TRANSLATORS 97 | # environment variable. 98 | # 99 | # The default translator below should be all that is required for a DC/OS or Mesos/Marathon 100 | # environment. Kubernetes DNS SRV records take the form: 101 | # 102 | # _my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster.local 103 | # 104 | # ...which implies that your service name should provide the port name. For example with 105 | # Cassandra where there are typically 3 ports ("native" => 9042, "rpc" => 9160 and "storage" => 7200), 106 | # your service name might use a hyphen as a delim for the namespace, service name and port name e.g. 107 | # "customers-cassandra-native". In this case your translator would look like: 108 | # 109 | # "(.*)-(.*)-(.*)" = "_$3._tcp.$2.$1.svc.cluster.local" 110 | # 111 | # You may also want to encode the service's protocol into its name given that the caller 112 | # that is interested in the service location will typically know whether it will be tcp or udp 113 | # (or anything else). Taking the above example, you might then have "customers-cassandra-native-tcp" 114 | # and translate these four components. 115 | # 116 | # You can of course have multiple translators though and statically declare the translations as 117 | # your service's configuration (you'll be supplying environment specific configuration quite typically 118 | # anyway...), and thus keep your service names nice and clean. 119 | # 120 | # By default though, we don't translate much i.e. we let it all pass through and put the onus on the 121 | # client to specify the right service name. 122 | name-translators = [ 123 | { 124 | "^.*$" = "$0" 125 | } 126 | ] 127 | name-translators = ${?SERVICE_LOCATOR_DNS_NAME_TRANSLATORS} 128 | 129 | # A list of translators - their order is significant. Translates the SRV lookup name for downstream processing by 130 | # the library. The name will initially be looked up (after being passed through name-translators), and then the 131 | # result will be mapped according to this translation table. 132 | # 133 | # For example, this entry will rewrite any queries starting with _api._tcp and ensuring they become _api._http 134 | # 135 | # "^_api[.]_tcp[.](.+)$" = "_api.http.$1", 136 | # 137 | # By default, we don't transluate i.e. we let it all pass through. 138 | srv-translators = [ 139 | { 140 | "^.*$" = "$0" 141 | } 142 | ] 143 | srv-translators = ${?SERVICE_LOCATOR_DNS_SRV_TRANSLATORS} 144 | 145 | # The amount of time to wait for a DNS resolution to occur for the first and second lookups of a given 146 | # name. 147 | resolve-timeout1 = 1 second 148 | 149 | # The amount of time to wait for a DNS resolution to occur for the third lookup of a given 150 | # name. 151 | resolve-timeout2 = 2 seconds 152 | } 153 | ``` 154 | 155 | ## Maintenance 156 | 157 | Enterprise Suite Platform Team 158 | -------------------------------------------------------------------------------- /service-locator-dns/src/main/scala/com/lightbend/dns/locator/ServiceLocator.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Lightbend, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.lightbend.dns.locator 18 | 19 | import java.util.concurrent.ThreadLocalRandom 20 | 21 | import akka.actor.{ Actor, ActorLogging, ActorRef, Props } 22 | import akka.io.AsyncDnsResolver.SrvResolved 23 | import akka.io.{ Dns, IO } 24 | import akka.pattern.{ AskTimeoutException, ask, pipe } 25 | import ru.smslv.akka.dns.raw.SRVRecord 26 | 27 | import scala.annotation.tailrec 28 | import scala.concurrent.Future 29 | import scala.concurrent.duration.FiniteDuration 30 | import scala.util.control.NonFatal 31 | import scala.util.matching.Regex 32 | 33 | object ServiceLocator { 34 | 35 | val Name = "DnsServiceLocator" 36 | 37 | def props: Props = 38 | Props(new ServiceLocator) 39 | 40 | /** 41 | * Get one address given a service name. Names will be translated given a list of translator 42 | * regexs provided as config. Only the first translation matched will be used. It is therefore 43 | * important that the matchers are ordered carefully. 44 | * 45 | * An [[Addresses]] object will be replied with addresses sorted by priority and weight, as 46 | * per RFC 2782. Only one of the addresses within the highest priority and randomized across weight 47 | * will be returned. 48 | */ 49 | final case class GetAddress(name: String) 50 | 51 | /** 52 | * Get one or more addresses given a service name. Names are translated as per [[GetAddress]]. 53 | * 54 | * An [[Addresses]] object will be replied with addresses sorted by priority and weight, as 55 | * per RFC 2782. 56 | */ 57 | final case class GetAddresses(name: String) 58 | 59 | /** 60 | * A sequence of ServiceAddress objects are the reply, which may of course be empty. 61 | */ 62 | sealed abstract case class Addresses(addresses: Seq[ServiceAddress]) 63 | object Addresses { 64 | private val empty: Addresses = new Addresses(Nil) {} 65 | 66 | def apply(addresses: Seq[ServiceAddress]): Addresses = 67 | if (addresses.nonEmpty) new Addresses(addresses) {} 68 | else empty 69 | } 70 | 71 | /** 72 | * Used within replies. 73 | */ 74 | final case class ServiceAddress(protocol: String, hostname: String, host: String, port: Int) 75 | 76 | private[locator] final case class RequestContext(replyTo: ActorRef, resolveOne: Boolean, srv: Seq[SRVRecord]) 77 | 78 | private[locator] final case class ReplyContext(resolutions: Seq[(Dns.Resolved, SRVRecord)], rc: RequestContext) 79 | 80 | @tailrec 81 | private[locator] def matchTranslation(name: String, translators: Seq[(Regex, String)]): Option[String] = 82 | translators match { 83 | case (r, s) +: tail => 84 | val matcher = r.pattern.matcher(name) 85 | if (matcher.matches()) 86 | Some(matcher.replaceAll(s)) 87 | else 88 | matchTranslation(name, tail) 89 | case _ => None 90 | } 91 | 92 | private[locator] def protocolFromName(name: String): String = 93 | name.iterator.dropWhile(_ != '.').drop(1).takeWhile(_ != '.').drop(1).mkString 94 | } 95 | 96 | /** 97 | * A service locator that can get all addresses for a service using DNS SRV lookups. 98 | * When considering DNS SRV we ignore priority and weight, sd they appear pretty useless 99 | * for distributing across service instances as the information is often static in nature. 100 | * If this turns out not to be the case though then we could certainly consider them. 101 | * We also avoid caching requests at the level of this actor as the underlying DNS 102 | * resolver will cache heavily for us. Again though, caching could be introduced at this 103 | * actor's level if we find that it is required. 104 | */ 105 | class ServiceLocator extends Actor with ActorSettings with ActorLogging { 106 | 107 | import ServiceLocator._ 108 | 109 | private val _dns = IO(Dns)(context.system) 110 | protected def dns: ActorRef = _dns 111 | 112 | override def receive: Receive = { 113 | case GetAddress(name) => 114 | resolveSrv(name, resolveOne = true) 115 | 116 | case GetAddresses(name) => 117 | resolveSrv(name, resolveOne = false) 118 | 119 | case rc: RequestContext => 120 | // When we return just one address then we randomize which of the candidates to return 121 | val (srvFrom, srvSize) = 122 | if (rc.resolveOne && rc.srv.nonEmpty) 123 | (ThreadLocalRandom.current.nextInt(rc.srv.size), 1) 124 | else 125 | (0, rc.srv.size) 126 | import context.dispatcher 127 | val resolutions = 128 | rc.srv 129 | .slice(srvFrom, srvFrom + srvSize) 130 | .map { srv => 131 | resolveDns(srv.target).map(_ -> srv) 132 | } 133 | Future 134 | .sequence(resolutions) 135 | .map(ReplyContext(_, rc)) 136 | .pipeTo(self) 137 | 138 | case ReplyContext(resolutions, rc) => 139 | log.debug("Resolved: {}", resolutions) 140 | val addresses = 141 | resolutions 142 | .flatMap { 143 | case (resolved, srv) => 144 | val protocol = protocolFromName(srv.name) 145 | val port = srv.port 146 | resolved.ipv4.map(host => ServiceAddress(protocol, srv.target, host.getHostAddress, port)) ++ 147 | resolved.ipv6.map(host => ServiceAddress(protocol, srv.target, host.getHostAddress, port)) 148 | } 149 | rc.replyTo ! Addresses(addresses) 150 | } 151 | 152 | private def resolveSrv(name: String, resolveOne: Boolean): Unit = { 153 | log.debug("Resolving: {}", name) 154 | val matchedName = matchTranslation(name, settings.nameTranslators) 155 | matchedName.foreach { mn => 156 | if (name != mn) 157 | log.debug("Translated {} to {}", name, mn) 158 | 159 | val replyTo = sender() 160 | import context.dispatcher 161 | resolveSrvOnce(mn, settings.resolveTimeout1) 162 | .recoverWith { 163 | case _: AskTimeoutException => 164 | resolveSrvOnce(mn, settings.resolveTimeout1) 165 | .recoverWith { 166 | case _: AskTimeoutException => 167 | resolveSrvOnce(mn, settings.resolveTimeout2) 168 | } 169 | } 170 | .recover { 171 | case iobe: IndexOutOfBoundsException => 172 | log.error("Could not substitute the service name with the name translator {}", iobe.getMessage) 173 | SrvResolved(mn, Nil) 174 | 175 | case ate: AskTimeoutException => 176 | log.debug("Timed out querying DNS SRV for {}", name) 177 | SrvResolved(mn, Nil) 178 | 179 | case NonFatal(e) => 180 | log.error(e, "Unexpected error when resolving an SRV record") 181 | SrvResolved(mn, Nil) 182 | } 183 | .map(resolved => 184 | RequestContext( 185 | replyTo, 186 | resolveOne, 187 | resolved.srv.map { record => 188 | matchTranslation(record.name, settings.srvTranslators) match { 189 | case Some(newName) if name != newName => 190 | log.debug("Translated {} to {}", record.name, newName) 191 | record.copy(name = newName) 192 | case _ => 193 | record 194 | } 195 | })) 196 | .pipeTo(self) 197 | } 198 | if (matchedName.isEmpty) 199 | sender() ! Addresses(Nil) 200 | } 201 | 202 | private def resolveSrvOnce(name: String, resolveTimeout: FiniteDuration): Future[SrvResolved] = { 203 | import context.dispatcher 204 | dns 205 | .ask(Dns.Resolve(name))(resolveTimeout) 206 | .map { 207 | case srvResolved: SrvResolved => srvResolved 208 | case _: Dns.Resolved => SrvResolved(name, Nil) 209 | } 210 | } 211 | 212 | private def resolveDns(name: String): Future[Dns.Resolved] = { 213 | import context.dispatcher 214 | dns 215 | .ask(Dns.Resolve(name))(settings.resolveTimeout1) 216 | .recoverWith { 217 | case _: AskTimeoutException => 218 | dns.ask(Dns.Resolve(name))(settings.resolveTimeout1) 219 | .recoverWith { 220 | case _: AskTimeoutException => 221 | dns.ask(Dns.Resolve(name))(settings.resolveTimeout2) 222 | } 223 | } 224 | .mapTo[Dns.Resolved] 225 | .recover { 226 | case ate: AskTimeoutException => 227 | log.debug("Timed out querying DNS for {}", name) 228 | Dns.Resolved(name, Nil) 229 | 230 | case NonFatal(e) => 231 | log.error(e, "Unexpected error when resolving an DNS record") 232 | Dns.Resolved(name, Nil) 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Typesafe Project & Developer Guidelines 2 | 3 | These guidelines are meant to be a living document that should be changed and adapted as needed. We encourage changes that makes it easier to achieve our goals in an efficient way. 4 | 5 | These guidelines mainly applies to Typesafe’s “mature” projects - not necessarily to projects of the type ‘collection of scripts’ etc. 6 | 7 | ## General Workflow 8 | 9 | This is the process for committing code into master. There are of course exceptions to these rules, for example minor changes to comments and documentation, fixing a broken build etc. 10 | 11 | 1. Make sure you have signed the [Typesafe CLA](http://www.typesafe.com/contribute/cla), if not, sign it online. 12 | 2. Before starting to work on a feature or a fix, you have to make sure that: 13 | 1. There is a ticket for your work in the project's issue tracker. If not, create it first. 14 | 2. The ticket has been scheduled for the current milestone. 15 | 3. The ticket is estimated by the team. 16 | 4. The ticket have been discussed and prioritized by the team. 17 | 3. You should always perform your work in a Git feature branch. The branch should be given a descriptive name that explains its intent. Some teams also like adding the ticket number and/or the [GitHub](http://github.com) user ID to the branch name, these details is up to each of the individual teams. 18 | 4. When the feature or fix is completed you should open a [Pull Request](https://help.github.com/articles/using-pull-requests) on GitHub. 19 | 5. The Pull Request should be reviewed by other maintainers (as many as feasible/practical). Note that the maintainers can consist of outside contributors, both within and outside Typesafe. Outside contributors (for example from EPFL or independent committers) are encouraged to participate in the review process, it is not a closed process. 20 | 6. After the review you should fix the issues as needed (pushing a new commit for new review etc.), iterating until the reviewers give their thumbs up. 21 | 7. Once the code has passed review the Pull Request can be merged into the master branch. 22 | 23 | ## Pull Request Requirements 24 | 25 | For a Pull Request to be considered at all it has to meet these requirements: 26 | 27 | 1. Live up to the current code standard: 28 | - Not violate [DRY](http://programmer.97things.oreilly.com/wiki/index.php/Don%27t_Repeat_Yourself). 29 | - [Boy Scout Rule](http://programmer.97things.oreilly.com/wiki/index.php/The_Boy_Scout_Rule) needs to have been applied. 30 | 2. Regardless if the code introduces new features or fixes bugs or regressions, it must have comprehensive tests. 31 | 3. The code must be well documented in the Typesafe's standard documentation format (see the ‘Documentation’ section below). 32 | 4. Copyright: 33 | All Typesafe projects must include Typesafe copyright notices. Each project can choose between one of two approaches: 34 | 1. All source files in the project must have a Typesafe copyright notice in the file header. 35 | 2. The Notices file for the project includes the Typesafe copyright notice and no other files contain copyright notices. See http://www.apache.org/legal/src-headers.html for instructions for managing this approach for copyrights. 36 | 37 | Other guidelines to follow for copyright notices: 38 | - Use a form of ``Copyright (C) 2011-2014 Typesafe Inc. ``, where the start year is when the project or file was first created and the end year is the last time the project or file was modified. 39 | - Never delete or change existing copyright notices, just add additional info. 40 | - Do not use ``@author`` tags since it does not encourage [Collective Code Ownership](http://www.extremeprogramming.org/rules/collective.html). However, each project should make sure that the contributors gets the credit they deserve—in a text file or page on the project website and in the release notes etc. 41 | 42 | If these requirements are not met then the code should **not** be merged into master, or even reviewed - regardless of how good or important it is. No exceptions. 43 | 44 | ## Continuous Integration 45 | 46 | Each project should be configured to use a continuous integration (CI) tool (i.e. a build server ala Jenkins). Typesafe has a Jenkins server farm that can be used. The CI tool should, on each push to master, build the **full** distribution and run **all** tests, and if something fails it should email out a notification with the failure report to the committer and the core team. The CI tool should also be used in conjunction with Typesafe’s Pull Request Validator (discussed below). 47 | 48 | ## Documentation 49 | 50 | All documentation should be generated using the sbt-site-plugin, *or* publish artifacts to a repository that can be consumed by the typesafe stack. 51 | 52 | All documentation must abide by the following maxims: 53 | 54 | - Example code should be run as part of an automated test suite. 55 | - Version should be **programmatically** specifiable to the build. 56 | - Generation should be **completely automated** and available for scripting. 57 | - Artifacts that must be included in the Typesafe Stack should be published to a maven “documentation” repository as documentation artifacts. 58 | 59 | All documentation is preferred to be in Typesafe's standard documentation format [reStructuredText](http://doc.akka.io/docs/akka/snapshot/dev/documentation.html) compiled using Typesafe's customized [Sphinx](http://sphinx.pocoo.org/) based documentation generation system, which among other things allows all code in the documentation to be externalized into compiled files and imported into the documentation. 60 | 61 | For more info, or for a starting point for new projects, look at the [Typesafe Documentation Template project](https://github.com/typesafehub/doc-template) 62 | 63 | For larger projects that have invested a lot of time and resources into their current documentation and samples scheme (like for example Play), it is understandable that it will take some time to migrate to this new model. In these cases someone from the project needs to take the responsibility of manual QA and verifier for the documentation and samples. 64 | 65 | ## External Dependencies 66 | 67 | All the external runtime dependencies for the project, including transitive dependencies, must have an open source license that is equal to, or compatible with, [Apache 2](http://www.apache.org/licenses/LICENSE-2.0). 68 | 69 | This must be ensured by manually verifying the license for all the dependencies for the project: 70 | 71 | 1. Whenever a committer to the project changes a version of a dependency (including Scala) in the build file. 72 | 2. Whenever a committer to the project adds a new dependency. 73 | 3. Whenever a new release is cut (public or private for a customer). 74 | 75 | Which licenses that are compatible with Apache 2 are defined in [this doc](http://www.apache.org/legal/3party.html#category-a), where you can see that the licenses that are listed under ``Category A`` automatically compatible with Apache 2, while the ones listed under ``Category B`` needs additional action: 76 | > “Each license in this category requires some degree of [reciprocity](http://www.apache.org/legal/3party.html#define-reciprocal); therefore, additional action must be taken in order to minimize the chance that a user of an Apache product will create a derivative work of a reciprocally-licensed portion of an Apache product without being aware of the applicable requirements.” 77 | 78 | Each project must also create and maintain a list of all dependencies and their licenses, including all their transitive dependencies. This can be done in either in the documentation or in the build file next to each dependency. 79 | 80 | ## Work In Progress 81 | 82 | It is ok to work on a public feature branch in the GitHub repository. Something that can sometimes be useful for early feedback etc. If so then it is preferable to name the branch accordingly. This can be done by either prefix the name with ``wip-`` as in ‘Work In Progress’, or use hierarchical names like ``wip/..``, ``feature/..`` or ``topic/..``. Either way is fine as long as it is clear that it is work in progress and not ready for merge. This work can temporarily have a lower standard. However, to be merged into master it will have to go through the regular process outlined above, with Pull Request, review etc.. 83 | 84 | Also, to facilitate both well-formed commits and working together, the ``wip`` and ``feature``/``topic`` identifiers also have special meaning. Any branch labelled with ``wip`` is considered “git-unstable” and may be rebased and have its history rewritten. Any branch with ``feature``/``topic`` in the name is considered “stable” enough for others to depend on when a group is working on a feature. 85 | 86 | ## Creating Commits And Writing Commit Messages 87 | 88 | Follow these guidelines when creating public commits and writing commit messages. 89 | 90 | 1. If your work spans multiple local commits (for example; if you do safe point commits while working in a feature branch or work in a branch for long time doing merges/rebases etc.) then please do not commit it all but rewrite the history by squashing the commits into a single big commit which you write a good commit message for (like discussed in the following sections). For more info read this article: [Git Workflow](http://sandofsky.com/blog/git-workflow.html). Every commit should be able to be used in isolation, cherry picked etc. 91 | 2. First line should be a descriptive sentence what the commit is doing. It should be possible to fully understand what the commit does by just reading this single line. It is **not ok** to only list the ticket number, type "minor fix" or similar. Include reference to ticket number, prefixed with #, at the end of the first line. If the commit is a small fix, then you are done. If not, go to 3. 92 | 3. Following the single line description should be a blank line followed by an enumerated list with the details of the commit. 93 | 4. Add keywords for your commit (depending on the degree of automation we reach, the list may change over time): 94 | * ``Review by @gituser`` - if you want to notify someone on the team. The others can, and are encouraged to participate. 95 | * ``Fix/Fixing/Fixes/Close/Closing/Refs #ticket`` - if you want to mark the ticket as fixed in the issue tracker (Assembla understands this). 96 | * ``backport to _branch name_`` - if the fix needs to be cherry-picked to another branch (like 2.9.x, 2.10.x, etc) 97 | 98 | Example: 99 | 100 | Adding monadic API to Future. Fixes #2731 101 | 102 | * Details 1 103 | * Details 2 104 | * Details 3 105 | -------------------------------------------------------------------------------- /service-locator-dns/src/test/scala/com/lightbend/dns/locator/ServiceLocatorSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Lightbend, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.lightbend.dns.locator 18 | 19 | import java.net.InetAddress 20 | 21 | import akka.actor.{ ActorRef, ActorSystem, Props } 22 | import akka.io.AsyncDnsResolver.SrvResolved 23 | import akka.io.Dns 24 | import akka.testkit.TestProbe 25 | import com.lightbend.dns.locator.ServiceLocator.{ Addresses, ServiceAddress } 26 | import com.typesafe.config.ConfigFactory 27 | import org.scalatest._ 28 | import ru.smslv.akka.dns.raw.SRVRecord 29 | 30 | import scala.concurrent.duration._ 31 | 32 | object ServiceLocatorSpec { 33 | class TestServiceLocator(dnsProbe: TestProbe) extends ServiceLocator { 34 | override protected def dns: ActorRef = 35 | dnsProbe.ref 36 | } 37 | } 38 | 39 | class ServiceLocatorSpec extends WordSpec with Matchers with BeforeAndAfterAll { 40 | 41 | import ServiceLocatorSpec._ 42 | 43 | implicit val system = ActorSystem( 44 | "ServiceLocatorSpec", 45 | ConfigFactory.parseString( 46 | s""" 47 | |service-locator-dns { 48 | | srv-translators = [ 49 | | { "^_test-srv-translator[.]_tcp[.](.+)" = "_test-srv-translator._http.$$1" }, 50 | | { "^.*$$" = "$$0" } 51 | | ] 52 | |} 53 | """.stripMargin)) 54 | 55 | "A DNS service locator" should { 56 | "resolve a service to 2 addresses" in { 57 | val dnsProbe = TestProbe() 58 | val serviceLocator = system.actorOf(Props(new TestServiceLocator(dnsProbe))) 59 | 60 | val requestor = TestProbe() 61 | 62 | requestor.send(serviceLocator, ServiceLocator.GetAddresses("_some-service._tcp.marathon.mesos")) 63 | 64 | dnsProbe.expectMsg(Dns.Resolve("_some-service._tcp.marathon.mesos")) 65 | val srv1 = SRVRecord("_some-service._tcp.marathon.mesos", 3600, 0, 0, 1000, "some-service-host1.marathon.mesos") 66 | val srv2 = SRVRecord("_some-service._tcp.marathon.mesos", 3600, 0, 0, 1001, "some-service-host2.marathon.mesos") 67 | dnsProbe.sender() ! SrvResolved("some-service", List(srv1, srv2)) 68 | 69 | dnsProbe.expectMsg(Dns.Resolve("some-service-host1.marathon.mesos")) 70 | dnsProbe.sender() ! Dns.Resolved("some-service-host1.marathon.mesos", List(InetAddress.getByName("1.1.1.1"))) 71 | dnsProbe.expectMsg(Dns.Resolve("some-service-host2.marathon.mesos")) 72 | dnsProbe.sender() ! Dns.Resolved("some-service-host2.marathon.mesos", List(InetAddress.getByName("1.1.1.2"))) 73 | 74 | requestor.expectMsg( 75 | Addresses( 76 | Seq( 77 | ServiceAddress("tcp", "some-service-host1.marathon.mesos", "1.1.1.1", 1000), 78 | ServiceAddress("tcp", "some-service-host2.marathon.mesos", "1.1.1.2", 1001)))) 79 | } 80 | 81 | "resolve a service to 1 address having requested just one" in { 82 | val dnsProbe = TestProbe() 83 | val serviceLocator = system.actorOf(Props(new TestServiceLocator(dnsProbe))) 84 | 85 | val requestor = TestProbe() 86 | 87 | requestor.send(serviceLocator, ServiceLocator.GetAddress("_some-service._tcp.marathon.mesos")) 88 | 89 | dnsProbe.expectMsg(Dns.Resolve("_some-service._tcp.marathon.mesos")) 90 | val srv1 = SRVRecord("_some-service._tcp.marathon.mesos", 3600, 0, 0, 1000, "some-service-host1.marathon.mesos") 91 | val srv2 = SRVRecord("_some-service._tcp.marathon.mesos", 3600, 0, 0, 1001, "some-service-host2.marathon.mesos") 92 | dnsProbe.sender() ! SrvResolved("some-service", List(srv1, srv2)) 93 | 94 | val (expectedHostname, expectedHost, expectedPort) = 95 | dnsProbe.expectMsgPF() { 96 | case Dns.Resolve(hostname @ "some-service-host1.marathon.mesos") => (hostname, "1.1.1.1", 1000) 97 | case Dns.Resolve(hostname @ "some-service-host2.marathon.mesos") => (hostname, "1.1.1.2", 1001) 98 | } 99 | dnsProbe.sender() ! Dns.Resolved(expectedHostname, List(InetAddress.getByName(expectedHost))) 100 | dnsProbe.expectNoMsg(500.millis) 101 | 102 | requestor.expectMsg(Addresses(Seq(ServiceAddress("tcp", expectedHostname, expectedHost, expectedPort)))) 103 | } 104 | 105 | "Fail to resolve a service due to no srv resolution" in { 106 | val dnsProbe = TestProbe() 107 | val serviceLocator = system.actorOf(Props(new TestServiceLocator(dnsProbe))) 108 | 109 | val requestor = TestProbe() 110 | 111 | requestor.send(serviceLocator, ServiceLocator.GetAddress("_some-service._tcp.marathon.mesos")) 112 | 113 | dnsProbe.expectMsg(Dns.Resolve("_some-service._tcp.marathon.mesos")) 114 | dnsProbe.sender() ! SrvResolved("some-service", List.empty) 115 | 116 | dnsProbe.expectNoMsg(500.millis) 117 | 118 | requestor.expectMsg(Addresses(Seq.empty)) 119 | } 120 | 121 | "Fail to resolve a service due to no target resolution" in { 122 | val dnsProbe = TestProbe() 123 | val serviceLocator = system.actorOf(Props(new TestServiceLocator(dnsProbe))) 124 | 125 | val requestor = TestProbe() 126 | 127 | requestor.send(serviceLocator, ServiceLocator.GetAddress("_some-service._tcp.marathon.mesos")) 128 | 129 | dnsProbe.expectMsg(Dns.Resolve("_some-service._tcp.marathon.mesos")) 130 | val srv1 = SRVRecord("_some-service._tcp.marathon.mesos", 3600, 0, 0, 1000, "some-service-host1.marathon.mesos") 131 | dnsProbe.sender() ! SrvResolved("some-service", List(srv1)) 132 | 133 | dnsProbe.expectMsg(Dns.Resolve("some-service-host1.marathon.mesos")) 134 | dnsProbe.sender() ! Dns.Resolved("some-service-host1.marathon.mesos", List.empty) 135 | dnsProbe.expectNoMsg(500.millis) 136 | 137 | requestor.expectMsg(Addresses(Seq.empty)) 138 | } 139 | 140 | "Fail to resolve a service to 1 address given an error on the SRV resolution" in { 141 | val dnsProbe = TestProbe() 142 | val serviceLocator = system.actorOf(Props(new TestServiceLocator(dnsProbe))) 143 | 144 | val requestor = TestProbe() 145 | 146 | requestor.send(serviceLocator, ServiceLocator.GetAddress("_some-service._tcp.marathon.mesos")) 147 | 148 | dnsProbe.expectMsg(Dns.Resolve("_some-service._tcp.marathon.mesos")) 149 | dnsProbe.sender() ! "some stupid reply" 150 | dnsProbe.expectNoMsg(500.millis) 151 | 152 | requestor.expectMsg(Addresses(List.empty)) 153 | } 154 | 155 | "Fail to resolve a service to 1 address given an error on the DNS resolution of a successful SRV lookup" in { 156 | val dnsProbe = TestProbe() 157 | val serviceLocator = system.actorOf(Props(new TestServiceLocator(dnsProbe))) 158 | 159 | val requestor = TestProbe() 160 | 161 | requestor.send(serviceLocator, ServiceLocator.GetAddress("_some-service._tcp.marathon.mesos")) 162 | 163 | dnsProbe.expectMsg(Dns.Resolve("_some-service._tcp.marathon.mesos")) 164 | val srv1 = SRVRecord("_some-service._tcp.marathon.mesos", 3600, 0, 0, 1000, "some-service-host1.marathon.mesos") 165 | dnsProbe.sender() ! SrvResolved("some-service", List(srv1)) 166 | 167 | dnsProbe.expectMsg(Dns.Resolve("some-service-host1.marathon.mesos")) 168 | dnsProbe.sender() ! "some stupid reply" 169 | dnsProbe.expectNoMsg(500.millis) 170 | 171 | requestor.expectMsg(Addresses(List.empty)) 172 | } 173 | 174 | "Translate SRV records after resolving" in { 175 | val dnsProbe = TestProbe() 176 | val serviceLocator = system.actorOf(Props(new TestServiceLocator(dnsProbe))) 177 | 178 | val requestor = TestProbe() 179 | 180 | requestor.send(serviceLocator, ServiceLocator.GetAddress("_test-srv-translator._tcp.marathon.mesos")) 181 | 182 | dnsProbe.expectMsg(Dns.Resolve("_test-srv-translator._tcp.marathon.mesos")) 183 | val srv = SRVRecord("_test-srv-translator._tcp.marathon.mesos", 3600, 0, 0, 1000, "some-service-host.marathon.mesos") 184 | dnsProbe.reply(SrvResolved("some-service", List(srv))) 185 | 186 | dnsProbe.expectMsgPF() { 187 | case Dns.Resolve("some-service-host.marathon.mesos") => 188 | } 189 | 190 | dnsProbe.reply(Dns.Resolved("some-service-host.marathon.mesos", List(InetAddress.getByName("127.0.0.1")))) 191 | dnsProbe.expectNoMsg(500.millis) 192 | 193 | requestor.expectMsg(Addresses(Seq(ServiceAddress("http", "some-service-host.marathon.mesos", "127.0.0.1", 1000)))) 194 | } 195 | } 196 | 197 | override protected def afterAll(): Unit = { 198 | system.terminate() 199 | super.afterAll() 200 | } 201 | } 202 | 203 | class ServiceLocatorStaticSpec extends WordSpec with Matchers { 204 | 205 | "matching" should { 206 | "mesos replace all" in { 207 | val nameTranslators = Seq("^.*$".r -> "_$0._tcp.marathon.mesos") 208 | val name = "some-service" 209 | val expected = Some("_some-service._tcp.marathon.mesos") 210 | ServiceLocator.matchTranslation(name, nameTranslators) should be(expected) 211 | } 212 | 213 | "k8s replace all" in { 214 | val nameTranslators = Seq("(.*)-(.*)-(.*)-(.*)".r -> "_$3._$4.$2.$1.svc.cluster.local") 215 | val name = "customers-cassandra-native-tcp" 216 | val expected = Some("_native._tcp.cassandra.customers.svc.cluster.local") 217 | ServiceLocator.matchTranslation(name, nameTranslators) should be(expected) 218 | } 219 | 220 | "k8s replace all of the second translator" in { 221 | val nameTranslators = 222 | Seq( 223 | "(.*)-(.*)-(.*)-(.*)".r -> "_$3._$4.$2.$1.svc.cluster.local", 224 | "(.*)-(.*)-(.*)".r -> "_$3._udp.$2.$1.svc.cluster.local") 225 | val name = "customers-cassandra-native" 226 | val expected = Some("_native._udp.cassandra.customers.svc.cluster.local") 227 | ServiceLocator.matchTranslation(name, nameTranslators) should be(expected) 228 | } 229 | 230 | "k8s not find a match in any of the translators" in { 231 | val nameTranslators = 232 | Seq( 233 | "(.*)-(.*)-(.*)-(.*)".r -> "_$3._$4.$2.$1.svc.cluster.local", 234 | "(.*)-(.*)-(.*)".r -> "_$3._udp.$2.$1.svc.cluster.local") 235 | val name = "cannot be matched" 236 | val expected = None 237 | ServiceLocator.matchTranslation(name, nameTranslators) should be(expected) 238 | } 239 | } 240 | 241 | "extracting a protocol" should { 242 | "work given a properly formatted string" in { 243 | ServiceLocator.protocolFromName("_native._udp.cassandra.customers.svc.cluster.local") should be("udp") 244 | } 245 | 246 | "still work given an improperly formatted string" in { 247 | ServiceLocator.protocolFromName("native.udp.cassandra.customers.svc.cluster.local") should be("dp") 248 | } 249 | } 250 | 251 | } 252 | --------------------------------------------------------------------------------