diff --git a/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/annotations/Annotations.scala b/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/annotations/Annotations.scala index d4143398..072443a7 100644 --- a/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/annotations/Annotations.scala +++ b/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/annotations/Annotations.scala @@ -37,6 +37,7 @@ case class Annotations( cpu: Option[Double], endpoints: Map[String, Endpoint], managementEndpointName: Option[String], + remotingEndpointName: Option[String], secrets: Seq[Secret], annotations: Seq[Annotation] = Seq.empty, privileged: Boolean, @@ -45,6 +46,18 @@ case class Annotations( modules: Set[String], akkaClusterBootstrapSystemName: Option[String]) { + def headlessEndpoints: Map[String, Endpoint] = + endpoints filterKeys { (k: String) => + (k.some === managementEndpointName) || + (k.some === remotingEndpointName) + } + + def publicEndpoints: Map[String, Endpoint] = + endpoints filterKeys { (k: String) => + !((k.some === managementEndpointName) || + (k.some === remotingEndpointName)) + } + def applicationValidation(application: Option[String]): ValidationNel[String, Option[Seq[String]]] = application match { case None => @@ -118,6 +131,7 @@ object Annotations extends LazyLogging { cpu = args.cpu.orElse(cpu(labels)), endpoints = endpoints(selectArrayWithIndex(labels, ns("endpoints")), applicationVersion), managementEndpointName = managementEndpointName(labels), + remotingEndpointName = remotingEndpointName(labels), secrets = secrets(selectArray(labels, ns("secrets"))), annotations = annotations(selectArray(labels, ns("annotations"))), privileged = privileged(labels), @@ -217,6 +231,10 @@ object Annotations extends LazyLogging { labels .get(ns("management-endpoint")) + private[annotations] def remotingEndpointName(labels: Map[String, String]): Option[String] = + labels + .get(ns("remoting-endpoint")) + private[annotations] def endpoints(endpoints: Seq[(Int, Map[String, String])], version: Option[String]): Map[String, Endpoint] = endpoints.flatMap(v => endpoint(v._2, v._1, version)).toMap diff --git a/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/argparse/CommandArgs.scala b/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/argparse/CommandArgs.scala index 0902d660..5d53167f 100644 --- a/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/argparse/CommandArgs.scala +++ b/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/argparse/CommandArgs.scala @@ -60,6 +60,20 @@ case object CanaryDeploymentType extends DeploymentType case object BlueGreenDeploymentType extends DeploymentType case object RollingDeploymentType extends DeploymentType +/** + * Represents the discovery method during Akka Boostrap on Kubernetes. + */ +sealed trait DiscoveryMethod +object DiscoveryMethod { + case object KubernetesApi extends DiscoveryMethod { + override def toString: String = "kubernetes-api" + } + case object AkkaDns extends DiscoveryMethod { + override def toString = "akka-dns" + } + def all = Seq(AkkaDns, KubernetesApi) +} + /** * Represents the input argument for `generate-deployment` command. */ @@ -68,6 +82,7 @@ case class GenerateDeploymentArgs( akkaClusterJoinExisting: Boolean = false, akkaClusterSkipValidation: Boolean = false, deploymentType: DeploymentType = CanaryDeploymentType, + discoveryMethod: DiscoveryMethod = DiscoveryMethod.AkkaDns, dockerImages: Seq[String] = Seq.empty, name: Option[String] = None, version: Option[String] = None, diff --git a/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/argparse/InputArgs.scala b/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/argparse/InputArgs.scala index 1929a93e..f6767bd5 100644 --- a/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/argparse/InputArgs.scala +++ b/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/argparse/InputArgs.scala @@ -37,6 +37,14 @@ object InputArgs { throw new IllegalArgumentException(s"Invalid deployment type $v. Available: ${DeploymentType.All.mkString(", ")}") } + implicit val discoveryMethodRead: scopt.Read[DiscoveryMethod] = + scopt.Read.reads { + case v if v.toLowerCase == DiscoveryMethod.AkkaDns.toString => DiscoveryMethod.AkkaDns + case v if v.toLowerCase == DiscoveryMethod.KubernetesApi.toString => DiscoveryMethod.KubernetesApi + case v => + throw new IllegalArgumentException(s"Invalid discovery method $v. Available: ${DiscoveryMethod.all.mkString(", ")}") + } + implicit val logLevelsRead: scopt.Read[LogLevel] = scopt.Read.reads { case v if v.toLowerCase == "error" => LogLevel.ERROR @@ -122,6 +130,11 @@ object InputArgs { .optional() .action(GenerateDeploymentArgs.set((t, args) => args.copy(deploymentType = t))), + opt[DiscoveryMethod]("discovery-method") + .text(s"Sets the discovery method. Default: ${DiscoveryMethod.AkkaDns}; Available: ${DiscoveryMethod.all.mkString(", ")}") + .optional() + .action(GenerateDeploymentArgs.set((t, args) => args.copy(discoveryMethod = t))), + opt[String]("env") /* note: this argument will apply for other targets */ .text("Sets an environment variable. Format: NAME=value") .minOccurs(0) diff --git a/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/Deployment.scala b/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/Deployment.scala index 1489847b..efeee4bb 100644 --- a/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/Deployment.scala +++ b/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/Deployment.scala @@ -47,6 +47,7 @@ object Deployment { noOfReplicas: Int, externalServices: Map[String, Seq[String]], deploymentType: DeploymentType, + discoveryMethod: DiscoveryMethod, jsonTransform: JsonTransform, akkaClusterJoinExisting: Boolean): ValidationNel[String, Deployment] = @@ -57,9 +58,17 @@ object Deployment { val appName = serviceName(rawAppName) val appNameVersion = serviceName(s"$appName$VersionSeparator$version") + val serviceResourceName = + deploymentType match { + case CanaryDeploymentType => appName + case BlueGreenDeploymentType => appNameVersion + case RollingDeploymentType => appName + } + val labels = Map( - "appName" -> appName, - "appNameVersion" -> appNameVersion) ++ annotations.akkaClusterBootstrapSystemName.fold(Map.empty[String, String])(system => Map("actorSystemName" -> system)) + "app" -> appName, + "appNameVersion" -> appNameVersion) ++ annotations.akkaClusterBootstrapSystemName.fold(Map( + serviceNameLabel -> serviceResourceName))(system => Map(serviceNameLabel -> system)) val podTemplate = PodTemplate.generate( @@ -72,6 +81,7 @@ object Deployment { RestartPolicy.Always, // The only valid RestartPolicy for Deployment externalServices, deploymentType, + discoveryMethod, akkaClusterJoinExisting, applicationArgs, appName, @@ -87,7 +97,7 @@ object Deployment { (appNameVersion, Json("appNameVersion" -> appNameVersion.asJson)) case RollingDeploymentType => - (appName, Json("appName" -> appName.asJson)) + (appName, Json("app" -> appName.asJson)) } Deployment( diff --git a/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/Job.scala b/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/Job.scala index e3ac94e1..28b9f20f 100644 --- a/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/Job.scala +++ b/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/Job.scala @@ -47,6 +47,7 @@ object Job { noOfReplicas: Int, externalServices: Map[String, Seq[String]], deploymentType: DeploymentType, + discoveryMethod: DiscoveryMethod, jsonTransform: JsonTransform, akkaClusterJoinExisting: Boolean): ValidationNel[String, Job] = @@ -56,10 +57,17 @@ object Job { |@| restartPolicyValidation(restartPolicy)) { (applicationArgs, rawAppName, version, restartPolicy) => val appName = serviceName(rawAppName) val appNameVersion = serviceName(s"$appName$VersionSeparator$version") + val serviceResourceName = + deploymentType match { + case CanaryDeploymentType => appName + case BlueGreenDeploymentType => appNameVersion + case RollingDeploymentType => appName + } val labels = Map( - "appName" -> appName, - "appNameVersion" -> appNameVersion) ++ annotations.akkaClusterBootstrapSystemName.fold(Map.empty[String, String])(system => Map("actorSystemName" -> system)) + "app" -> appName, + "appNameVersion" -> appNameVersion) ++ annotations.akkaClusterBootstrapSystemName.fold(Map( + serviceNameLabel -> serviceResourceName))(system => Map(serviceNameLabel -> system)) val podTemplate = PodTemplate.generate( @@ -72,6 +80,7 @@ object Job { if (restartPolicy == RestartPolicy.Default) RestartPolicy.OnFailure else restartPolicy, externalServices, deploymentType, + discoveryMethod, akkaClusterJoinExisting, applicationArgs, appName, diff --git a/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/PodTemplate.scala b/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/PodTemplate.scala index 3635178d..7d22edbe 100644 --- a/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/PodTemplate.scala +++ b/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/PodTemplate.scala @@ -50,7 +50,7 @@ object PodTemplate { /** * Generates pod environment variables specific for RP applications. */ - def envs(annotations: Annotations, serviceResourceName: String, noOfReplicas: Int, externalServices: Map[String, Seq[String]], akkaClusterJoinExisting: Boolean): Map[String, EnvironmentVariable] = + def envs(annotations: Annotations, serviceResourceName: String, noOfReplicas: Int, externalServices: Map[String, Seq[String]], akkaClusterJoinExisting: Boolean, discoveryMethod: DiscoveryMethod): Map[String, EnvironmentVariable] = mergeEnvs( PodEnvs, appNameEnvs(annotations.appName), @@ -58,6 +58,8 @@ object PodTemplate { appTypeEnvs(annotations.appType, annotations.modules), configEnvs(annotations.configResource), akkaClusterEnvs( + annotations.appName, + discoveryMethod, annotations.modules, annotations.namespace, serviceResourceName, @@ -78,6 +80,8 @@ object PodTemplate { }.toMap private[kubernetes] def akkaClusterEnvs( + appName: Option[String], + discoveryMethod: DiscoveryMethod, modules: Set[String], namespace: Option[String], serviceResourceName: String, @@ -90,13 +94,29 @@ object PodTemplate { else Map( "RP_JAVA_OPTS" -> LiteralEnvironmentVariable( - Seq( - s"-Dakka.management.cluster.bootstrap.contact-point-discovery.discovery-method=kubernetes-api", - s"-Dakka.management.cluster.bootstrap.contact-point-discovery.port-name=$managementEndpointName", - s"-Dakka.management.cluster.bootstrap.contact-point-discovery.effective-name=$serviceResourceName", - s"-Dakka.management.cluster.bootstrap.contact-point-discovery.required-contact-point-nr=$noOfReplicas", - akkaClusterBootstrapSystemName.fold("-Dakka.discovery.kubernetes-api.pod-label-selector=appName=%s")(systemName => s"-Dakka.discovery.kubernetes-api.pod-label-selector=actorSystemName=$systemName"), - s"${if (akkaClusterJoinExisting) "-Dakka.management.cluster.bootstrap.form-new-cluster=false" else ""}") + ((discoveryMethod match { + case DiscoveryMethod.KubernetesApi => + List( + s"-Dakka.management.cluster.bootstrap.contact-point-discovery.discovery-method=kubernetes-api", + s"-Dakka.management.cluster.bootstrap.contact-point-discovery.port-name=$managementEndpointName", + // https://github.com/akka/akka-management/blob/v0.20.0/cluster-bootstrap/src/main/resources/reference.conf + akkaClusterBootstrapSystemName match { + case Some(systemName) => s"-Dakka.management.cluster.bootstrap.contact-point-discovery.effective-name=$systemName" + case _ => s"-Dakka.management.cluster.bootstrap.contact-point-discovery.effective-name=$serviceResourceName" + }, + "-Dakka.discovery.kubernetes-api.pod-label-selector=akka.lightbend.com/service-name=%s") + case DiscoveryMethod.AkkaDns => + List( + s"-Dakka.management.cluster.bootstrap.contact-point-discovery.discovery-method=akka-dns", + s"-Dakka.management.cluster.bootstrap.contact-point-discovery.port-name=$managementEndpointName", + appName match { + case Some(name) => s"-Dakka.management.cluster.bootstrap.contact-point-discovery.service-name=$name-internal" + case _ => sys.error("appName was expected") + }) + }) ++ + List( + s"-Dakka.management.cluster.bootstrap.contact-point-discovery.required-contact-point-nr=$noOfReplicas", + s"${if (akkaClusterJoinExisting) "-Dakka.management.cluster.bootstrap.form-new-cluster=false" else ""}")) .filter(_.nonEmpty) .mkString(" ")), "RP_DYN_JAVA_OPTS" -> LiteralEnvironmentVariable( @@ -158,7 +178,7 @@ object PodTemplate { * If the akkaClusterJoinExisting flag is provided, these labels are removed from the pod template so that * it isn't used for bootstrap. */ - private[kubernetes] val PodDiscoveryLabels = Set("appName", "actorSystemName") + private[kubernetes] val PodDiscoveryLabels = Set("app", "actorSystemName") /** * Represents possible values for imagePullPolicy field within the Kubernetes pod template. @@ -274,6 +294,7 @@ object PodTemplate { restartPolicy: RestartPolicy.Value, externalServices: Map[String, Seq[String]], deploymentType: DeploymentType, + discoveryMethod: DiscoveryMethod, akkaClusterJoinExisting: Boolean, applicationArgs: Option[Seq[String]], appName: String, @@ -368,7 +389,7 @@ object PodTemplate { "imagePullPolicy" -> imagePullPolicy.asJson, "env" -> RpEnvironmentVariables.mergeEnvs( annotations.environmentVariables ++ - RpEnvironmentVariables.envs(annotations, serviceResourceName, noOfReplicas, externalServices, akkaClusterJoinExisting)).asJson, + RpEnvironmentVariables.envs(annotations, serviceResourceName, noOfReplicas, externalServices, akkaClusterJoinExisting, discoveryMethod)).asJson, "ports" -> annotations.endpoints.asJson, "volumeMounts" -> secretNames .map { diff --git a/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/Service.scala b/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/Service.scala index c2369c71..b04b4137 100644 --- a/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/Service.scala +++ b/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/Service.scala @@ -59,43 +59,67 @@ object Service { apiVersion: String, clusterIp: Option[String], deploymentType: DeploymentType, + discoveryMethod: DiscoveryMethod, jsonTransform: JsonTransform, loadBalancerIp: Option[String], - serviceType: Option[String]): ValidationNel[String, Option[Service]] = + serviceType: Option[String]): ValidationNel[String, List[Service]] = (annotations.appNameValidation |@| annotations.versionValidation) { (rawAppName, version) => // FIXME there's a bit of code duplicate in Deployment val appName = serviceName(rawAppName) + val internalAppname = appName + "-internal" val appNameVersion = serviceName(s"$appName${PodTemplate.VersionSeparator}$version") val selector = deploymentType match { - case CanaryDeploymentType => Json("appName" -> appName.asJson) - case RollingDeploymentType => Json("appName" -> appName.asJson) + case CanaryDeploymentType => Json("app" -> appName.asJson) + case RollingDeploymentType => Json("app" -> appName.asJson) case BlueGreenDeploymentType => Json("appNameVersion" -> appNameVersion.asJson) } - if (annotations.endpoints.isEmpty) - None - else - Some( - Service( - appName, - Json( - "apiVersion" -> apiVersion.asJson, - "kind" -> "Service".asJson, - "metadata" -> Json( - "labels" -> Json( - "appName" -> appName.asJson), - "name" -> appName.asJson) - .deepmerge( - annotations.namespace.fold(jEmptyObject)(ns => Json("namespace" -> serviceName(ns).asJson))), - "spec" -> Json( - "ports" -> annotations.endpoints.asJson, - "selector" -> selector) - .deepmerge(clusterIp.fold(jEmptyObject)(cIp => Json("clusterIP" -> jString(cIp)))) - .deepmerge(serviceType.fold(jEmptyObject)(svcType => Json("type" -> jString(svcType)))) - .deepmerge(loadBalancerIp.fold(jEmptyObject)(lbIp => Json("loadBalancerIP" -> jString(lbIp))))), - jsonTransform)) + def svc(endpoints: Map[String, Endpoint]) = Service( + appName, + Json( + "apiVersion" -> apiVersion.asJson, + "kind" -> "Service".asJson, + "metadata" -> Json( + "labels" -> Json( + "app" -> appName.asJson), + "name" -> appName.asJson) + .deepmerge( + annotations.namespace.fold(jEmptyObject)(ns => Json("namespace" -> serviceName(ns).asJson))), + "spec" -> Json( + "ports" -> endpoints.asJson, + "selector" -> selector) + .deepmerge(clusterIp.fold(jEmptyObject)(cIp => Json("clusterIP" -> jString(cIp)))) + .deepmerge(serviceType.fold(jEmptyObject)(svcType => Json("type" -> jString(svcType)))) + .deepmerge(loadBalancerIp.fold(jEmptyObject)(lbIp => Json("loadBalancerIP" -> jString(lbIp))))), + jsonTransform) + + def headlessService(endpoints: Map[String, Endpoint]) = Service( + internalAppname, + Json( + "apiVersion" -> apiVersion.asJson, + "kind" -> "Service".asJson, + "metadata" -> Json( + "labels" -> Json( + "app" -> appName.asJson), + "annotations" -> Json( + "service.alpha.kubernetes.io/tolerate-unready-endpoints" -> jString("true")), + "name" -> internalAppname.asJson) + .deepmerge( + annotations.namespace.fold(jEmptyObject)(ns => Json("namespace" -> serviceName(ns).asJson))), + "spec" -> Json( + "ports" -> endpoints.asJson, + "selector" -> selector, + "clusterIP" -> jString("None"), + "publishNotReadyAddresses" -> jTrue)), + jsonTransform) + + if (annotations.endpoints.isEmpty) List() + else if (discoveryMethod == DiscoveryMethod.AkkaDns) List( + headlessService(annotations.headlessEndpoints), + svc(annotations.publicEndpoints)) + else List(svc(annotations.endpoints)) } } diff --git a/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/package.scala b/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/package.scala index 9885f66b..d373cbfa 100644 --- a/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/package.scala +++ b/cli/shared/src/main/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/package.scala @@ -34,6 +34,7 @@ import slogging.LazyLogging import Scalaz._ package object kubernetes extends LazyLogging { + private[reactivecli] val serviceNameLabel = "akka.lightbend.com/service-name" private[reactivecli] val LivenessInitialDelaySeconds = 60 private[reactivecli] val StatusPeriodSeconds = 10 @@ -90,6 +91,7 @@ package object kubernetes extends LazyLogging { kubernetesArgs.podControllerArgs.numberOfReplicas, generateDeploymentArgs.externalServices, generateDeploymentArgs.deploymentType, + generateDeploymentArgs.discoveryMethod, kubernetesArgs.transformPodControllers.fold(JsonTransform.noop)(JsonTransform.jq), generateDeploymentArgs.akkaClusterJoinExisting) @@ -104,6 +106,7 @@ package object kubernetes extends LazyLogging { kubernetesArgs.podControllerArgs.numberOfReplicas, generateDeploymentArgs.externalServices, generateDeploymentArgs.deploymentType, + generateDeploymentArgs.discoveryMethod, kubernetesArgs.transformPodControllers.fold(JsonTransform.noop)(JsonTransform.jq), generateDeploymentArgs.akkaClusterJoinExisting) } @@ -113,6 +116,7 @@ package object kubernetes extends LazyLogging { serviceApiVersion, kubernetesArgs.serviceArgs.clusterIp, generateDeploymentArgs.deploymentType, + generateDeploymentArgs.discoveryMethod, kubernetesArgs.transformServices.fold(JsonTransform.noop)(JsonTransform.jq), kubernetesArgs.serviceArgs.loadBalancerIp, kubernetesArgs.serviceArgs.serviceType) diff --git a/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/annotations/AnnotationsTest.scala b/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/annotations/AnnotationsTest.scala index f6a8e291..4b3130a0 100644 --- a/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/annotations/AnnotationsTest.scala +++ b/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/annotations/AnnotationsTest.scala @@ -121,6 +121,7 @@ object AnnotationsTest extends TestSuite { cpu = None, endpoints = Map.empty, managementEndpointName = None, + remotingEndpointName = None, secrets = Seq.empty, privileged = false, environmentVariables = Map.empty, @@ -202,6 +203,7 @@ object AnnotationsTest extends TestSuite { "ep2" -> TcpEndpoint(1, "ep2", 1234), "ep3" -> UdpEndpoint(2, "ep3", 1234)), managementEndpointName = Some("management"), + remotingEndpointName = Some("remoting"), secrets = Seq.empty, annotations = Vector( Annotation("annotationKey0", "annotationValue0"), @@ -245,6 +247,7 @@ object AnnotationsTest extends TestSuite { cpu = Some(0.5), endpoints = Map.empty, managementEndpointName = None, + remotingEndpointName = None, secrets = Seq.empty, privileged = false, environmentVariables = Map( @@ -272,6 +275,7 @@ object AnnotationsTest extends TestSuite { cpu = None, endpoints = Map.empty, managementEndpointName = None, + remotingEndpointName = None, secrets = Seq.empty, privileged = false, environmentVariables = Map.empty, @@ -295,6 +299,7 @@ object AnnotationsTest extends TestSuite { cpu = None, endpoints = Map.empty, managementEndpointName = None, + remotingEndpointName = None, secrets = Seq.empty, privileged = false, environmentVariables = Map.empty, @@ -318,6 +323,7 @@ object AnnotationsTest extends TestSuite { cpu = None, endpoints = Map.empty, managementEndpointName = None, + remotingEndpointName = None, secrets = Seq.empty, privileged = false, environmentVariables = Map.empty, @@ -347,6 +353,7 @@ object AnnotationsTest extends TestSuite { endpoints = Map( "ep2" -> TcpEndpoint(1, "ep2", 1234)), managementEndpointName = None, + remotingEndpointName = None, secrets = Seq.empty, privileged = false, environmentVariables = Map.empty, @@ -374,6 +381,7 @@ object AnnotationsTest extends TestSuite { endpoints = Map( "ep2" -> TcpEndpoint(1, "ep2", 1234)), managementEndpointName = None, + remotingEndpointName = None, secrets = Seq.empty, privileged = false, environmentVariables = Map.empty, diff --git a/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/DeploymentJsonTest.scala b/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/DeploymentJsonTest.scala index c287936d..1e1b4672 100644 --- a/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/DeploymentJsonTest.scala +++ b/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/DeploymentJsonTest.scala @@ -47,6 +47,7 @@ object DeploymentJsonTest extends TestSuite { cpu = Some(0.5D), endpoints = endpoints, managementEndpointName = None, + remotingEndpointName = None, secrets = Seq(Secret("acme.co", "my-secret")), annotations = Seq( Annotation("annotationKey0", "annotationValue0"), @@ -66,7 +67,7 @@ object DeploymentJsonTest extends TestSuite { "deploymentType" - { "Canary" - { Deployment - .generate(annotations, "apps/v1beta2", None, imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, noOfReplicas = 1, Map.empty, CanaryDeploymentType, JsonTransform.noop, false) + .generate(annotations, "apps/v1beta2", None, imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, noOfReplicas = 1, Map.empty, CanaryDeploymentType, DiscoveryMethod.AkkaDns, JsonTransform.noop, false) .toOption .get .payload @@ -79,7 +80,7 @@ object DeploymentJsonTest extends TestSuite { "BlueGreen" - { Deployment - .generate(annotations, "v1", None, imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, noOfReplicas = 1, Map.empty, BlueGreenDeploymentType, JsonTransform.noop, false) + .generate(annotations, "v1", None, imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, noOfReplicas = 1, Map.empty, BlueGreenDeploymentType, DiscoveryMethod.AkkaDns, JsonTransform.noop, false) .toOption .get .payload @@ -92,7 +93,7 @@ object DeploymentJsonTest extends TestSuite { "Rolling" - { Deployment - .generate(annotations, "v1", None, imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, noOfReplicas = 1, Map.empty, RollingDeploymentType, JsonTransform.noop, false) + .generate(annotations, "v1", None, imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, noOfReplicas = 1, Map.empty, RollingDeploymentType, DiscoveryMethod.AkkaDns, JsonTransform.noop, false) .toOption .get .payload @@ -113,8 +114,9 @@ object DeploymentJsonTest extends TestSuite { | "metadata": { | "name": "friendimpl-v3-2-1-snapshot", | "labels": { - | "appName": "friendimpl", - | "appNameVersion": "friendimpl-v3-2-1-snapshot" + | "app": "friendimpl", + | "appNameVersion": "friendimpl-v3-2-1-snapshot", + | "akka.lightbend.com/service-name": "friendimpl" | }, | "namespace": "chirper" | }, @@ -128,8 +130,9 @@ object DeploymentJsonTest extends TestSuite { | "template": { | "metadata": { | "labels": { - | "appName": "friendimpl", - | "appNameVersion": "friendimpl-v3-2-1-snapshot" + | "app": "friendimpl", + | "appNameVersion": "friendimpl-v3-2-1-snapshot", + | "akka.lightbend.com/service-name": "friendimpl" | }, | "annotations": { | "annotationKey0": "annotationValue0", @@ -240,7 +243,8 @@ object DeploymentJsonTest extends TestSuite { """.stripMargin.parse.right.get val result = Deployment.generate(annotations, "apps/v1beta2", None, imageName, - PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, noOfReplicas = 1, Map.empty, CanaryDeploymentType, JsonTransform.noop, false).toOption.get + PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, noOfReplicas = 1, Map.empty, CanaryDeploymentType, + DiscoveryMethod.AkkaDns, JsonTransform.noop, false).toOption.get if (result.json != expectedJson) { println(s"deployment K8 JSON:\n" + PrettyParams.spaces2.copy(colonLeft = "").pretty(result.json)) @@ -248,6 +252,67 @@ object DeploymentJsonTest extends TestSuite { assert(result == Deployment("friendimpl-v3-2-1-snapshot", expectedJson, JsonTransform.noop)) } + "should generate JAVA_OPTS override for Akka Cluster Boostrapping" - { + val result = Deployment.generate(annotations.copy( + modules = Set("akka-management", "status", "akka-cluster-bootstrapping"), + managementEndpointName = Some("management")), "apps/v1beta2", None, imageName, + PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, noOfReplicas = 1, Map.empty, + CanaryDeploymentType, DiscoveryMethod.KubernetesApi, JsonTransform.noop, false).toOption.get + + result + .payload + .map({ j => + val javaOpts = (j.hcursor --\ "spec" --\ "template" --\ "spec" --\ "containers") + .downArray + .first + .downField("env") + .downN(4) + .downField("value") + .focus + .get + .string + .get + .split(' ') + .toSet + assert(javaOpts.contains("-Dakka.management.cluster.bootstrap.contact-point-discovery.discovery-method=kubernetes-api") && + javaOpts.contains("-Dakka.management.cluster.bootstrap.contact-point-discovery.port-name=management") && + javaOpts.contains("-Dakka.management.cluster.bootstrap.contact-point-discovery.effective-name=friendimpl") && + javaOpts.contains("-Dakka.discovery.kubernetes-api.pod-label-selector=akka.lightbend.com/service-name=%s") + ) + }) + } + + "should generate JAVA_OPTS override for Akka Cluster Boostrapping using DNS" - { + val result = Deployment.generate(annotations.copy( + modules = Set("akka-management", "status", "akka-cluster-bootstrapping"), + managementEndpointName = Some("management")), "apps/v1beta2", None, imageName, + PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, noOfReplicas = 1, Map.empty, + CanaryDeploymentType, DiscoveryMethod.AkkaDns, JsonTransform.noop, false).toOption.get + + result + .payload + .map({ j => + val javaOpts = (j.hcursor --\ "spec" --\ "template" --\ "spec" --\ "containers") + .downArray + .first + .downField("env") + .downN(4) + .downField("value") + .focus + .get + .string + .get + .split(' ') + .toSet + assert(javaOpts.contains("-Dakka.management.cluster.bootstrap.contact-point-discovery.discovery-method=akka-dns") && + javaOpts.contains("-Dakka.management.cluster.bootstrap.contact-point-discovery.service-name=friendimpl-internal") && + javaOpts.contains("-Dakka.management.cluster.bootstrap.contact-point-discovery.port-name=management") && + !javaOpts.contains("-Dakka.management.cluster.bootstrap.contact-point-discovery.effective-name=friendimpl") && + !javaOpts.contains("-Dakka.discovery.kubernetes-api.pod-label-selector=akka.lightbend.com/service-name=%s") + ) + }) + } + "should generate application health check given status module" - { val expectedJson = """ @@ -257,8 +322,9 @@ object DeploymentJsonTest extends TestSuite { | "metadata": { | "name": "friendimpl-v3-2-1-snapshot", | "labels": { - | "appName": "friendimpl", - | "appNameVersion": "friendimpl-v3-2-1-snapshot" + | "app": "friendimpl", + | "appNameVersion": "friendimpl-v3-2-1-snapshot", + | "akka.lightbend.com/service-name": "friendimpl" | }, | "namespace": "chirper" | }, @@ -272,8 +338,9 @@ object DeploymentJsonTest extends TestSuite { | "template": { | "metadata": { | "labels": { - | "appName": "friendimpl", - | "appNameVersion": "friendimpl-v3-2-1-snapshot" + | "app": "friendimpl", + | "appNameVersion": "friendimpl-v3-2-1-snapshot", + | "akka.lightbend.com/service-name": "friendimpl" | }, | "annotations": { | "annotationKey0": "annotationValue0", @@ -351,7 +418,7 @@ object DeploymentJsonTest extends TestSuite { | }, | { | "name": "RP_JAVA_OPTS", - | "value": "-Dconfig.resource=my-config.conf -Dakka.management.cluster.bootstrap.contact-point-discovery.discovery-method=kubernetes-api -Dakka.management.cluster.bootstrap.contact-point-discovery.port-name=management -Dakka.management.cluster.bootstrap.contact-point-discovery.effective-name=friendimpl -Dakka.management.cluster.bootstrap.contact-point-discovery.required-contact-point-nr=1 -Dakka.discovery.kubernetes-api.pod-label-selector=appName=%s" + | "value": "-Dconfig.resource=my-config.conf -Dakka.management.cluster.bootstrap.contact-point-discovery.discovery-method=kubernetes-api -Dakka.management.cluster.bootstrap.contact-point-discovery.port-name=management -Dakka.management.cluster.bootstrap.contact-point-discovery.effective-name=friendimpl -Dakka.discovery.kubernetes-api.pod-label-selector=akka.lightbend.com/service-name=%s -Dakka.management.cluster.bootstrap.contact-point-discovery.required-contact-point-nr=1" | }, | { | "name": "RP_KUBERNETES_POD_IP", @@ -408,7 +475,8 @@ object DeploymentJsonTest extends TestSuite { val result = Deployment.generate(annotations.copy( modules = Set("akka-management", "status", "akka-cluster-bootstrapping"), managementEndpointName = Some("management")), "apps/v1beta2", None, imageName, - PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, noOfReplicas = 1, Map.empty, CanaryDeploymentType, JsonTransform.noop, false).toOption.get + PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, noOfReplicas = 1, Map.empty, + CanaryDeploymentType, DiscoveryMethod.KubernetesApi, JsonTransform.noop, false).toOption.get if (result.json != expectedJson) { println(s"deployment K8 JSON:\n" + PrettyParams.spaces2.copy(colonLeft = "").pretty(result.json)) } @@ -417,16 +485,16 @@ object DeploymentJsonTest extends TestSuite { "should fail if application name is not defined" - { val invalid = annotations.copy(appName = None) - assert(Deployment.generate(invalid, "apps/v1beta2", None, imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, 1, Map.empty, CanaryDeploymentType, JsonTransform.noop, false).toOption.isEmpty) + assert(Deployment.generate(invalid, "apps/v1beta2", None, imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, 1, Map.empty, CanaryDeploymentType, DiscoveryMethod.AkkaDns, JsonTransform.noop, false).toOption.isEmpty) } "should fail when restart policy is wrong" - { - assert(Deployment.generate(annotations, "apps/v1beta2", None, imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Never, 1, Map.empty, CanaryDeploymentType, JsonTransform.noop, false).toOption.isEmpty) + assert(Deployment.generate(annotations, "apps/v1beta2", None, imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Never, 1, Map.empty, CanaryDeploymentType, DiscoveryMethod.AkkaDns, JsonTransform.noop, false).toOption.isEmpty) } "jq" - { Deployment - .generate(annotations, "apps/v1beta2", None, imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, 1, Map.empty, CanaryDeploymentType, JsonTransform.jq(JsonTransformExpression(".jqTest = \"test\"")), false) + .generate(annotations, "apps/v1beta2", None, imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, 1, Map.empty, CanaryDeploymentType, DiscoveryMethod.AkkaDns, JsonTransform.jq(JsonTransformExpression(".jqTest = \"test\"")), false) .toOption .get .payload @@ -436,7 +504,7 @@ object DeploymentJsonTest extends TestSuite { "applications" - { "should select default given no application" - { Deployment - .generate(annotations.copy(applications = Vector("test" -> Vector("arg1", "arg2"), "default" -> Vector("def1"))), "apps/v1beta2", None, imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, 1, Map.empty, CanaryDeploymentType, JsonTransform.noop, false) + .generate(annotations.copy(applications = Vector("test" -> Vector("arg1", "arg2"), "default" -> Vector("def1"))), "apps/v1beta2", None, imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, 1, Map.empty, CanaryDeploymentType, DiscoveryMethod.AkkaDns, JsonTransform.noop, false) .toOption .get .payload @@ -454,7 +522,7 @@ object DeploymentJsonTest extends TestSuite { "should select requested application given an application" - { Deployment - .generate(annotations.copy(applications = Vector("test" -> Vector("arg1", "arg2"), "default" -> Vector("def1"))), "apps/v1beta2", Some("test"), imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, 1, Map.empty, CanaryDeploymentType, JsonTransform.noop, false) + .generate(annotations.copy(applications = Vector("test" -> Vector("arg1", "arg2"), "default" -> Vector("def1"))), "apps/v1beta2", Some("test"), imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, 1, Map.empty, CanaryDeploymentType, DiscoveryMethod.AkkaDns, JsonTransform.noop, false) .toOption .get .payload @@ -488,7 +556,7 @@ object DeploymentJsonTest extends TestSuite { val generatedJson = Deployment - .generate(annotations, "apps/v1beta2", None, imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, 1, Map.empty, CanaryDeploymentType, JsonTransform.noop, false) + .generate(annotations, "apps/v1beta2", None, imageName, PodTemplate.ImagePullPolicy.Never, PodTemplate.RestartPolicy.Default, 1, Map.empty, CanaryDeploymentType, DiscoveryMethod.AkkaDns, JsonTransform.noop, false) .toOption .get .payload diff --git a/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/IngressJsonTest.scala b/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/IngressJsonTest.scala index 73040f49..08fd46fd 100644 --- a/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/IngressJsonTest.scala +++ b/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/IngressJsonTest.scala @@ -46,6 +46,7 @@ object IngressJsonTest extends TestSuite { HttpIngress(Seq(80, 443), Seq("hello.com"), Seq.empty), HttpIngress(Seq(80, 443), Seq("hello.com", "world.io"), Seq(urlOne, urlTwo))))), managementEndpointName = None, + remotingEndpointName = None, secrets = Seq.empty, privileged = true, environmentVariables = Map( diff --git a/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/JobJsonTest.scala b/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/JobJsonTest.scala index 01f8d326..7b95ad59 100644 --- a/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/JobJsonTest.scala +++ b/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/JobJsonTest.scala @@ -18,7 +18,7 @@ package com.lightbend.rp.reactivecli.runtime.kubernetes import argonaut._ import com.lightbend.rp.reactivecli.annotations.{ Annotations, LiteralEnvironmentVariable, Secret } -import com.lightbend.rp.reactivecli.argparse.CanaryDeploymentType +import com.lightbend.rp.reactivecli.argparse.{ CanaryDeploymentType, DiscoveryMethod } import com.lightbend.rp.reactivecli.json.JsonTransform import scala.collection.immutable.Seq import utest._ @@ -37,6 +37,7 @@ object JobJsonTest extends TestSuite { cpu = None, endpoints = Map.empty, managementEndpointName = None, + remotingEndpointName = None, secrets = Seq.empty, privileged = false, environmentVariables = Map.empty, @@ -58,6 +59,7 @@ object JobJsonTest extends TestSuite { noOfReplicas = 1, Map.empty, CanaryDeploymentType, + DiscoveryMethod.AkkaDns, JsonTransform.noop, true) @@ -69,13 +71,15 @@ object JobJsonTest extends TestSuite { "metadata" -> jObjectFields( "name" -> jString("friendimpl-v3-2-1-snapshot"), "labels" -> jObjectFields( - "appName" -> jString("friendimpl"), - "appNameVersion" -> jString("friendimpl-v3-2-1-snapshot"))), + "app" -> jString("friendimpl"), + "appNameVersion" -> jString("friendimpl-v3-2-1-snapshot"), + "akka.lightbend.com/service-name" -> jString("friendimpl"))), "spec" -> jObjectFields( "template" -> jObjectFields( "metadata" -> jObjectFields( "labels" -> jObjectFields( - "appNameVersion" -> jString("friendimpl-v3-2-1-snapshot"))), + "appNameVersion" -> jString("friendimpl-v3-2-1-snapshot"), + "akka.lightbend.com/service-name" -> jString("friendimpl"))), "spec" -> jObjectFields( "restartPolicy" -> jString("OnFailure"), "containers" -> jArrayElements( @@ -110,6 +114,7 @@ object JobJsonTest extends TestSuite { noOfReplicas = 1, Map.empty, CanaryDeploymentType, + DiscoveryMethod.AkkaDns, JsonTransform.noop, true) diff --git a/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/KubernetesPackageTest.scala b/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/KubernetesPackageTest.scala index 2a5933f7..86a9c77c 100644 --- a/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/KubernetesPackageTest.scala +++ b/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/KubernetesPackageTest.scala @@ -17,7 +17,7 @@ package com.lightbend.rp.reactivecli.runtime.kubernetes import argonaut._ -import com.lightbend.rp.reactivecli.argparse.GenerateDeploymentArgs +import com.lightbend.rp.reactivecli.argparse.{ GenerateDeploymentArgs, DiscoveryMethod } import com.lightbend.rp.reactivecli.argparse.kubernetes.KubernetesArgs import com.lightbend.rp.reactivecli.concurrent._ import com.lightbend.rp.reactivecli.docker.Config @@ -67,34 +67,43 @@ object KubernetesPackageTest extends TestSuite { "com.lightbend.rp.environment-variables.2.type" -> "kubernetes.fieldRef", "com.lightbend.rp.environment-variables.2.name" -> "testing3", "com.lightbend.rp.environment-variables.2.field-path" -> "metadata.name", - "com.lightbend.rp.endpoints.0.name" -> "ep1", - "com.lightbend.rp.endpoints.0.protocol" -> "http", - "com.lightbend.rp.endpoints.0.version" -> "9", - "com.lightbend.rp.endpoints.0.ingress.0.type" -> "http", - "com.lightbend.rp.endpoints.0.ingress.0.paths.0" -> "/pizza", - "com.lightbend.rp.endpoints.0.some-key" -> "test", - "com.lightbend.rp.endpoints.0.acls.0.some-key" -> "test", - "com.lightbend.rp.endpoints.1.name" -> "ep2", + "com.lightbend.rp.endpoints.0.name" -> "remoting", + "com.lightbend.rp.endpoints.0.protocol" -> "tcp", + "com.lightbend.rp.endpoints.0.port" -> "2552", + "com.lightbend.rp.endpoints.1.name" -> "management", "com.lightbend.rp.endpoints.1.protocol" -> "tcp", - "com.lightbend.rp.endpoints.1.version" -> "1", - "com.lightbend.rp.endpoints.1.port" -> "1234", + "com.lightbend.rp.endpoints.1.port" -> "8558", "com.lightbend.rp.endpoints.2.name" -> "ep3", - "com.lightbend.rp.endpoints.2.protocol" -> "udp", - "com.lightbend.rp.endpoints.2.port" -> "1234")))) + "com.lightbend.rp.endpoints.2.protocol" -> "http", + "com.lightbend.rp.endpoints.2.version" -> "9", + "com.lightbend.rp.endpoints.2.ingress.0.type" -> "http", + "com.lightbend.rp.endpoints.2.ingress.0.paths.0" -> "/pizza", + "com.lightbend.rp.endpoints.2.some-key" -> "test", + "com.lightbend.rp.endpoints.2.acls.0.some-key" -> "test", + "com.lightbend.rp.endpoints.3.name" -> "ep4", + "com.lightbend.rp.endpoints.3.protocol" -> "tcp", + "com.lightbend.rp.endpoints.3.version" -> "1", + "com.lightbend.rp.endpoints.3.port" -> "1234", + "com.lightbend.rp.endpoints.4.name" -> "ep5", + "com.lightbend.rp.endpoints.4.protocol" -> "udp", + "com.lightbend.rp.endpoints.4.port" -> "1234", + "com.lightbend.rp.remoting-endpoint" -> "remoting", + "com.lightbend.rp.management-endpoint" -> "management")))) "generates kubernetes deployment + service resource" - { val k8sArgs = kubernetesArgs.copy(generateNamespaces = true, namespace = Some("chirper")) - generateResources(imageName, dockerConfig, generateDeploymentArgs.copy(targetRuntimeArgs = Some(k8sArgs)), k8sArgs) + generateResources(imageName, dockerConfig, generateDeploymentArgs + .copy(targetRuntimeArgs = Some(k8sArgs), discoveryMethod = DiscoveryMethod.AkkaDns), k8sArgs) .map(_.toOption) .flatMap { result => assert(result.nonEmpty) val generatedResources = result.get - val (namespace, deployment, service, ingress) = generatedResources match { - case Seq(namespace: Namespace, deployment: Deployment, service: Service, ingress: Ingress) => - (namespace, deployment, service, ingress) + val (namespace, deployment, headlessService, service, ingress) = generatedResources match { + case Seq(namespace: Namespace, deployment: Deployment, headlessService: Service, service: Service, ingress: Ingress) => + (namespace, deployment, headlessService, service, ingress) } var asserts = List.empty[Future[Unit]] @@ -131,8 +140,9 @@ object KubernetesPackageTest extends TestSuite { | "metadata": { | "name": "my-app-v3-2-1-snapshot", | "labels": { - | "appName": "my-app", - | "appNameVersion": "my-app-v3-2-1-snapshot" + | "app": "my-app", + | "appNameVersion": "my-app-v3-2-1-snapshot", + | "akka.lightbend.com/service-name": "my-app" | }, | "namespace": "chirper" | }, @@ -146,8 +156,9 @@ object KubernetesPackageTest extends TestSuite { | "template": { | "metadata": { | "labels": { - | "appName": "my-app", - | "appNameVersion": "my-app-v3-2-1-snapshot" + | "app": "my-app", + | "appNameVersion": "my-app-v3-2-1-snapshot", + | "akka.lightbend.com/service-name": "my-app" | } | }, | "spec": { @@ -229,16 +240,24 @@ object KubernetesPackageTest extends TestSuite { | ], | "ports": [ | { + | "containerPort": 2552, + | "name": "remoting" + | }, + | { + | "containerPort": 8558, + | "name": "management" + | }, + | { | "containerPort": 10000, - | "name": "ep1" + | "name": "ep3" | }, | { | "containerPort": 1234, - | "name": "ep2" + | "name": "ep4" | }, | { | "containerPort": 1234, - | "name": "ep3" + | "name": "ep5" | } | ] | } @@ -251,6 +270,47 @@ object KubernetesPackageTest extends TestSuite { """.stripMargin.parse.right.get assertPayload("deployment", deployment, deploymentJsonExpected) + assert(headlessService.name == "my-app-internal") + val headlessServiceJsonExpected = + """ + |{ + | "apiVersion": "v1", + | "kind": "Service", + | "metadata": { + | "labels": { + | "app": "my-app" + | }, + | "annotations": { + | "service.alpha.kubernetes.io/tolerate-unready-endpoints" : "true" + | }, + | "name": "my-app-internal", + | "namespace": "chirper" + | }, + | "spec": { + | "ports": [ + | { + | "name": "management", + | "port": 8558, + | "protocol": "TCP", + | "targetPort": 8558 + | }, + | { + | "name": "remoting", + | "port": 2552, + | "protocol": "TCP", + | "targetPort": 2552 + | } + | ], + | "selector": { + | "app": "my-app" + | }, + | "clusterIP" : "None", + | "publishNotReadyAddresses" : true + | } + |} + """.stripMargin.parse.right.get + assertPayload("headless service", headlessService, headlessServiceJsonExpected) + assert(service.name == "my-app") val serviceJsonExpected = """ @@ -259,7 +319,7 @@ object KubernetesPackageTest extends TestSuite { | "kind": "Service", | "metadata": { | "labels": { - | "appName": "my-app" + | "app": "my-app" | }, | "name": "my-app", | "namespace": "chirper" @@ -267,26 +327,26 @@ object KubernetesPackageTest extends TestSuite { | "spec": { | "ports": [ | { - | "name": "ep1", + | "name": "ep5", + | "port": 1234, + | "protocol": "UDP", + | "targetPort": 1234 + | }, + | { + | "name": "ep3", | "port": 10000, | "protocol": "TCP", | "targetPort": 10000 | }, | { - | "name": "ep2", + | "name": "ep4", | "port": 1234, | "protocol": "TCP", | "targetPort": 1234 - | }, - | { - | "name": "ep3", - | "port": 1234, - | "protocol": "UDP", - | "targetPort": 1234 | } | ], | "selector": { - | "appName": "my-app" + | "app": "my-app" | } | } |} @@ -324,13 +384,278 @@ object KubernetesPackageTest extends TestSuite { } } + "generates kubernetes deployment + service resource using Kubernetes API" - { + val k8sArgs = kubernetesArgs.copy(generateNamespaces = true, namespace = Some("chirper")) + + generateResources(imageName, dockerConfig, generateDeploymentArgs + .copy(targetRuntimeArgs = Some(k8sArgs), discoveryMethod = DiscoveryMethod.KubernetesApi), k8sArgs) + .map(_.toOption) + .flatMap { result => + assert(result.nonEmpty) + + val generatedResources = result.get + + val (namespace, deployment, service, ingress) = generatedResources match { + case Seq(namespace: Namespace, deployment: Deployment, service: Service, ingress: Ingress) => + (namespace, deployment, service, ingress) + } + + var asserts = List.empty[Future[Unit]] + def assertPayload(label: String, generatedResource: GeneratedResource[Json], jsonExpected: Json): Unit = { + asserts ::= generatedResource.payload.map { p => + if (p != jsonExpected) + println(s"$label payload:\n" + PrettyParams.spaces2.copy(colonLeft = "").pretty(p)) + assert(p == jsonExpected) + } + } + + assert(namespace.name == "chirper") + val namespaceJsonExpected = + """ + |{ + | "apiVersion": "v1", + | "kind": "Namespace", + | "metadata": { + | "name": "chirper", + | "labels": { + | "name": "chirper" + | } + | } + |} + """.stripMargin.parse.right.get + assertPayload("namespace", namespace, namespaceJsonExpected) + + assert(deployment.name == "my-app-v3-2-1-snapshot") + val deploymentJsonExpected = + """ + |{ + | "apiVersion": "apps/v1beta2", + | "kind": "Deployment", + | "metadata": { + | "name": "my-app-v3-2-1-snapshot", + | "labels": { + | "app": "my-app", + | "appNameVersion": "my-app-v3-2-1-snapshot", + | "akka.lightbend.com/service-name": "my-app" + | }, + | "namespace": "chirper" + | }, + | "spec": { + | "replicas": 1, + | "selector": { + | "matchLabels": { + | "appNameVersion": "my-app-v3-2-1-snapshot" + | } + | }, + | "template": { + | "metadata": { + | "labels": { + | "app": "my-app", + | "appNameVersion": "my-app-v3-2-1-snapshot", + | "akka.lightbend.com/service-name": "my-app" + | } + | }, + | "spec": { + | "restartPolicy": "Always", + | "containers": [ + | { + | "name": "my-app", + | "image": "fsat/testimpl:1.0.0-SNAPSHOT", + | "resources": { + | "limits": { + | "cpu": 0.5, + | "memory": 8192 + | }, + | "requests": { + | "cpu": 0.5, + | "memory": 8192 + | } + | }, + | "imagePullPolicy": "IfNotPresent", + | "volumeMounts": [], + | "env": [ + | { + | "name": "RP_APP_NAME", + | "value": "my-app" + | }, + | { + | "name": "RP_APP_VERSION", + | "value": "3.2.1-SNAPSHOT" + | }, + | { + | "name": "RP_KUBERNETES_POD_IP", + | "valueFrom": { + | "fieldRef": { + | "fieldPath": "status.podIP" + | } + | } + | }, + | { + | "name": "RP_KUBERNETES_POD_NAME", + | "valueFrom": { + | "fieldRef": { + | "fieldPath": "metadata.name" + | } + | } + | }, + | { + | "name": "RP_NAMESPACE", + | "valueFrom": { + | "fieldRef": { + | "fieldPath": "metadata.namespace" + | } + | } + | }, + | { + | "name": "RP_PLATFORM", + | "value": "kubernetes" + | }, + | { + | "name": "testing1", + | "value": "testingvalue1" + | }, + | { + | "name": "testing2", + | "valueFrom": { + | "configMapKeyRef": { + | "name": "mymap", + | "key": "mykey" + | } + | } + | }, + | { + | "name": "testing3", + | "valueFrom": { + | "fieldRef": { + | "fieldPath": "metadata.name" + | } + | } + | } + | ], + | "ports": [ + | { + | "containerPort": 2552, + | "name": "remoting" + | }, + | { + | "containerPort": 8558, + | "name": "management" + | }, + | { + | "containerPort": 10000, + | "name": "ep3" + | }, + | { + | "containerPort": 1234, + | "name": "ep4" + | }, + | { + | "containerPort": 1234, + | "name": "ep5" + | } + | ] + | } + | ], + | "volumes": [] + | } + | } + | } + |} + """.stripMargin.parse.right.get + assertPayload("deployment", deployment, deploymentJsonExpected) + + assert(service.name == "my-app") + val serviceJsonExpected = + """ + |{ + | "apiVersion": "v1", + | "kind": "Service", + | "metadata": { + | "labels": { + | "app": "my-app" + | }, + | "name": "my-app", + | "namespace": "chirper" + | }, + | "spec": { + | "ports": [ + | { + | "name": "ep5", + | "port": 1234, + | "protocol": "UDP", + | "targetPort": 1234 + | }, + | { + | "name": "ep3", + | "port": 10000, + | "protocol": "TCP", + | "targetPort": 10000 + | }, + | { + | "name": "ep4", + | "port": 1234, + | "protocol": "TCP", + | "targetPort": 1234 + | }, + | { + | "name": "management", + | "port": 8558, + | "protocol": "TCP", + | "targetPort": 8558 + | }, + | { + | "name": "remoting", + | "port": 2552, + | "protocol": "TCP", + | "targetPort": 2552 + | } + | ], + | "selector": { + | "app": "my-app" + | } + | } + |} + """.stripMargin.parse.right.get + assertPayload("service", service, serviceJsonExpected) + + assert(ingress.name == "my-app") + val ingressJsonExpected = + """ + |{ + | "apiVersion": "extensions/v1beta1", + | "kind": "Ingress", + | "metadata": { + | "name": "my-app", + | "namespace": "chirper" + | }, + | "spec": { + | "rules": [{ + | "http": { + | "paths": [{ + | "path": "/pizza", + | "backend": { + | "serviceName": "my-app", + | "servicePort": 10000 + | } + | }] + | } + | }] + | } + |} + """.stripMargin.parse.right.get + assertPayload("ingress", ingress, ingressJsonExpected) + + Future.sequence(asserts.reverse) + } + } + "honor generate flags" - { "generateNamespaces" - { val k8sArgs = kubernetesArgs.copy(generateNamespaces = true, namespace = Some("test")) generateResources(imageName, dockerConfig, generateDeploymentArgs.copy(targetRuntimeArgs = Some(k8sArgs)), k8sArgs) .map(_.toOption.get) .map { result => - assert(result.length == 4) + assert(result.length == 5) assert(result.head.resourceType == "namespace") } } @@ -360,7 +685,7 @@ object KubernetesPackageTest extends TestSuite { generateResources(imageName, dockerConfig, generateDeploymentArgs.copy(targetRuntimeArgs = Some(k8sArgs)), k8sArgs) .map(_.toOption.get) .map { result => - assert(result.length == 1) + assert(result.length == 2) assert(result.head.resourceType == "service") } } diff --git a/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/NamespaceJsonTest.scala b/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/NamespaceJsonTest.scala index fa7872a8..7caee1a3 100644 --- a/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/NamespaceJsonTest.scala +++ b/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/NamespaceJsonTest.scala @@ -41,6 +41,7 @@ object NamespaceJsonTest extends TestSuite { cpu = None, endpoints = Map.empty, managementEndpointName = None, + remotingEndpointName = None, secrets = Seq.empty, privileged = false, environmentVariables = Map.empty, diff --git a/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/ServiceJsonTest.scala b/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/ServiceJsonTest.scala index 24ea16d1..d41953f6 100644 --- a/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/ServiceJsonTest.scala +++ b/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/kubernetes/ServiceJsonTest.scala @@ -18,7 +18,7 @@ package com.lightbend.rp.reactivecli.runtime.kubernetes import argonaut._ import com.lightbend.rp.reactivecli.annotations._ -import com.lightbend.rp.reactivecli.argparse.{ CanaryDeploymentType, BlueGreenDeploymentType, RollingDeploymentType } +import com.lightbend.rp.reactivecli.argparse.{ CanaryDeploymentType, BlueGreenDeploymentType, RollingDeploymentType, DiscoveryMethod } import com.lightbend.rp.reactivecli.concurrent._ import com.lightbend.rp.reactivecli.json.{ JsonTransform, JsonTransformExpression } import scala.collection.immutable.Seq @@ -38,8 +38,11 @@ object ServiceJsonTest extends TestSuite { memory = Some(8192L), cpu = Some(0.5D), endpoints = Map( - "ep1" -> TcpEndpoint(0, "ep1", 1234)), - managementEndpointName = None, + "remoting" -> TcpEndpoint(0, "remoting", 2552), + "management" -> TcpEndpoint(1, "management", 8558), + "ep3" -> TcpEndpoint(2, "ep3", 1234)), + managementEndpointName = Some("management"), + remotingEndpointName = Some("remoting"), secrets = Seq.empty, privileged = true, environmentVariables = Map( @@ -51,7 +54,7 @@ object ServiceJsonTest extends TestSuite { val tests = this{ "json serialization" - { "empty" - { - val result = Service.generate(annotations.copy(endpoints = Map.empty), "v1", clusterIp = None, CanaryDeploymentType, JsonTransform.noop, None, None).toOption.get.isEmpty + val result = Service.generate(annotations.copy(endpoints = Map.empty), "v1", clusterIp = None, CanaryDeploymentType, DiscoveryMethod.AkkaDns, JsonTransform.noop, None, None).toOption.get.isEmpty assert(result) } @@ -59,13 +62,13 @@ object ServiceJsonTest extends TestSuite { "deploymentType" - { "Canary" - { Service - .generate(annotations, "v1", clusterIp = None, CanaryDeploymentType, JsonTransform.noop, None, None) + .generate(annotations, "v1", clusterIp = None, CanaryDeploymentType, DiscoveryMethod.KubernetesApi, JsonTransform.noop, None, None) .toOption .get - .get + .head .payload .map { j => - val result = (j.hcursor --\ "spec" --\ "selector" --\ "appName").focus + val result = (j.hcursor --\ "spec" --\ "selector" --\ "app").focus val expected = Some(jString("friendimpl")) assert(result == expected) @@ -74,38 +77,113 @@ object ServiceJsonTest extends TestSuite { "BlueGreen" - { Service - .generate(annotations, "v1", clusterIp = None, BlueGreenDeploymentType, JsonTransform.noop, None, None) + .generate(annotations, "v1", clusterIp = None, BlueGreenDeploymentType, DiscoveryMethod.KubernetesApi, JsonTransform.noop, None, None) .toOption .get - .get + .head .payload .map(j => assert((j.hcursor --\ "spec" --\ "selector" --\ "appNameVersion").focus.contains(jString("friendimpl-v3-2-1-snapshot")))) } "Rolling" - { Service - .generate(annotations, "v1", clusterIp = None, RollingDeploymentType, JsonTransform.noop, None, None) + .generate(annotations, "v1", clusterIp = None, RollingDeploymentType, DiscoveryMethod.KubernetesApi, JsonTransform.noop, None, None) .toOption .get - .get + .head .payload - .map(j => assert((j.hcursor --\ "spec" --\ "selector" --\ "appName").focus.contains(jString("friendimpl")))) + .map(j => assert((j.hcursor --\ "spec" --\ "selector" --\ "app").focus.contains(jString("friendimpl")))) } } "jq" - { Service - .generate(annotations, "v1", clusterIp = None, CanaryDeploymentType, JsonTransform.jq(JsonTransformExpression(".jqTest = \"test\"")), None, None) + .generate(annotations, "v1", clusterIp = None, CanaryDeploymentType, DiscoveryMethod.KubernetesApi, JsonTransform.jq(JsonTransformExpression(".jqTest = \"test\"")), None, None) .toOption .get - .get + .head .payload .map(j => assert((j.hcursor --\ "jqTest").focus.contains(jString("test")))) } + "discovery method" - { + "Akka DNS" - { + val generatedJson = Service.generate(annotations, "v1", clusterIp = None, CanaryDeploymentType, DiscoveryMethod.AkkaDns, JsonTransform.noop, None, None).toOption.get + val headlessJson = + """ + |{ + | "apiVersion": "v1", + | "kind": "Service", + | "metadata": { + | "labels": { + | "app": "friendimpl" + | }, + | "annotations": { + | "service.alpha.kubernetes.io/tolerate-unready-endpoints" : "true" + | }, + | "name": "friendimpl-internal", + | "namespace": "chirper" + | }, + | "spec": { + | "ports": [ + | { + | "name": "remoting", + | "port": 2552, + | "protocol": "TCP", + | "targetPort": 2552 + | }, + | { + | "name": "management", + | "port": 8558, + | "protocol": "TCP", + | "targetPort": 8558 + | } + | ], + | "selector": { + | "app": "friendimpl" + | }, + | "clusterIP" : "None", + | "publishNotReadyAddresses" : true + | } + |} + """.stripMargin.parse.right.get + val serviceJson = + """ + |{ + | "apiVersion": "v1", + | "kind": "Service", + | "metadata": { + | "labels": { + | "app": "friendimpl" + | }, + | "name": "friendimpl", + | "namespace": "chirper" + | }, + | "spec": { + | "ports": [ + | { + | "name": "ep3", + | "port": 1234, + | "protocol": "TCP", + | "targetPort": 1234 + | } + | ], + | "selector": { + | "app": "friendimpl" + | } + | } + |} + """.stripMargin.parse.right.get + assert(generatedJson == List( + Service("friendimpl-internal", headlessJson, JsonTransform.noop), + Service("friendimpl", serviceJson, JsonTransform.noop))) + + } + } + "options" - { "not defined" - { - val generatedJson = Service.generate(annotations, "v1", clusterIp = None, CanaryDeploymentType, JsonTransform.noop, None, None).toOption.get + val generatedJson = Service.generate(annotations, "v1", clusterIp = None, CanaryDeploymentType, DiscoveryMethod.KubernetesApi, JsonTransform.noop, None, None).toOption.get val expectedJson = """ |{ @@ -113,7 +191,7 @@ object ServiceJsonTest extends TestSuite { | "kind": "Service", | "metadata": { | "labels": { - | "appName": "friendimpl" + | "app": "friendimpl" | }, | "name": "friendimpl", | "namespace": "chirper" @@ -121,23 +199,35 @@ object ServiceJsonTest extends TestSuite { | "spec": { | "ports": [ | { - | "name": "ep1", + | "name": "remoting", + | "port": 2552, + | "protocol": "TCP", + | "targetPort": 2552 + | }, + | { + | "name": "management", + | "port": 8558, + | "protocol": "TCP", + | "targetPort": 8558 + | }, + | { + | "name": "ep3", | "port": 1234, | "protocol": "TCP", | "targetPort": 1234 | } | ], | "selector": { - | "appName": "friendimpl" + | "app": "friendimpl" | } | } |} """.stripMargin.parse.right.get - assert(generatedJson.get == Service("friendimpl", expectedJson, JsonTransform.noop)) + assert(generatedJson == List(Service("friendimpl", expectedJson, JsonTransform.noop))) } "defined" - { - val generatedJson = Service.generate(annotations, "v1", clusterIp = Some("10.0.0.5"), CanaryDeploymentType, JsonTransform.noop, Some("10.0.0.1"), Some("NodePort")).toOption.get + val generatedJson = Service.generate(annotations, "v1", clusterIp = Some("10.0.0.5"), CanaryDeploymentType, DiscoveryMethod.KubernetesApi, JsonTransform.noop, Some("10.0.0.1"), Some("NodePort")).toOption.get val expectedJson = """ |{ @@ -145,7 +235,7 @@ object ServiceJsonTest extends TestSuite { | "kind": "Service", | "metadata": { | "labels": { - | "appName": "friendimpl" + | "app": "friendimpl" | }, | "name": "friendimpl", | "namespace": "chirper" @@ -155,7 +245,19 @@ object ServiceJsonTest extends TestSuite { | "clusterIP": "10.0.0.5", | "ports": [ | { - | "name": "ep1", + | "name": "remoting", + | "port": 2552, + | "protocol": "TCP", + | "targetPort": 2552 + | }, + | { + | "name": "management", + | "port": 8558, + | "protocol": "TCP", + | "targetPort": 8558 + | }, + | { + | "name": "ep3", | "port": 1234, | "protocol": "TCP", | "targetPort": 1234 @@ -163,13 +265,13 @@ object ServiceJsonTest extends TestSuite { | ], | "type": "NodePort", | "selector": { - | "appName": "friendimpl" + | "app": "friendimpl" | } | } |} """.stripMargin.parse.right.get - assert(generatedJson.get == Service("friendimpl", expectedJson, JsonTransform.noop)) + assert(generatedJson == List(Service("friendimpl", expectedJson, JsonTransform.noop))) } } } diff --git a/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/marathon/RpEnvironmentVariablesTest.scala b/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/marathon/RpEnvironmentVariablesTest.scala index 44cdab01..a2d467c4 100644 --- a/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/marathon/RpEnvironmentVariablesTest.scala +++ b/cli/shared/src/test/scala/com/lightbend/rp/reactivecli/runtime/marathon/RpEnvironmentVariablesTest.scala @@ -43,6 +43,7 @@ object RpEnvironmentVariablesTest extends TestSuite { endpoints = Map( "ep1" -> TcpEndpoint(0, "ep1", 1234)), managementEndpointName = None, + remotingEndpointName = None, secrets = Seq.empty, privileged = true, environmentVariables = Map( @@ -91,6 +92,7 @@ object RpEnvironmentVariablesTest extends TestSuite { endpoints = Map( "ep1" -> TcpEndpoint(0, "ep1", 1234)), managementEndpointName = None, + remotingEndpointName = None, secrets = Seq.empty, privileged = true, environmentVariables = Map( @@ -141,6 +143,7 @@ object RpEnvironmentVariablesTest extends TestSuite { endpoints = Map( "ep1" -> TcpEndpoint(0, "ep1", 1234)), managementEndpointName = Some("management"), + remotingEndpointName = Some("remoting"), secrets = Seq.empty, privileged = true, environmentVariables = Map( @@ -191,6 +194,7 @@ object RpEnvironmentVariablesTest extends TestSuite { endpoints = Map( "ep1" -> TcpEndpoint(0, "ep1", 1234)), managementEndpointName = None, + remotingEndpointName = None, secrets = Seq.empty, privileged = true, environmentVariables = Map( diff --git a/integration-test/src/sbt-test/bootstrap-demo/kubernetes-api/build.sbt b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-api/build.sbt index 308cedf9..00c0dfc9 100644 --- a/integration-test/src/sbt-test/bootstrap-demo/kubernetes-api/build.sbt +++ b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-api/build.sbt @@ -45,7 +45,7 @@ lazy val root = (project in file(".")) val v = version.value val namespace = "reactivelibtest1" val rpPath = file(sys.props("reactiveclipath")) / "reactive-cli-out" - val out = Process(s"$rpPath generate-kubernetes-resources --registry-use-local --generate-all $nm:$v --pod-controller-replicas 3").!! + val out = Process(s"$rpPath generate-kubernetes-resources --registry-use-local --generate-all --discovery-method kubernetes-api $nm:$v --pod-controller-replicas 3").!! val x = if (!Deckhand.isOpenShift) out.replaceAllLiterally("imagePullPolicy: IfNotPresent", "imagePullPolicy: Never") diff --git a/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/build.sbt b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/build.sbt new file mode 100644 index 00000000..fbe72234 --- /dev/null +++ b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/build.sbt @@ -0,0 +1,87 @@ +// https://github.com/akka/akka-management/tree/master/bootstrap-demo/kubernetes-dns + +import Dependencies._ +import scala.sys.process.Process +import scala.util.control.NonFatal + +ThisBuild / version := "0.1.6" +ThisBuild / organization := "com.example" +ThisBuild / scalaVersion := "2.12.7" + +lazy val check = taskKey[Unit]("check") +lazy val generateYaml = taskKey[Unit]("generateYaml") + +lazy val root = (project in file(".")) + .enablePlugins(SbtReactiveAppPlugin) + .settings( + name := "bootstrap-kubernetes-dns-demo", + scalacOptions ++= Seq( + "-encoding", + "UTF-8", + "-feature", + "-unchecked", + "-deprecation", + "-Xlint", + "-Yno-adapted-args", + ), + libraryDependencies ++= Seq( + akkaManagement, + akkaClusterHttp, + akkaCluster, + akkaClusterSharding, + akkaClusterTools, + akkaDiscoveryDns, + akkaSlj4j, + logback, + scalaTest + ), + enableAkkaClusterBootstrap := true, + + // run nativeLink in the host build first + generateYaml := { + val s = streams.value + val nm = name.value + val v = version.value + val namespace = "reactivelibtest1" + val rpPath = file(sys.props("reactiveclipath")) / "reactive-cli-out" + val out = Process(s"$rpPath generate-kubernetes-resources --registry-use-local --generate-all $nm:$v --pod-controller-replicas 3 --stacktrace").!! + val x = + if (!Deckhand.isOpenShift) + out.replaceAllLiterally("imagePullPolicy: IfNotPresent", "imagePullPolicy: Never") + else out + .replaceAllLiterally("imagePullPolicy: IfNotPresent", "imagePullPolicy: Always") + .replaceAllLiterally("image: \"" + s"$nm:$v" + "\"", s"image: docker-registry-default.centralpark.lightbend.com/$namespace/$nm:$v") + s.log.info("generated YAML: " + x) + IO.write(target.value / "temp.yaml", x) + }, + + // this logic was taken from test.sh + check := { + val s = streams.value + val nm = name.value + val v = version.value + val namespace = "reactivelibtest1" + val kubectl = Deckhand.kubectl(s.log) + val docker = Deckhand.docker(s.log) + + try { + if (!Deckhand.isOpenShift) { + kubectl.tryCreate(s"namespace $namespace") + kubectl.setCurrentNamespace(namespace) + } else { + // work around: /rp-start: line 60: /opt/docker/bin/bootstrap-kapi-demo: Permission denied + kubectl.command(s"adm policy add-scc-to-user anyuid system:serviceaccount:$namespace:default") + kubectl.command(s"policy add-role-to-user system:image-builder system:serviceaccount:$namespace:default") + docker.tag(s"$nm:$v docker-registry-default.centralpark.lightbend.com/$namespace/$nm:$v") + docker.push(s"docker-registry-default.centralpark.lightbend.com/$namespace/$nm") + } + kubectl.apply(target.value / "temp.yaml") + kubectl.waitForPods(3) + kubectl.describe("pods") + kubectl.checkAkkaCluster(3, _.contains(nm)) + } finally { + kubectl.delete(s"services,pods,deployment --all --namespace $namespace") + kubectl.waitForPods(0) + } + } + ) diff --git a/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/project/Dependencies.scala b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/project/Dependencies.scala new file mode 100644 index 00000000..c8759277 --- /dev/null +++ b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/project/Dependencies.scala @@ -0,0 +1,21 @@ +import sbt._ + +object Dependencies { + val akkaVersion = "2.5.19" + val akkaManagementVersion = "0.20.0" + + val akkaActor = "com.typesafe.akka" %% "akka-actor" % akkaVersion + val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % akkaVersion + val akkaClusterSharding = "com.typesafe.akka" %% "akka-cluster-sharding" % akkaVersion + val akkaClusterTools = "com.typesafe.akka" %% "akka-cluster-tools" % akkaVersion + val akkaSlj4j = "com.typesafe.akka" %% "akka-slf4j" % akkaVersion + + val akkaManagement = "com.lightbend.akka.management" %% "akka-management" % akkaManagementVersion + val akkaBootstrap = "com.lightbend.akka.management" %% "akka-management-cluster-bootstrap" % akkaManagementVersion + val akkaDiscoveryDns = "com.lightbend.akka.discovery" %% "akka-discovery-dns" % akkaManagementVersion + val akkaClusterHttp = "com.lightbend.akka.management" %% "akka-management-cluster-http" % akkaManagementVersion + + val logback = "ch.qos.logback" % "logback-classic" % "1.2.3" + + val scalaTest = "org.scalatest" %% "scalatest" % "3.0.5" % Test +} diff --git a/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/project/build.properties b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/project/build.properties new file mode 100644 index 00000000..ee0a5b57 --- /dev/null +++ b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/project/build.properties @@ -0,0 +1,2 @@ +sbt.version=1.2.7 + diff --git a/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/project/plugins.sbt b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/project/plugins.sbt new file mode 100644 index 00000000..fbb53bc1 --- /dev/null +++ b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("com.lightbend.rp" % "sbt-reactive-app" % "1.7.0-M1") +addSbtPlugin("com.lightbend.rp" % "sbt-deckhand" % "0.1.0") diff --git a/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/src/main/resources/application.conf b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/src/main/resources/application.conf new file mode 100644 index 00000000..be0b09ed --- /dev/null +++ b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/src/main/resources/application.conf @@ -0,0 +1,3 @@ +akka { + loglevel = DEBUG +} diff --git a/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/src/main/scala/foo/ClusterApp.scala b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/src/main/scala/foo/ClusterApp.scala new file mode 100644 index 00000000..cdc16437 --- /dev/null +++ b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/src/main/scala/foo/ClusterApp.scala @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017-2018 Lightbend Inc. + */ + +package foo + +import akka.actor.{ Actor, ActorLogging, ActorSystem, PoisonPill, Props } +import akka.cluster.ClusterEvent.ClusterDomainEvent +import akka.cluster.singleton.{ ClusterSingletonManager, ClusterSingletonManagerSettings } +import akka.cluster.{ Cluster, ClusterEvent } +import akka.http.scaladsl.Http +import akka.http.scaladsl.model._ +import akka.http.scaladsl.server.Directives._ +import akka.stream.ActorMaterializer + +object ClusterApp { + + def main(args: Array[String]): Unit = { + + implicit val system = ActorSystem() + implicit val materializer = ActorMaterializer() + implicit val executionContext = system.dispatcher + + val cluster = Cluster(system) + system.log.info("Starting Akka Management") + system.log.info("something2") + // AkkaManagement(system).start() + // ClusterBootstrap(system).start() + + system.actorOf( + ClusterSingletonManager.props( + Props[NoisySingleton], + PoisonPill, + ClusterSingletonManagerSettings(system))) + Cluster(system).subscribe( + system.actorOf(Props[ClusterWatcher]), + ClusterEvent.InitialStateAsEvents, + classOf[ClusterDomainEvent]) + + // add real app routes here + val routes = + path("hello") { + get { + complete( + HttpEntity(ContentTypes.`text/html(UTF-8)`, "

Hello

")) + } + } + + Http().bindAndHandle(routes, "0.0.0.0", 8080) + + system.log.info( + s"Server online at http://localhost:8080/\nPress RETURN to stop...") + + cluster.registerOnMemberUp(() => { + system.log.info("Cluster member is up!") + }) + } + + class ClusterWatcher extends Actor with ActorLogging { + val cluster = Cluster(context.system) + + override def receive = { + case msg ⇒ log.info(s"Cluster ${cluster.selfAddress} >>> " + msg) + } + } +} diff --git a/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/src/main/scala/foo/NoisySingleton.scala b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/src/main/scala/foo/NoisySingleton.scala new file mode 100644 index 00000000..f874b1a6 --- /dev/null +++ b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/src/main/scala/foo/NoisySingleton.scala @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017-2018 Lightbend Inc. + */ + +package foo + +import akka.actor.Actor +import akka.actor.ActorLogging + +class NoisySingleton extends Actor with ActorLogging { + + override def preStart(): Unit = + log.info("Noisy singleton started") + + override def postStop(): Unit = + log.info("Noisy singleton stopped") + + override def receive: Receive = { + case msg => log.info("Msg: {}", msg) + } +} diff --git a/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/test b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/test new file mode 100644 index 00000000..acd08eb1 --- /dev/null +++ b/integration-test/src/sbt-test/bootstrap-demo/kubernetes-dns/test @@ -0,0 +1,4 @@ +> Docker/publishLocal +> generateYaml +$exists target/temp.yaml +> check