From 434cb7d5acc53ecc3a1fc65cb8414116ee50a74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Andr=C3=A9n?= Date: Mon, 19 Feb 2024 19:59:20 +0100 Subject: [PATCH 1/7] feat: Native-image metadata and docs --- .../reflect-config.json | 43 +++++++++++ .../HttpBootstrapJsonProtocol.scala | 2 + .../reflect-config.json | 57 ++++++++++++++ .../ClusterHttpManagementProtocol.scala | 1 + .../reflect-config.json | 48 ++++++++++++ .../discovery/kubernetes/JsonFormat.scala | 1 + docs/src/main/paradox/index.md | 1 + docs/src/main/paradox/native-image.md | 14 ++++ .../akka-lease-kubernetes/reflect-config.json | 26 +++++++ .../internal/KubernetesJsonSupport.scala | 1 + .../akka-management/reflect-config.json | 15 ++++ .../reflect-config.json | 77 +++++++++++++++++++ .../kubernetes/KubernetesJsonSupport.scala | 1 + 13 files changed, 287 insertions(+) create mode 100644 cluster-bootstrap/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-management-cluster-bootstrap/reflect-config.json create mode 100644 cluster-http/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-management-cluster-http/reflect-config.json create mode 100644 discovery-kubernetes-api/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-discovery-kubernetes-api/reflect-config.json create mode 100644 docs/src/main/paradox/native-image.md create mode 100644 lease-kubernetes/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-lease-kubernetes/reflect-config.json create mode 100644 management/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-management/reflect-config.json create mode 100644 rolling-update-kubernetes/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-rolling-upgrade-kubernetes/reflect-config.json diff --git a/cluster-bootstrap/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-management-cluster-bootstrap/reflect-config.json b/cluster-bootstrap/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-management-cluster-bootstrap/reflect-config.json new file mode 100644 index 000000000..7e27e98a6 --- /dev/null +++ b/cluster-bootstrap/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-management-cluster-bootstrap/reflect-config.json @@ -0,0 +1,43 @@ +[ + { + "name": "akka.management.cluster.bootstrap.LowestAddressJoinDecider", + "methods": [ + { + "name": "", + "parameterTypes": ["akka.actor.ActorSystem", "akka.management.cluster.bootstrap.ClusterBootstrapSettings"] + } + ] + }, + { + "name": "akka.management.cluster.bootstrap.ClusterBootstrap$", + "fields": [ + { + "name": "MODULE$" + } + ] + }, + { + "name": "akka.management.cluster.bootstrap.ClusterBootstrap", + "methods": [ + { + "name": "", + "parameterTypes": ["akka.actor.ExtendedActorSystem"] + } + ] + }, + { + "name": "akka.management.cluster.bootstrap.contactpoint.HttpBootstrapJsonProtocol$SeedNode", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.management.cluster.bootstrap.contactpoint.HttpBootstrapJsonProtocol$ClusterMember", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.management.cluster.bootstrap.contactpoint.HttpBootstrapJsonProtocol$SeedNodes", + "allDeclaredFields": true, + "queryAllPublicMethods": true + } +] \ No newline at end of file diff --git a/cluster-bootstrap/src/main/scala/akka/management/cluster/bootstrap/contactpoint/HttpBootstrapJsonProtocol.scala b/cluster-bootstrap/src/main/scala/akka/management/cluster/bootstrap/contactpoint/HttpBootstrapJsonProtocol.scala index a0f1b9cbe..3f209e2e0 100644 --- a/cluster-bootstrap/src/main/scala/akka/management/cluster/bootstrap/contactpoint/HttpBootstrapJsonProtocol.scala +++ b/cluster-bootstrap/src/main/scala/akka/management/cluster/bootstrap/contactpoint/HttpBootstrapJsonProtocol.scala @@ -19,6 +19,8 @@ trait HttpBootstrapJsonProtocol extends SprayJsonSupport with DefaultJsonProtoco override def write(obj: Address): JsValue = JsString(obj.toString) } + + // If adding more formats here, remember to also add in META-INF/native-image reflect config implicit val SeedNodeFormat: RootJsonFormat[SeedNode] = jsonFormat1(SeedNode.apply) implicit val ClusterMemberFormat: RootJsonFormat[ClusterMember] = jsonFormat4(ClusterMember.apply) implicit val ClusterMembersFormat: RootJsonFormat[SeedNodes] = jsonFormat2(SeedNodes.apply) diff --git a/cluster-http/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-management-cluster-http/reflect-config.json b/cluster-http/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-management-cluster-http/reflect-config.json new file mode 100644 index 000000000..300add000 --- /dev/null +++ b/cluster-http/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-management-cluster-http/reflect-config.json @@ -0,0 +1,57 @@ +[ + { + "name": "akka.management.cluster.ClusterHttpManagementRouteProvider$", + "fields": [ { + "name": "MODULE$" + } ] + }, + { + "name": "akka.management.cluster.javadsl.ClusterMembershipCheck", + "methods": [{ + "name": "", + "parameterTypes": ["akka.actor.ActorSystem"] + }] + }, + { + "name": "akka.management.cluster.scaladsl.ClusterMembershipCheck", + "methods": [{ + "name": "", + "parameterTypes": ["akka.actor.ActorSystem"] + }] + }, + { + "name": "akka.management.cluster.ClusterUnreachableMember", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.management.cluster.ClusterMember", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.management.cluster.ClusterMembers", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.management.cluster.ClusterHttpManagementMessage", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.management.cluster.ShardEntityTypeKeys", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.management.cluster.ShardRegionInfo", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.management.cluster.ShardDetails", + "allDeclaredFields": true, + "queryAllPublicMethods": true + } +] \ No newline at end of file diff --git a/cluster-http/src/main/scala/akka/management/cluster/ClusterHttpManagementProtocol.scala b/cluster-http/src/main/scala/akka/management/cluster/ClusterHttpManagementProtocol.scala index 87ae8d7d5..874108936 100644 --- a/cluster-http/src/main/scala/akka/management/cluster/ClusterHttpManagementProtocol.scala +++ b/cluster-http/src/main/scala/akka/management/cluster/ClusterHttpManagementProtocol.scala @@ -47,6 +47,7 @@ final case class ShardDetails(regions: immutable.Seq[ShardRegionInfo]) } trait ClusterHttpManagementJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol { + // If adding more formats here, remember to also add in META-INF/native-image reflect config implicit val clusterUnreachableMemberFormat: RootJsonFormat[ClusterUnreachableMember] = jsonFormat2(ClusterUnreachableMember.apply) implicit val clusterMemberFormat: RootJsonFormat[ClusterMember] = jsonFormat4(ClusterMember.apply) diff --git a/discovery-kubernetes-api/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-discovery-kubernetes-api/reflect-config.json b/discovery-kubernetes-api/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-discovery-kubernetes-api/reflect-config.json new file mode 100644 index 000000000..cc8b64cc2 --- /dev/null +++ b/discovery-kubernetes-api/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-discovery-kubernetes-api/reflect-config.json @@ -0,0 +1,48 @@ +[ + { + "name": "akka.discovery.kubernetes.KubernetesApiServiceDiscovery", + "methods": [ + { + "name": "", + "parameterTypes": [ + "akka.actor.ActorSystem" + ] + } + ] + }, + { + "name": "akka.discovery.kubernetes.PodList$ContainerPort", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.discovery.kubernetes.PodList.Container", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.discovery.kubernetes.PodList.PodSpec", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.discovery.kubernetes.PodList.ContainerStatus", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.discovery.kubernetes.PodList.PodStatus", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.discovery.kubernetes.PodList.Pod", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.discovery.kubernetes.PodList.Metadata", + "allDeclaredFields": true, + "queryAllPublicMethods": true + } +] \ No newline at end of file diff --git a/discovery-kubernetes-api/src/main/scala/akka/discovery/kubernetes/JsonFormat.scala b/discovery-kubernetes-api/src/main/scala/akka/discovery/kubernetes/JsonFormat.scala index feed77e4c..59d290792 100644 --- a/discovery-kubernetes-api/src/main/scala/akka/discovery/kubernetes/JsonFormat.scala +++ b/discovery-kubernetes-api/src/main/scala/akka/discovery/kubernetes/JsonFormat.scala @@ -13,6 +13,7 @@ import spray.json._ * INTERNAL API */ @InternalApi private[akka] object JsonFormat extends SprayJsonSupport with DefaultJsonProtocol { + // If adding more formats here, remember to also add in META-INF/native-image reflect config implicit val containerPortFormat: JsonFormat[ContainerPort] = jsonFormat2(ContainerPort.apply) implicit val containerFormat: JsonFormat[Container] = jsonFormat2(Container.apply) implicit val podSpecFormat: JsonFormat[PodSpec] = jsonFormat1(PodSpec.apply) diff --git a/docs/src/main/paradox/index.md b/docs/src/main/paradox/index.md index 8b3f9fbae..ae9220758 100644 --- a/docs/src/main/paradox/index.md +++ b/docs/src/main/paradox/index.md @@ -36,4 +36,5 @@ Various parts of Akka management can be used together for deploying Akka Cluster - [Akka Cluster Management (JMX)](cluster-jmx-management.md) - [Dynamic Log Levels](loglevels/index.md) - [Akka Coordination Lease for Kubernetes](kubernetes-lease.md) + - [Native Image](native-image.md) @@@ diff --git a/docs/src/main/paradox/native-image.md b/docs/src/main/paradox/native-image.md new file mode 100644 index 000000000..3078f7f4e --- /dev/null +++ b/docs/src/main/paradox/native-image.md @@ -0,0 +1,14 @@ +# Building Native Images + +Building native images with Akka HTTP is supported out of the box for the following modules: + + * akka-management + * akka-management-cluster-bootstrap + * akka-management-cluster-http + * akka-management-discovery-kubernetes-api + * akka-lease-kubernetes + * akka-rolling-upgrade-kubernetes + +Other modules can likely be used but will require figuring out and adding additional native-image metadata. + +For details about building native images with Akka in general, see the @extref[Akka Documentation](akka:additional/native-image.html). \ No newline at end of file diff --git a/lease-kubernetes/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-lease-kubernetes/reflect-config.json b/lease-kubernetes/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-lease-kubernetes/reflect-config.json new file mode 100644 index 000000000..ee8e4d5bf --- /dev/null +++ b/lease-kubernetes/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-lease-kubernetes/reflect-config.json @@ -0,0 +1,26 @@ +[ + { + "name": "akka.coordination.lease.kubernetes.KubernetesLease", + "methods": [ + { + "name": "", + "parameterTypes": ["akka.coordination.lease.LeaseSettings", "akka.actor.ExtendedActorSystem"] + } + ] + }, + { + "name": "akka.coordination.lease.kubernetes.internal.LeaseCustomResource", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.coordination.lease.kubernetes.internal.Metadata", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.coordination.lease.kubernetes.internal.Spec", + "allDeclaredFields": true, + "queryAllPublicMethods": true + } +] \ No newline at end of file diff --git a/lease-kubernetes/src/main/scala/akka/coordination/lease/kubernetes/internal/KubernetesJsonSupport.scala b/lease-kubernetes/src/main/scala/akka/coordination/lease/kubernetes/internal/KubernetesJsonSupport.scala index 7e1ec132e..926028e28 100644 --- a/lease-kubernetes/src/main/scala/akka/coordination/lease/kubernetes/internal/KubernetesJsonSupport.scala +++ b/lease-kubernetes/src/main/scala/akka/coordination/lease/kubernetes/internal/KubernetesJsonSupport.scala @@ -35,6 +35,7 @@ case class Spec(owner: String, time: Long) */ @InternalApi trait KubernetesJsonSupport extends SprayJsonSupport with DefaultJsonProtocol { + // If adding more formats here, remember to also add in META-INF/native-image reflect config implicit val metadataFormat: JsonFormat[Metadata] = jsonFormat2(Metadata.apply) implicit val specFormat: JsonFormat[Spec] = jsonFormat2(Spec.apply) implicit val leaseCustomResourceFormat: RootJsonFormat[LeaseCustomResource] = jsonFormat4(LeaseCustomResource.apply) diff --git a/management/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-management/reflect-config.json b/management/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-management/reflect-config.json new file mode 100644 index 000000000..24ce3b948 --- /dev/null +++ b/management/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-management/reflect-config.json @@ -0,0 +1,15 @@ +[ + { + "name": "akka.management.scaladsl.AkkaManagement$", + "fields": [ { + "name": "MODULE$" + } ] + }, + { + "name": "akka.management.HealthCheckRoutes", + "methods": [{ + "name": "", + "parameterTypes": ["akka.actor.ExtendedActorSystem"] + }] + } +] \ No newline at end of file diff --git a/rolling-update-kubernetes/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-rolling-upgrade-kubernetes/reflect-config.json b/rolling-update-kubernetes/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-rolling-upgrade-kubernetes/reflect-config.json new file mode 100644 index 000000000..d80c76b0b --- /dev/null +++ b/rolling-update-kubernetes/src/main/resources/META-INF/native-image/com.lightbend.akka.management/akka-rolling-upgrade-kubernetes/reflect-config.json @@ -0,0 +1,77 @@ +[ + { + "name": "akka.rollingupdate.kubernetes.AppVersionRevision$", + "fields": [ + { + "name": "MODULE$" + } + ] + }, + { + "name": "akka.rollingupdate.kubernetes.PodDeletionCost$", + "fields": [ + { + "name": "MODULE$" + } + ] + }, + { + "name": "akka.management.cluster.bootstrap.ClusterBootstrap", + "methods": [ + { + "name": "", + "parameterTypes": ["akka.actor.ExtendedActorSystem"] + } + ] + }, + { + "name": "akka.rollingupdate.kubernetes.Metadata", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.rollingupdate.kubernetes.PodCost", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.rollingupdate.kubernetes.Spec", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.rollingupdate.kubernetes.PodCostCustomResource", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.rollingupdate.kubernetes.PodOwnerRef", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.rollingupdate.kubernetes.PodMetadata", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.rollingupdate.kubernetes.Pod", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.rollingupdate.kubernetes.ReplicaAnnotation", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.rollingupdate.kubernetes.ReplicaSetMetadata", + "allDeclaredFields": true, + "queryAllPublicMethods": true + }, + { + "name": "akka.rollingupdate.kubernetes.ReplicaSet", + "allDeclaredFields": true, + "queryAllPublicMethods": true + } +] \ No newline at end of file diff --git a/rolling-update-kubernetes/src/main/scala/akka/rollingupdate/kubernetes/KubernetesJsonSupport.scala b/rolling-update-kubernetes/src/main/scala/akka/rollingupdate/kubernetes/KubernetesJsonSupport.scala index 0ad00767d..cb929c3e8 100644 --- a/rolling-update-kubernetes/src/main/scala/akka/rollingupdate/kubernetes/KubernetesJsonSupport.scala +++ b/rolling-update-kubernetes/src/main/scala/akka/rollingupdate/kubernetes/KubernetesJsonSupport.scala @@ -75,6 +75,7 @@ case class Spec(pods: immutable.Seq[PodCost]) */ @InternalApi trait KubernetesJsonSupport extends SprayJsonSupport with DefaultJsonProtocol { + // If adding more formats here, remember to also add in META-INF/native-image reflect config implicit val metadataFormat: JsonFormat[Metadata] = jsonFormat2(Metadata.apply) implicit val podCostFormat: JsonFormat[PodCost] = jsonFormat5(PodCost.apply) implicit val specFormat: JsonFormat[Spec] = jsonFormat1(Spec.apply) From ceceaa32cf29a399bdc81ae916efcfbc349fa11a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Andr=C3=A9n?= Date: Tue, 20 Feb 2024 15:55:59 +0100 Subject: [PATCH 2/7] figured out a way to test --- .github/workflows/native-image-tests.yml | 14 +++-- docs/src/main/paradox/native-image.md | 4 +- native-image-tests/.gitignore | 12 +++++ native-image-tests/build.sbt | 51 +++++++++++++++++++ native-image-tests/project/build.properties | 1 + native-image-tests/project/plugins.sbt | 1 + .../src/main/resources/application.conf | 37 ++++++++++++++ .../src/main/resources/logback.xml | 20 ++++++++ .../src/main/scala/com/lightbend/Main.scala | 42 +++++++++++++++ 9 files changed, 172 insertions(+), 10 deletions(-) create mode 100644 native-image-tests/.gitignore create mode 100644 native-image-tests/build.sbt create mode 100644 native-image-tests/project/build.properties create mode 100644 native-image-tests/project/plugins.sbt create mode 100644 native-image-tests/src/main/resources/application.conf create mode 100644 native-image-tests/src/main/resources/logback.xml create mode 100644 native-image-tests/src/main/scala/com/lightbend/Main.scala diff --git a/.github/workflows/native-image-tests.yml b/.github/workflows/native-image-tests.yml index 21f5af9f0..609e72965 100644 --- a/.github/workflows/native-image-tests.yml +++ b/.github/workflows/native-image-tests.yml @@ -43,14 +43,12 @@ jobs: run: |- sbt "publishLocal; publishM2" - #- name: Akka Management native image test app build - # run: |- - # cd native-image-tests/grpc-scala - # sbt nativeImage -Dakka.grpc.version=`cat ~/.version` - # # run the binary, netty client backend - # target/native-image/grpc-scala - # # akka-http client backend - # target/native-image/grpc-scala akka-http-client + - name: Akka Management native image test app build + run: |- + cd native-image-tests + sbt nativeImage -Dakka.management.version=`cat ~/.version` + # run the binary to see it can bootstrap a cluster + target/native-image/native-image-tests - name: Email on failure if: ${{ failure() }} diff --git a/docs/src/main/paradox/native-image.md b/docs/src/main/paradox/native-image.md index 3078f7f4e..374e61483 100644 --- a/docs/src/main/paradox/native-image.md +++ b/docs/src/main/paradox/native-image.md @@ -5,9 +5,9 @@ Building native images with Akka HTTP is supported out of the box for the follow * akka-management * akka-management-cluster-bootstrap * akka-management-cluster-http - * akka-management-discovery-kubernetes-api + * akka-discovery-kubernetes-api * akka-lease-kubernetes - * akka-rolling-upgrade-kubernetes + * akka-rolling-update-kubernetes Other modules can likely be used but will require figuring out and adding additional native-image metadata. diff --git a/native-image-tests/.gitignore b/native-image-tests/.gitignore new file mode 100644 index 000000000..c4ddf4449 --- /dev/null +++ b/native-image-tests/.gitignore @@ -0,0 +1,12 @@ +target/ + +.settings +.project +.classpath + +.idea +*.iml + +.metals +.bloop +.bsp diff --git a/native-image-tests/build.sbt b/native-image-tests/build.sbt new file mode 100644 index 000000000..1af47ce05 --- /dev/null +++ b/native-image-tests/build.sbt @@ -0,0 +1,51 @@ +name := "native-image-tests" + +version := "1.0" + +scalaVersion := s"2.13.12" + +resolvers += "Akka library repository".at("https://repo.akka.io/maven") + +lazy val akkaVersion = sys.props.getOrElse("akka.version", "2.9.1") +lazy val akkaHttpVersion = sys.props.getOrElse("akka.http.version", "10.6.0") + +// Note: this default isn't really used anywhere so not important to bump +lazy val akkaManagementVersion = sys.props.getOrElse("akka.management.version", "1.5.0") + +// Run in a separate JVM, to make sure sbt waits until all threads have +// finished before returning. +// If you want to keep the application running while executing other +// sbt tasks, consider https://github.com/spray/sbt-revolver/ +fork := true + +libraryDependencies ++= Seq( + "com.typesafe.akka" %% "akka-actor-typed" % akkaVersion, + "com.typesafe.akka" %% "akka-cluster-typed" % akkaVersion, + "com.typesafe.akka" %% "akka-cluster" % akkaVersion, + "com.typesafe.akka" %% "akka-discovery" % akkaVersion, + "com.typesafe.akka" %% "akka-persistence" % akkaVersion, + "com.typesafe.akka" %% "akka-cluster-sharding" % akkaVersion, + "com.typesafe.akka" %% "akka-remote" % akkaVersion, + "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, + "com.typesafe.akka" %% "akka-http-core" % akkaHttpVersion, + "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion, + "com.lightbend.akka.management" %% "akka-management" % akkaManagementVersion, + "com.lightbend.akka.management" %% "akka-management-cluster-bootstrap" % akkaManagementVersion, + "com.lightbend.akka.management" %% "akka-management-cluster-http" % akkaManagementVersion, + "com.lightbend.akka.discovery" %% "akka-discovery-kubernetes-api" % akkaManagementVersion, + "com.lightbend.akka.management" %% "akka-lease-kubernetes" % akkaManagementVersion, + "com.lightbend.akka.management" %% "akka-rolling-update-kubernetes" % akkaManagementVersion, + "ch.qos.logback" % "logback-classic" % "1.2.13" +) + +// useful for investigations: sbt nativeImageRunAgent + +// GraalVM native image build +enablePlugins(NativeImagePlugin) +nativeImageJvm := "graalvm-community" +nativeImageVersion := "21.0.2" +nativeImageOptions := Seq( + "--no-fallback", + "--verbose", + "-Dakka.native-image.debug=true" +) diff --git a/native-image-tests/project/build.properties b/native-image-tests/project/build.properties new file mode 100644 index 000000000..abbbce5da --- /dev/null +++ b/native-image-tests/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.9.8 diff --git a/native-image-tests/project/plugins.sbt b/native-image-tests/project/plugins.sbt new file mode 100644 index 000000000..f28fde7a9 --- /dev/null +++ b/native-image-tests/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.4") diff --git a/native-image-tests/src/main/resources/application.conf b/native-image-tests/src/main/resources/application.conf new file mode 100644 index 000000000..f921292b3 --- /dev/null +++ b/native-image-tests/src/main/resources/application.conf @@ -0,0 +1,37 @@ +akka { + actor.provider = "cluster" + extensions = ["akka.management.cluster.bootstrap.ClusterBootstrap"] + + + discovery { + aggregate { + discovery-methods = ["kubernetes-api", "config"] + } + config.services.local-cluster.endpoints = [ + { + host = "127.0.0.1" + port = 8558 + } + ] + } + + + management { + http.hostname = "127.0.0.1" + http.port = 8558 + cluster.bootstrap { + contact-point-discovery { + # to allow easier testing, we aggregate kubernetes-api and then use config as a fallback, + # native-image-brokenness would cause class not found or linker error, so k8 api discovery failing because + # not running inside k8 means things likely works as expected (not a watertight check though) + discovery-method = aggregate + service-name = "local-cluster" + required-contact-point-nr = 1 + } + } + } + + cluster { + downing-provider-class = "akka.cluster.sbr.SplitBrainResolverProvider" + } +} \ No newline at end of file diff --git a/native-image-tests/src/main/resources/logback.xml b/native-image-tests/src/main/resources/logback.xml new file mode 100644 index 000000000..b1fe9ae92 --- /dev/null +++ b/native-image-tests/src/main/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + [%date{ISO8601}] [%level] [%logger] [%thread] [%X{akkaSource}] - %msg%n + + + + + 1024 + true + + + + + + + + diff --git a/native-image-tests/src/main/scala/com/lightbend/Main.scala b/native-image-tests/src/main/scala/com/lightbend/Main.scala new file mode 100644 index 000000000..680ad2f7e --- /dev/null +++ b/native-image-tests/src/main/scala/com/lightbend/Main.scala @@ -0,0 +1,42 @@ +package com.lightbend + +import akka.actor.typed.ActorSystem +import akka.actor.typed.Behavior +import akka.actor.typed.scaladsl.Behaviors +import akka.cluster.typed.Cluster +import akka.cluster.typed.SelfUp +import akka.cluster.typed.Subscribe +import akka.management.scaladsl.AkkaManagement + +import scala.concurrent.duration.DurationInt + +object RootBehavior { + def apply(): Behavior[AnyRef] = Behaviors.setup { context => + Behaviors.withTimers { timers => + // Note that some exceptions in the log from k8 api discovery is expected, see application.conf + AkkaManagement(context.system).start() + timers.startSingleTimer("Timeout", 30.seconds) + Cluster(context.system).subscriptions ! Subscribe(context.self, classOf[SelfUp]) + + // FIXME cover k8 lease + // FIXME cover rolling-update + + Behaviors.receiveMessagePartial { + case SelfUp(_) => + context.log.info("Managed to bootstrap cluster, shutting down") + Behaviors.stopped + + case "Timeout" => + context.log.error("Didn't manage to bootstrap within 30s, something is off") + System.exit(1) + Behaviors.same + } + } + } +} + +object Main extends App { + + val system: ActorSystem[AnyRef] = ActorSystem(RootBehavior(), "ManagementNativeImageTests") + +} From b0a94fa3c21a480209e4d25ea576fbf71564f770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Andr=C3=A9n?= Date: Wed, 21 Feb 2024 17:16:49 +0100 Subject: [PATCH 3/7] Fix JMX link --- docs/src/main/paradox/cluster-jmx-management.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/paradox/cluster-jmx-management.md b/docs/src/main/paradox/cluster-jmx-management.md index 5bb0e4c19..915b04b89 100644 --- a/docs/src/main/paradox/cluster-jmx-management.md +++ b/docs/src/main/paradox/cluster-jmx-management.md @@ -1,4 +1,4 @@ # Built-in JMX Management Akka has built-in JMX beans which you can use to manage the cluster, read more about them in the Akka -documentation: @extref:[Cluster Usage: JMX](akka:cluster-usage.html#cluster-jmx) +documentation: @extref:[Cluster Usage: JMX](akka:additional/operations.html#jmx) From 6bd8ff2a366c9d4585950674d4d9479349bec7de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Andr=C3=A9n?= Date: Mon, 26 Feb 2024 15:09:57 +0100 Subject: [PATCH 4/7] Test coverage for k8 lease and rolling update --- native-image-tests/build.sbt | 3 +- .../src/main/resources/application.conf | 5 +++- .../src/main/resources/logback.xml | 8 +---- .../src/main/scala/com/lightbend/Main.scala | 29 +++++++++++++++++-- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/native-image-tests/build.sbt b/native-image-tests/build.sbt index 1af47ce05..d26cd73a4 100644 --- a/native-image-tests/build.sbt +++ b/native-image-tests/build.sbt @@ -2,7 +2,7 @@ name := "native-image-tests" version := "1.0" -scalaVersion := s"2.13.12" +scalaVersion := "2.13.12" resolvers += "Akka library repository".at("https://repo.akka.io/maven") @@ -47,5 +47,6 @@ nativeImageVersion := "21.0.2" nativeImageOptions := Seq( "--no-fallback", "--verbose", + "--initialize-at-build-time=ch.qos.logback", "-Dakka.native-image.debug=true" ) diff --git a/native-image-tests/src/main/resources/application.conf b/native-image-tests/src/main/resources/application.conf index f921292b3..13a4f6d69 100644 --- a/native-image-tests/src/main/resources/application.conf +++ b/native-image-tests/src/main/resources/application.conf @@ -34,4 +34,7 @@ akka { cluster { downing-provider-class = "akka.cluster.sbr.SplitBrainResolverProvider" } -} \ No newline at end of file +} + +# just to keep the class name out of graalvms sight +pod-cost-class = "akka.rollingupdate.kubernetes.PodDeletionCost" \ No newline at end of file diff --git a/native-image-tests/src/main/resources/logback.xml b/native-image-tests/src/main/resources/logback.xml index b1fe9ae92..7aff05869 100644 --- a/native-image-tests/src/main/resources/logback.xml +++ b/native-image-tests/src/main/resources/logback.xml @@ -7,14 +7,8 @@ - - 1024 - true - - - - + diff --git a/native-image-tests/src/main/scala/com/lightbend/Main.scala b/native-image-tests/src/main/scala/com/lightbend/Main.scala index 680ad2f7e..637e5b826 100644 --- a/native-image-tests/src/main/scala/com/lightbend/Main.scala +++ b/native-image-tests/src/main/scala/com/lightbend/Main.scala @@ -1,16 +1,40 @@ package com.lightbend +import akka.actor.ExtendedActorSystem +import akka.actor.ExtensionId import akka.actor.typed.ActorSystem import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.Behaviors import akka.cluster.typed.Cluster import akka.cluster.typed.SelfUp import akka.cluster.typed.Subscribe +import akka.coordination.lease.LeaseSettings import akka.management.scaladsl.AkkaManagement import scala.concurrent.duration.DurationInt object RootBehavior { + + def checkK8Lease(system: ActorSystem[_]): Unit = { + // this throws if not all spray-json metadata in place + new akka.coordination.lease.kubernetes.internal.KubernetesJsonSupport {} + + // we can't really set up the lease but it is expected to be constructed via config/reflection, so let's check access + // that native-image can't guess + val exensionNameClass = system.settings.config.getString("akka.coordination.lease.kubernetes.lease-class") + val clazz = system.dynamicAccess.getClassFor[akka.coordination.lease.scaladsl.Lease](exensionNameClass).get + // we cant really call it though, but would get a NoSuchMethod here if it can't be found + clazz.getConstructor(classOf[LeaseSettings], classOf[ExtendedActorSystem]) + + } + + def checkK8RollingUpdate(system: ActorSystem[_]): Unit = { + // this throws if not all spray-json metadata in place + new akka.rollingupdate.kubernetes.KubernetesJsonSupport {} + val extensionClazzName = system.settings.config.getString("pod-cost-class") + system.dynamicAccess.getObjectFor[ExtensionId[_]](extensionClazzName).get + } + def apply(): Behavior[AnyRef] = Behaviors.setup { context => Behaviors.withTimers { timers => // Note that some exceptions in the log from k8 api discovery is expected, see application.conf @@ -18,8 +42,9 @@ object RootBehavior { timers.startSingleTimer("Timeout", 30.seconds) Cluster(context.system).subscriptions ! Subscribe(context.self, classOf[SelfUp]) - // FIXME cover k8 lease - // FIXME cover rolling-update + // best effort coverage of k8 lease and rolling update without actually using them + checkK8RollingUpdate(context.system) + checkK8Lease(context.system) Behaviors.receiveMessagePartial { case SelfUp(_) => From 04bf0aed96e56320ea71f5ec20a6423198d4612a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Andr=C3=A9n?= Date: Tue, 27 Feb 2024 19:37:50 +0100 Subject: [PATCH 5/7] bump Akka 2.9.2 and Akka HTTP 10.6.1 --- integration-test/dns-api-mesos/build.sbt | 2 +- integration-test/kubernetes-api-java/pom.xml | 4 ++-- native-image-tests/build.sbt | 4 ++-- project/Dependencies.scala | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/integration-test/dns-api-mesos/build.sbt b/integration-test/dns-api-mesos/build.sbt index d25cfc8b8..8c82f1185 100644 --- a/integration-test/dns-api-mesos/build.sbt +++ b/integration-test/dns-api-mesos/build.sbt @@ -14,4 +14,4 @@ libraryDependencies += "com.lightbend.akka.management" %% "akka-management-clust libraryDependencies += "com.lightbend.akka.management" %% "akka-management-cluster-http" % akkaManagementVersion( version.value) -libraryDependencies += "com.typesafe.akka" %% "akka-discovery" % "2.9.0" +libraryDependencies += "com.typesafe.akka" %% "akka-discovery" % "2.9.2" diff --git a/integration-test/kubernetes-api-java/pom.xml b/integration-test/kubernetes-api-java/pom.xml index 02d21f835..ef9bcd574 100644 --- a/integration-test/kubernetes-api-java/pom.xml +++ b/integration-test/kubernetes-api-java/pom.xml @@ -17,8 +17,8 @@ UTF-8 11 - 2.9.0 - 10.6.0 + 2.9.2 + 10.6.1 1.5.0 2.13 diff --git a/native-image-tests/build.sbt b/native-image-tests/build.sbt index d26cd73a4..74d8a5aed 100644 --- a/native-image-tests/build.sbt +++ b/native-image-tests/build.sbt @@ -6,8 +6,8 @@ scalaVersion := "2.13.12" resolvers += "Akka library repository".at("https://repo.akka.io/maven") -lazy val akkaVersion = sys.props.getOrElse("akka.version", "2.9.1") -lazy val akkaHttpVersion = sys.props.getOrElse("akka.http.version", "10.6.0") +lazy val akkaVersion = sys.props.getOrElse("akka.version", "2.9.2") +lazy val akkaHttpVersion = sys.props.getOrElse("akka.http.version", "10.6.1") // Note: this default isn't really used anywhere so not important to bump lazy val akkaManagementVersion = sys.props.getOrElse("akka.management.version", "1.5.0") diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 4d3af8424..786783406 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -8,10 +8,10 @@ object Dependencies { val CrossScalaVersions = Seq(Scala213, Scala3) // Align the versions in integration-test/kubernetes-api-java/pom.xml - val AkkaVersion = "2.9.0" + val AkkaVersion = "2.9.2" val AkkaBinaryVersion = "2.9" // Align the versions in integration-test/kubernetes-api-java/pom.xml - val AkkaHttpVersion = "10.6.0" + val AkkaHttpVersion = "10.6.1" val AkkaHttpBinaryVersion = "10.6" val ScalaTestVersion = "3.2.18" From c77f7c44b1a702ae1b371d4a336f20c2f0aa2a6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Andr=C3=A9n?= Date: Wed, 28 Feb 2024 15:29:07 +0100 Subject: [PATCH 6/7] better logic to pick up snapshot/local version --- .github/workflows/native-image-tests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/native-image-tests.yml b/.github/workflows/native-image-tests.yml index 609e72965..cbbc7e150 100644 --- a/.github/workflows/native-image-tests.yml +++ b/.github/workflows/native-image-tests.yml @@ -35,9 +35,11 @@ jobs: jvm: temurin:1.11 - name: Gather version + # some cleanup of the sbt output to get the version sbt will use when publishing below run: |- - echo `git describe --tags | sed -e "s/v\(.*\)-\([0-9][0-9]*\).*/\\1+\\2-/"``git rev-parse HEAD | head -c8`-SNAPSHOT > ~/.version - cat ~/.version + sbt "akka-management/version" --batch --no-colors | tail -n 1 | cut -f 2 -d ' ' | tr -d '\n' > ~/.version + echo [$(cat ~/.version)] + # useful for debugging: hexdump -c ~/.version - name: Publish artifacts locally run: |- From 6ef752cdff679f01b015fccdf1b659e26ac58348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Andr=C3=A9n?= Date: Wed, 28 Feb 2024 17:23:08 +0100 Subject: [PATCH 7/7] Update native-image.md --- docs/src/main/paradox/native-image.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/paradox/native-image.md b/docs/src/main/paradox/native-image.md index 374e61483..8d1583e5d 100644 --- a/docs/src/main/paradox/native-image.md +++ b/docs/src/main/paradox/native-image.md @@ -1,6 +1,6 @@ # Building Native Images -Building native images with Akka HTTP is supported out of the box for the following modules: +Building native images with Akka Management is supported out of the box for the following modules: * akka-management * akka-management-cluster-bootstrap @@ -11,4 +11,4 @@ Building native images with Akka HTTP is supported out of the box for the follow Other modules can likely be used but will require figuring out and adding additional native-image metadata. -For details about building native images with Akka in general, see the @extref[Akka Documentation](akka:additional/native-image.html). \ No newline at end of file +For details about building native images with Akka in general, see the @extref[Akka Documentation](akka:additional/native-image.html).