diff --git a/client/scala/.bsp/sbt.json b/client/scala/.bsp/sbt.json new file mode 100644 index 00000000000..acdc19473d2 --- /dev/null +++ b/client/scala/.bsp/sbt.json @@ -0,0 +1 @@ +{"name":"sbt","version":"1.10.5","bspVersion":"2.1.0-M1","languages":["scala"],"argv":["/usr/lib/jvm/java-21-openjdk-21.0.4.0.7-2.fc40.x86_64/bin/java","-Xms100m","-Xmx100m","-classpath","/home/clif/.cache/sbt/boot/sbt-launch/1.10.5/sbt-launch-1.10.5.jar","-Dsbt.script=/usr/bin/sbt","xsbt.boot.Boot","-bsp"]} \ No newline at end of file diff --git a/client/scala/scala-armada-client/.gitignore b/client/scala/scala-armada-client/.gitignore new file mode 100644 index 00000000000..9e79245eefc --- /dev/null +++ b/client/scala/scala-armada-client/.gitignore @@ -0,0 +1,32 @@ +# macOS +.DS_Store + +# sbt specific +dist/* +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ +project/local-plugins.sbt +.history +.ensime +.ensime_cache/ +.sbt-scripted/ +local.sbt + +# Bloop +.bsp + +# VS Code +.vscode/ + +# Metals +.bloop/ +.metals/ +metals.sbt + +# IDEA +.idea +.idea_modules +/.worksheet/ diff --git a/client/scala/scala-armada-client/README.md b/client/scala/scala-armada-client/README.md new file mode 100644 index 00000000000..102c5cad5f8 --- /dev/null +++ b/client/scala/scala-armada-client/README.md @@ -0,0 +1,8 @@ +## sbt project compiled with Scala 3 + +### Usage + +This is a normal sbt project. You can compile code with `sbt compile`, run it with `sbt run`, and `sbt console` will start a Scala 3 REPL. + +For more information on the sbt-dotty plugin, see the +[scala3-example-project](https://github.com/scala/scala3-example-project/blob/main/README.md). diff --git a/client/scala/scala-armada-client/build.sbt b/client/scala/scala-armada-client/build.sbt new file mode 100644 index 00000000000..4b278369245 --- /dev/null +++ b/client/scala/scala-armada-client/build.sbt @@ -0,0 +1,21 @@ +val scala3Version = "3.5.2" + +lazy val root = project + .in(file(".")) + .settings( + name := "Scala Armada Client", + version := "0.1.0-SNAPSHOT", + + scalaVersion := scala3Version, + + libraryDependencies += "org.scalameta" %% "munit" % "1.0.0" % Test + ) + +Compile / PB.targets := Seq( + scalapb.gen() -> (Compile / sourceManaged).value +) + +libraryDependencies ++= Seq( + "io.grpc" % "grpc-netty" % scalapb.compiler.Version.grpcJavaVersion, + "com.thesamet.scalapb" %% "scalapb-runtime-grpc" % scalapb.compiler.Version.scalapbVersion +) diff --git a/client/scala/scala-armada-client/project/build.properties b/client/scala/scala-armada-client/project/build.properties new file mode 100644 index 00000000000..db1723b0862 --- /dev/null +++ b/client/scala/scala-armada-client/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.10.5 diff --git a/client/scala/scala-armada-client/project/plugins.sbt b/client/scala/scala-armada-client/project/plugins.sbt new file mode 100644 index 00000000000..ab620c5f468 --- /dev/null +++ b/client/scala/scala-armada-client/project/plugins.sbt @@ -0,0 +1,7 @@ +addSbtPlugin("com.thesamet" % "sbt-protoc" % "1.0.7") + +libraryDependencies += "com.thesamet.scalapb" %% "compilerplugin" % "0.11.13" + +libraryDependencies ++= Seq( + "com.google.protobuf" % "protobuf-java" % "3.13.0" % "protobuf" +) diff --git a/client/scala/scala-armada-client/src/main/protobuf/armada/event.proto b/client/scala/scala-armada-client/src/main/protobuf/armada/event.proto new file mode 100644 index 00000000000..6c1af5ddeaf --- /dev/null +++ b/client/scala/scala-armada-client/src/main/protobuf/armada/event.proto @@ -0,0 +1,296 @@ +syntax = 'proto3'; + +package api; +option go_package = "github.com/armadaproject/armada/pkg/api"; +option csharp_namespace = "ArmadaProject.Io.Api"; + +import "google/protobuf/timestamp.proto"; +import "armada/submit.proto"; +import "armada/health.proto"; +import "google/protobuf/empty.proto"; +import "google/api/annotations.proto"; +import "k8s.io/apimachinery/pkg/api/resource/generated.proto"; + +message JobSubmittedEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + Job job = 5; +} + +message JobQueuedEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; +} + +message JobLeasedEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + string cluster_id = 5; +} + +message JobLeaseReturnedEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + string cluster_id = 5; + string reason = 6; + string kubernetes_id = 7; + int32 pod_number = 8; + bool run_attempted = 9; +} + +message JobLeaseExpiredEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; +} + +message JobPendingEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + string cluster_id = 5; + string kubernetes_id = 6; + int32 pod_number = 7; + string pod_name = 8; + string pod_namespace = 9; +} + +message JobRunningEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + string cluster_id = 5; + string kubernetes_id = 6; + string node_name = 7; + int32 pod_number = 8; + string pod_name = 9; + string pod_namespace = 10; +} + +message JobIngressInfoEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + string cluster_id = 5; + string kubernetes_id = 6; + string node_name = 7; + int32 pod_number = 8; + string pod_name = 10; + string pod_namespace = 11; + map ingress_addresses = 9; +} + +message JobUnableToScheduleEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + string cluster_id = 5; + string reason = 6; + string kubernetes_id = 7; + string node_name = 8; + int32 pod_number = 9; + string pod_name = 10; + string pod_namespace = 11; +} + +message JobFailedEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + string cluster_id = 5; + string reason = 6; + map exit_codes = 7 [deprecated = true]; + string kubernetes_id = 8; + string node_name = 9; + int32 pod_number = 10; + string pod_name = 13; + string pod_namespace = 14; + repeated ContainerStatus container_statuses = 11; + Cause cause = 12; +} + +message JobPreemptingEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + string requestor = 5; + string reason = 6; +} + +message JobPreemptedEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + string cluster_id = 5; + string run_id = 6; + string preemptive_job_id = 7; + string preemptive_run_id = 8; + string reason = 9; +} + +message JobSucceededEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + string cluster_id = 5; + string kubernetes_id = 6; + string node_name = 7; + int32 pod_number = 8; + string pod_name = 9; + string pod_namespace = 10; +} + +message JobUtilisationEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + string cluster_id = 5; + string kubernetes_id = 6; + map MaxResourcesForPeriod = 7; + string node_name = 8; + int32 pod_number = 9; + string pod_name = 10; + string pod_namespace = 11; + map total_cumulative_usage = 12; +} + +message JobReprioritizingEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + double new_priority = 5; + string requestor = 6; +} + +message JobReprioritizedEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + double new_priority = 5; + string requestor = 6; +} + +message JobCancellingEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + string requestor = 5; + string reason = 6; +} + +message JobCancelledEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + string requestor = 5; + string reason = 6; +} + +message JobTerminatedEvent { + string job_id = 1; + string job_set_id = 2; + string queue = 3; + google.protobuf.Timestamp created = 4; + string cluster_id = 5; + string kubernetes_id = 6; + int32 pod_number = 7; + string pod_name = 9; + string pod_namespace = 10; + string reason = 8; +} + + +message EventMessage { + oneof events { + JobSubmittedEvent submitted = 1; + JobQueuedEvent queued = 2; + JobLeasedEvent leased = 3; + JobLeaseReturnedEvent lease_returned = 4; + JobLeaseExpiredEvent lease_expired = 5; + JobPendingEvent pending = 6; + JobRunningEvent running = 7; + JobUnableToScheduleEvent unable_to_schedule = 8; + JobFailedEvent failed = 9; + JobSucceededEvent succeeded = 10; + JobReprioritizedEvent reprioritized = 11; + JobCancellingEvent cancelling = 12; + JobCancelledEvent cancelled = 13; + JobUtilisationEvent utilisation = 15; + JobIngressInfoEvent ingress_info = 17; + JobReprioritizingEvent reprioritizing = 18; + JobPreemptedEvent preempted = 21; + JobPreemptingEvent preempting = 22; + } +} + +enum Cause { + Error = 0; + Evicted = 1; + OOM = 2; + DeadlineExceeded = 3; + Rejected = 4; +} + +message ContainerStatus { + string name = 1; + int32 exitCode = 2; + string message = 3; + string reason = 4; + Cause cause = 5; +} + +// swagger:model +message EventStreamMessage { + string id = 1; + EventMessage message = 2; +} + +// swagger:model +message JobSetRequest { + string id = 1; + bool watch = 2; + string from_message_id = 3; + string queue = 4; + bool errorIfMissing = 5; +} + +message WatchRequest { + string queue = 1; + string job_set_id = 2; + string from_id = 3; +} + +service Event { + rpc GetJobSetEvents (JobSetRequest) returns (stream EventStreamMessage) { + option (google.api.http) = { + post: "/v1/job-set/{queue}/{id}" + body: "*" + }; + } + rpc Watch (WatchRequest) returns (stream EventStreamMessage) { + option deprecated = true; + } + rpc Health(google.protobuf.Empty) returns (HealthCheckResponse); +} diff --git a/client/scala/scala-armada-client/src/main/protobuf/armada/health.proto b/client/scala/scala-armada-client/src/main/protobuf/armada/health.proto new file mode 120000 index 00000000000..315066782a0 --- /dev/null +++ b/client/scala/scala-armada-client/src/main/protobuf/armada/health.proto @@ -0,0 +1 @@ +../../../../../../../proto/armada/health.proto \ No newline at end of file diff --git a/client/scala/scala-armada-client/src/main/protobuf/armada/submit.proto b/client/scala/scala-armada-client/src/main/protobuf/armada/submit.proto new file mode 120000 index 00000000000..9ef65ec8681 --- /dev/null +++ b/client/scala/scala-armada-client/src/main/protobuf/armada/submit.proto @@ -0,0 +1 @@ +../../../../../../../proto/armada/submit.proto \ No newline at end of file diff --git a/client/scala/scala-armada-client/src/main/protobuf/google b/client/scala/scala-armada-client/src/main/protobuf/google new file mode 120000 index 00000000000..77f72d1e538 --- /dev/null +++ b/client/scala/scala-armada-client/src/main/protobuf/google @@ -0,0 +1 @@ +../../../../../../proto/google/ \ No newline at end of file diff --git a/client/scala/scala-armada-client/src/main/protobuf/k8s.io b/client/scala/scala-armada-client/src/main/protobuf/k8s.io new file mode 120000 index 00000000000..ef4e7171f02 --- /dev/null +++ b/client/scala/scala-armada-client/src/main/protobuf/k8s.io @@ -0,0 +1 @@ +../../../../../../proto/k8s.io \ No newline at end of file diff --git a/client/scala/scala-armada-client/src/main/scala/ArmadaClient.scala b/client/scala/scala-armada-client/src/main/scala/ArmadaClient.scala new file mode 100644 index 00000000000..1c374b3b1c2 --- /dev/null +++ b/client/scala/scala-armada-client/src/main/scala/ArmadaClient.scala @@ -0,0 +1 @@ +import api.submit diff --git a/client/scala/scala-armada-client/src/main/scala/Main.scala b/client/scala/scala-armada-client/src/main/scala/Main.scala new file mode 100644 index 00000000000..bfb8166e1b3 --- /dev/null +++ b/client/scala/scala-armada-client/src/main/scala/Main.scala @@ -0,0 +1,10 @@ +import api.submit.Job + +@main def hello(): Unit = + println("Hello world!") + + Job(id = "test ID") + + println(msg) + +def msg = "I was compiled by Scala 3. :) api.submit.Job exists" diff --git a/client/scala/scala-armada-client/src/test/scala/MySuite.scala b/client/scala/scala-armada-client/src/test/scala/MySuite.scala new file mode 100644 index 00000000000..621784d17d5 --- /dev/null +++ b/client/scala/scala-armada-client/src/test/scala/MySuite.scala @@ -0,0 +1,9 @@ +// For more information on writing tests, see +// https://scalameta.org/munit/docs/getting-started.html +class MySuite extends munit.FunSuite { + test("example test that succeeds") { + val obtained = 42 + val expected = 42 + assertEquals(obtained, expected) + } +}