diff --git a/Dockerfile-activator-api b/Dockerfile-activator-api index d155e7ba6..8ab1884a6 100644 --- a/Dockerfile-activator-api +++ b/Dockerfile-activator-api @@ -31,7 +31,7 @@ ENV RELEASE_NAME="activator_api" # This will be the full nodename ENV RELEASE_NODE="${RELEASE_NAME}@${POD_IP}" -RUN echo "-setcookie ${NODE_COOKIE}" >> ./rel/vm.args.eex +RUN echo "-setcookie ${RELEASE_COOKIE}" >> ./rel/vm.args.eex RUN cd spawn_activators/activator_api \ && mix deps.get \ diff --git a/Dockerfile-activator-kafka b/Dockerfile-activator-kafka index 23c515e41..28baafea4 100644 --- a/Dockerfile-activator-kafka +++ b/Dockerfile-activator-kafka @@ -31,7 +31,7 @@ ENV RELEASE_NAME="activator_kafka" # This will be the full nodename ENV RELEASE_NODE="${RELEASE_NAME}@${POD_IP}" -RUN echo "-setcookie ${NODE_COOKIE}" >> ./rel/vm.args.eex +RUN echo "-setcookie ${RELEASE_COOKIE}" >> ./rel/vm.args.eex RUN cd spawn_activators/activator_kafka \ && mix deps.get \ diff --git a/Dockerfile-activator-pubsub b/Dockerfile-activator-pubsub index 4c076e9df..c79ed239d 100644 --- a/Dockerfile-activator-pubsub +++ b/Dockerfile-activator-pubsub @@ -31,7 +31,7 @@ ENV RELEASE_NAME="activator_pubsub" # This will be the full nodename ENV RELEASE_NODE="${RELEASE_NAME}@${POD_IP}" -RUN echo "-setcookie ${NODE_COOKIE}" >> ./rel/vm.args.eex +RUN echo "-setcookie ${RELEASE_COOKIE}" >> ./rel/vm.args.eex RUN cd spawn_activators/activator_pubsub \ && mix deps.get \ diff --git a/Dockerfile-activator-rabbitmq b/Dockerfile-activator-rabbitmq index 59563eefe..7776af9b7 100644 --- a/Dockerfile-activator-rabbitmq +++ b/Dockerfile-activator-rabbitmq @@ -31,7 +31,7 @@ ENV RELEASE_NAME="activator_rabbitmq" # This will be the full nodename ENV RELEASE_NODE="${RELEASE_NAME}@${POD_IP}" -RUN echo "-setcookie ${NODE_COOKIE}" >> ./rel/vm.args.eex +RUN echo "-setcookie ${RELEASE_COOKIE}" >> ./rel/vm.args.eex RUN cd spawn_activators/activator_rabbitmq \ && mix deps.get \ diff --git a/Dockerfile-activator-sqs b/Dockerfile-activator-sqs index 8dec77d04..bea99014d 100644 --- a/Dockerfile-activator-sqs +++ b/Dockerfile-activator-sqs @@ -31,7 +31,7 @@ ENV RELEASE_NAME="activator_sqs" # This will be the full nodename ENV RELEASE_NODE="${RELEASE_NAME}@${POD_IP}" -RUN echo "-setcookie ${NODE_COOKIE}" >> ./rel/vm.args.eex +RUN echo "-setcookie ${RELEASE_COOKIE}" >> ./rel/vm.args.eex RUN cd spawn_activators/activator_sqs \ && mix deps.get \ diff --git a/Dockerfile-elixir-example b/Dockerfile-elixir-example index b72f08953..1cba0eda8 100644 --- a/Dockerfile-elixir-example +++ b/Dockerfile-elixir-example @@ -21,7 +21,7 @@ RUN mix local.rebar --force \ && mix release.init RUN echo "-name spawn_sdk_elixir@${HOSTNAME}" >> ./rel/vm.args.eex \ - && echo "-setcookie ${NODE_COOKIE}" >> ./rel/vm.args.eex + && echo "-setcookie ${RELEASE_COOKIE}" >> ./rel/vm.args.eex RUN cd spawn_sdk/spawn_sdk_example \ && mix deps.get \ diff --git a/Dockerfile-initializer b/Dockerfile-initializer index 903f36727..b3140100b 100644 --- a/Dockerfile-initializer +++ b/Dockerfile-initializer @@ -13,15 +13,11 @@ RUN mix local.rebar --force \ && mix deps.get \ && mix release.init -# Overriden at runtime -ENV POD_IP="127.0.0.1" - # This will be the basename of node ENV RELEASE_NAME="spawn_initializer" -# This will be the full nodename -ENV RELEASE_NODE="${RELEASE_NAME}@${POD_IP}" - +# Disable Erlang Dist +ENV RELEASE_DISTRIBUTION=none RUN mix deps.get \ && mix release spawn_initializer diff --git a/Dockerfile-operator b/Dockerfile-operator index b1399e04d..7750ab6f5 100644 --- a/Dockerfile-operator +++ b/Dockerfile-operator @@ -31,7 +31,7 @@ ENV RELEASE_NAME="spawn_operator" # This will be the full nodename ENV RELEASE_NODE="${RELEASE_NAME}@${POD_IP}" -RUN echo "-setcookie ${NODE_COOKIE}" >> ./rel/vm.args.eex +RUN echo "-setcookie ${RELEASE_COOKIE}" >> ./rel/vm.args.eex RUN cd spawn_operator/spawn_operator \ && mix deps.get \ diff --git a/Dockerfile-proxy b/Dockerfile-proxy index b97bd317a..19107e10e 100644 --- a/Dockerfile-proxy +++ b/Dockerfile-proxy @@ -31,7 +31,7 @@ ENV RELEASE_NAME="proxy" # This will be the full nodename ENV RELEASE_NODE="${RELEASE_NAME}@${POD_IP}" -#RUN echo "-setcookie ${NODE_COOKIE}" >> ./priv/rel/vm.args.eex +#RUN echo "-setcookie ${RELEASE_COOKIE}" >> ./priv/rel/vm.args.eex RUN cd spawn_proxy/proxy \ && mix deps.get \ diff --git a/Makefile b/Makefile index 3f85a43da..28e746980 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ -version=1.4.3 +#version=1.4.3 +version=1.4.4-rc.38 registry=eigr CLUSTER_NAME=spawn-k8s @@ -65,10 +66,7 @@ build-all-images: #docker build --no-cache -f Dockerfile-elixir-example -t ${spawn-sdk-example-image} . test-spawn: - MIX_ENV=test PROXY_DATABASE_TYPE=mariadb PROXY_DATABASE_PORT=3307 PROXY_DATABASE_POOL_SIZE=15 PROXY_CLUSTER_STRATEGY=gossip PROXY_HTTP_PORT=9005 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= elixir --name spawn@127.0.0.1 -S mix test - -test-statestores_mysql: - cd spawn_statestores/statestores_mysql && MIX_ENV=test mix deps.get && MIX_ENV=test PROXY_CLUSTER_STRATEGY=gossip PROXY_HTTP_PORT=9005 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= elixir --name spawn@127.0.0.1 -S mix test + MIX_ENV=test SPAWN_PROXY_LOGGER_LEVEL=info SPAWN_PROXY_TASK_CONFIG="PMRHIYLTNNAWG5DPOJZSEOS3PMRGCY3UN5ZE4YLNMURDUISKN5ZWKIRMEJ2G64DPNRXWO6JCHJ5SE3TPMRSVGZLMMVRXI33SEI5HWITHOB2SEORCMZQWY43FEJ6X27K5PU======" PROXY_DATABASE_TYPE=native PROXY_DATABASE_PORT=3307 PROXY_DATABASE_POOL_SIZE=15 PROXY_CLUSTER_STRATEGY=gossip PROXY_HTTP_PORT=9005 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= elixir --name spawn@127.0.0.1 -S mix test test-statestores_mariadb: cd spawn_statestores/statestores_mariadb && MIX_ENV=test mix deps.get && MIX_ENV=test PROXY_DATABASE_TYPE=mariadb PROXY_DATABASE_PORT=3307 PROXY_CLUSTER_STRATEGY=gossip PROXY_HTTP_PORT=9005 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= elixir --name spawn@127.0.0.1 -S mix test @@ -76,26 +74,20 @@ test-statestores_mariadb: test-statestores_postgres: cd spawn_statestores/statestores_postgres && MIX_ENV=test mix deps.get && MIX_ENV=test PROXY_CLUSTER_STRATEGY=gossip PROXY_HTTP_PORT=9005 PROXY_DATABASE_USERNAME=postgres PROXY_DATABASE_SECRET=postgres SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= elixir --name spawn@127.0.0.1 -S mix test -test-statestores_mssql: - cd spawn_statestores/statestores_mssql && MIX_ENV=test mix deps.get && MIX_ENV=test PROXY_CLUSTER_STRATEGY=gossip PROXY_HTTP_PORT=9005 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= elixir --name spawn@127.0.0.1 -S mix test - -test-statestores_sqlite: - cd spawn_statestores/statestores_sqlite && MIX_ENV=test mix deps.get && MIX_ENV=test PROXY_CLUSTER_STRATEGY=gossip PROXY_HTTP_PORT=9005 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= elixir --name spawn@127.0.0.1 -S mix test - test-statestores_native: cd spawn_statestores/statestores_native && MIX_ENV=test mix deps.get && MIX_ENV=test PROXY_CLUSTER_STRATEGY=gossip PROXY_HTTP_PORT=9005 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= elixir --name spawn@127.0.0.1 -S mix test test-spawn-sdk: - cd spawn_sdk/spawn_sdk && MIX_ENV=test mix deps.get && MIX_ENV=test PROXY_DATABASE_TYPE=mariadb PROXY_DATABASE_PORT=3307 PROXY_CLUSTER_STRATEGY=gossip PROXY_HTTP_PORT=9005 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= PROXY_ACTOR_SYSTEM_NAME=spawn-system elixir --name spawn_test@127.0.0.1 -S mix test + cd spawn_sdk/spawn_sdk && MIX_ENV=test mix deps.get && MIX_ENV=test SPAWN_PROXY_USE_DEFAULT_FLAME_POOL=true PROXY_DATABASE_TYPE=native PROXY_DATABASE_PORT=3307 PROXY_CLUSTER_STRATEGY=gossip PROXY_HTTP_PORT=9005 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= PROXY_ACTOR_SYSTEM_NAME=spawn-system elixir --name spawn_test@127.0.0.1 -S mix test test-invoke-backpressure: - cd spawn_sdk/spawn_sdk && MIX_ENV=test mix deps.get && MIX_ENV=test PROXY_DATABASE_TYPE=mariadb PROXY_DATABASE_POOL_SIZE=50 PROXY_DATABASE_PORT=3307 PROXY_CLUSTER_STRATEGY=gossip PROXY_HTTP_PORT=9005 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= PROXY_ACTOR_SYSTEM_NAME=spawn-system elixir --name spawn_test@127.0.0.1 -S mix test ./test/actor/actor_test.exs --only parallel:true + cd spawn_sdk/spawn_sdk && MIX_ENV=test mix deps.get && MIX_ENV=test PROXY_DATABASE_TYPE=native PROXY_DATABASE_POOL_SIZE=50 PROXY_DATABASE_PORT=3307 PROXY_CLUSTER_STRATEGY=gossip PROXY_HTTP_PORT=9005 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= PROXY_ACTOR_SYSTEM_NAME=spawn-system elixir --name spawn_test@127.0.0.1 -S mix test ./test/actor/actor_test.exs --only parallel:true test-operator: - cd spawn_operator/spawn_operator && MIX_ENV=test mix deps.get && MIX_ENV=test PROXY_DATABASE_TYPE=mysql PROXY_CLUSTER_STRATEGY=gossip PROXY_HTTP_PORT=9005 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= elixir --name spawn@127.0.0.1 -S mix test + cd spawn_operator/spawn_operator && MIX_ENV=test mix deps.get && MIX_ENV=test PROXY_DATABASE_TYPE=native PROXY_CLUSTER_STRATEGY=gossip PROXY_HTTP_PORT=9005 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= elixir --name spawn@127.0.0.1 -S mix test test-proxy: - cd spawn_proxy/proxy && MIX_ENV=test mix deps.get && MIX_ENV=test SPAWN_PROXY_LOGGER_LEVEL=debug PROXY_DATABASE_TYPE=mariadb PROXY_DATABASE_PORT=3307 PROXY_CLUSTER_STRATEGY=gossip PROXY_HTTP_PORT=9005 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= elixir --name spawn@127.0.0.1 -S mix test + cd spawn_proxy/proxy && MIX_ENV=test mix deps.get && MIX_ENV=test SPAWN_PROXY_LOGGER_LEVEL=debug PROXY_DATABASE_TYPE=native PROXY_DATABASE_PORT=3307 PROXY_CLUSTER_STRATEGY=gossip PROXY_HTTP_PORT=9005 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= elixir --name spawn@127.0.0.1 -S mix test run-benchmark: cd spawn_sdk/spawn_sdk_example && mix deps.get && SPAWN_PROXY_LOGGER_LEVEL=info PROXY_CLUSTER_STRATEGY=gossip PROXY_DATABASE_TYPE=mariadb PROXY_DATABASE_PORT=3307 PROXY_DATABASE_POOL_SIZE=50 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= iex --name spawn@127.0.0.1 -S mix run benchmark.exs @@ -161,7 +153,7 @@ run-proxy-local2: ERL_ZFLAGS='-proto_dist inet_tls -ssl_dist_optfile rel/overlays/local-mtls.ssl.conf' cd spawn_proxy/proxy && mix deps.get && PROXY_DATABASE_TYPE=$(database) PROXY_HTTP_PORT=9003 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= iex --name spawn_a2@test.default.svc -S mix run-proxy-local-3: - cd spawn_proxy/proxy && mix deps.get && OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4317 OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=grpc OTEL_EXPORTER_OTLP_TRACES_COMPRESSION=gzip SPAWN_PROXY_LOGGER_LEVEL=info PROXY_CLUSTER_STRATEGY=epmd SPAWN_USE_INTERNAL_NATS=false SPAWN_PUBSUB_ADAPTER=native PROXY_DATABASE_PORT=3307 PROXY_DATABASE_TYPE=native PROXY_DATABASE_POOL_SIZE=30 PROXY_HTTP_PORT=9001 USER_FUNCTION_PORT=8090 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= iex --name spawn_a3@127.0.0.1 -S mix + cd spawn_proxy/proxy && mix deps.get && OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4317 OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=grpc OTEL_EXPORTER_OTLP_TRACES_COMPRESSION=gzip PROXY_CLUSTER_STRATEGY=epmd SPAWN_USE_INTERNAL_NATS=false SPAWN_PUBSUB_ADAPTER=native PROXY_DATABASE_PORT=3307 PROXY_DATABASE_TYPE=native PROXY_DATABASE_POOL_SIZE=30 PROXY_HTTP_PORT=9001 USER_FUNCTION_PORT=8090 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= iex --name spawn_a3@127.0.0.1 -S mix run-proxy-local-nodejs-test: ERL_ZFLAGS='-proto_dist inet_tls -ssl_dist_optfile rel/overlays/local-mtls.ssl.conf' cd spawn_proxy/proxy && mix deps.get && PROXY_DATABASE_TYPE=$(database) PROXY_HTTP_PORT=9001 SPAWN_STATESTORE_KEY=3Jnb0hZiHIzHTOih7t2cTEPEpY98Tu1wvQkPfq/XwqE= PROXY_ACTOR_SYSTEM_NAME=SpawnSysTest SPAWN_SUPERVISORS_STATE_HANDOFF_CONTROLLER=crdt iex --name spawn_a1@test.default.svc -S mix diff --git a/compile-pb.sh b/compile-pb.sh index 787be7f14..8b87e3c2e 100755 --- a/compile-pb.sh +++ b/compile-pb.sh @@ -4,7 +4,7 @@ set -o nounset set -o errexit set -o pipefail -protoc --elixir_out=gen_descriptors=true,plugins=grpc:./lib/spawn/grpc --proto_path=priv/protos/grpc/ priv/protos/grpc/reflection/v1alpha/reflection.proto +#protoc --elixir_out=gen_descriptors=true,plugins=grpc:./lib/spawn/grpc --proto_path=priv/protos/grpc/ priv/protos/grpc/reflection/v1alpha/reflection.proto # protoc --elixir_out=gen_descriptors=true:./lib/spawn/google/protobuf --proto_path=priv/protos/google/protobuf priv/protos/google/protobuf/any.proto # protoc --elixir_out=gen_descriptors=true:./lib/spawn/google/protobuf --proto_path=priv/protos/google/protobuf priv/protos/google/protobuf/empty.proto @@ -48,7 +48,7 @@ for file in $PROTOS; do --include-path=$BASE_PATH/priv/protos/ \ --include-path=./priv/protos/google/protobuf \ --include-path=./priv/protos/google/api \ - --plugins=ProtobufGenerate.Plugins.GRPCWithOptions \ + --plugin=ProtobufGenerate.Plugins.GRPCWithOptions \ --one-file-per-module \ $BASE_PATH/$file done \ No newline at end of file diff --git a/config/config.exs b/config/config.exs index 78f09cd32..1b2a855f9 100644 --- a/config/config.exs +++ b/config/config.exs @@ -4,6 +4,11 @@ config :do_it, DoIt.Commfig, dirname: System.tmp_dir(), filename: "spawn_cli.json" +config :flame, :terminator, + shutdown_timeout: :timer.minutes(3), + failsafe_timeout: :timer.minutes(1), + log: :debug + # config :spawn_statestores, Statestores.Vault, # json_library: Jason, # ciphers: [ diff --git a/examples/helloworld-ts/host.yaml b/examples/helloworld-ts/host.yaml new file mode 100644 index 000000000..aba75d0a2 --- /dev/null +++ b/examples/helloworld-ts/host.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: spawn-eigr.io/v1 +kind: ActorHost +metadata: + name: flame + namespace: default + annotations: + spawn-eigr.io/actor-system: spawn-system + spawn-eigr.io/sidecar-logger-level: debug + spawn-eigr.io/sidecar-image-tag: "docker.io/eigr/spawn-proxy:1.4.4-rc.38" + spawn-eigr.io/sidecar-init-container-image-tag: "docker.io/eigr/spawn-initializer:1.4.4-rc.38" +spec: + replicas: 2 + host: + image: eigr/helloworld:2 + taskActors: + - actorName: TaskActor + workerPool: + min: 0 + ports: + - name: http + containerPort: 8091 diff --git a/examples/helloworld-ts/system.yaml b/examples/helloworld-ts/system.yaml new file mode 100644 index 000000000..1ab851a73 --- /dev/null +++ b/examples/helloworld-ts/system.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: spawn-eigr.io/v1 +kind: ActorSystem +metadata: + name: spawn-system + namespace: default +spec: + statestore: + type: Native + credentialsSecretRef: mariadb-connection-secret diff --git a/examples/security/acl/host.yaml b/examples/security/acl/host.yaml index f373197cd..32ac82d0e 100644 --- a/examples/security/acl/host.yaml +++ b/examples/security/acl/host.yaml @@ -9,7 +9,7 @@ metadata: spawn-eigr.io/sidecar-http-port: "9001" spawn-eigr.io/sidecar-pubsub-adapter: "nats" spawn-eigr.io/sidecar-pubsub-nats-hosts: "nats://spawn-nats:4222" - spawn-eigr.io/sidecar-image-tag: "docker.io/eigr/spawn-proxy:1.4.3" + spawn-eigr.io/sidecar-image-tag: "ghcr.io/eigr/spawn-proxy:1.4.3" spec: autoscaler: max: 3 diff --git a/examples/security/authentication/basic/host.yaml b/examples/security/authentication/basic/host.yaml index 139dfb405..871f9f9f3 100644 --- a/examples/security/authentication/basic/host.yaml +++ b/examples/security/authentication/basic/host.yaml @@ -34,7 +34,7 @@ metadata: spawn-eigr.io/sidecar-http-port: "9001" spawn-eigr.io/sidecar-pubsub-adapter: "nats" spawn-eigr.io/sidecar-pubsub-nats-hosts: "nats://spawn-nats:4222" - spawn-eigr.io/sidecar-image-tag: "docker.io/eigr/spawn-proxy:1.4.3" + spawn-eigr.io/sidecar-image-tag: "ghcr.io/eigr/spawn-proxy:1.4.3" spec: autoscaler: max: 3 diff --git a/examples/security/authentication/jwt/host.yaml b/examples/security/authentication/jwt/host.yaml index 30cb87a81..72e6dba5d 100644 --- a/examples/security/authentication/jwt/host.yaml +++ b/examples/security/authentication/jwt/host.yaml @@ -11,7 +11,7 @@ metadata: spawn-eigr.io/sidecar-http-port: "9001" spawn-eigr.io/sidecar-pubsub-adapter: "nats" spawn-eigr.io/sidecar-pubsub-nats-hosts: "nats://spawn-nats:4222" - spawn-eigr.io/sidecar-image-tag: "docker.io/eigr/spawn-proxy:1.4.3" + spawn-eigr.io/sidecar-image-tag: "ghcr.io/eigr/spawn-proxy:1.4.3" spec: autoscaler: max: 3 diff --git a/examples/simple/host-simple.yaml b/examples/simple/host-simple.yaml index 86ea46b63..713de3d89 100644 --- a/examples/simple/host-simple.yaml +++ b/examples/simple/host-simple.yaml @@ -14,7 +14,7 @@ metadata: # Optional. Here I`m using Nats Broker without authentication spawn-eigr.io/sidecar-pubsub-adapter: "nats" spawn-eigr.io/sidecar-pubsub-nats-hosts: "nats://spawn-nats:4222" - spawn-eigr.io/sidecar-image-tag: "docker.io/eigr/spawn-proxy:1.4.3" + spawn-eigr.io/sidecar-image-tag: "ghcr.io/eigr/spawn-proxy:1.4.3" spec: host: image: eigr/spawn-springboot-examples:0.5.3 # Mandatory diff --git a/examples/simple/host.yaml b/examples/simple/host.yaml index ac7ac1d2f..6c066287d 100644 --- a/examples/simple/host.yaml +++ b/examples/simple/host.yaml @@ -22,7 +22,7 @@ metadata: spawn-eigr.io/sidecar-mode: "sidecar" # Optional - spawn-eigr.io/sidecar-image-tag: "docker.io/eigr/spawn-proxy:1.4.3" + spawn-eigr.io/sidecar-image-tag: "ghcr.io/eigr/spawn-proxy:1.4.3" # Optional. Default 9001 spawn-eigr.io/sidecar-http-port: "9001" diff --git a/examples/topology/host.yaml b/examples/topology/host.yaml new file mode 100644 index 000000000..afe6f7eae --- /dev/null +++ b/examples/topology/host.yaml @@ -0,0 +1,66 @@ +--- +apiVersion: spawn-eigr.io/v1 +kind: ActorHost +metadata: + name: topology-example # Mandatory. Name of the Node containing Actor Host Functions + namespace: default # Optional. Default namespace is "default" + annotations: + # Mandatory. Name of the ActorSystem declared in ActorSystem CRD + spawn-eigr.io/actor-system: spawn-system +spec: + topology: + # affinity: + # podAffinity: + # preferredDuringSchedulingIgnoredDuringExecution: + # - weight: 50 + # podAffinityTerm: + # labelSelector: + # matchExpressions: + # - key: actor-system + # operator: In + # values: + # - system + # topologyKey: kubernetes.io/hostname + + # podAntiAffinity: + # preferredDuringSchedulingIgnoredDuringExecution: + # - weight: 100 + # podAffinityTerm: + # labelSelector: + # matchExpressions: + # - key: app + # operator: In + # values: + # - app_name + # topologyKey: kubernetes.io/hostname + nodeSelector: + gpu: "false" + tolerations: + - key: "cpu-machines" + operator: "Exists" + effect: "NoExecute" + host: + image: eigr/spawn-springboot-examples:0.5.5 # Mandatory + # this configure podTemplate for Task Actors + taskActors: + - actorName: Jose + workerPool: + min: 0 + max: 10 + maxConcurrency: 100 + bootTimeout: 30000 + callTimeout: 30000 + oneOff: "false" + idleShutdownAfter: 30000 + topology: + nodeSelector: + gpu: "true" + tolerations: + - key: "gpu-machines" + operator: "Exists" + effect: "NoExecute" + resources: + - actorName: Franchesco + topology: + nodeSelector: + beam: "true" diff --git a/examples/topology/system.yaml b/examples/topology/system.yaml new file mode 100644 index 000000000..8983ccee2 --- /dev/null +++ b/examples/topology/system.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: spawn-eigr.io/v1 +kind: ActorSystem +metadata: + name: spawn-system # Mandatory. Name of the state store + namespace: default # Optional. Default namespace is "default" +spec: + externalInvocation: + enabled: "true" + externalConnectorRef: invocation-connection-ref-secret + statestore: + type: MySql + credentialsSecretRef: mysql-connection-secret # The secret containing connection params + pool: # Optional + size: "10" diff --git a/lib/actors/actor/caller_consumer.ex b/lib/actors/actor/caller_consumer.ex index 187ad3cea..fe4a36fd4 100644 --- a/lib/actors/actor/caller_consumer.ex +++ b/lib/actors/actor/caller_consumer.ex @@ -515,7 +515,7 @@ defmodule Actors.Actor.CallerConsumer do # Instead of using Map.get/3, which performs a lookup twice, we use pattern matching timeout = case metadata["request-timeout"] do - nil -> 10_000 + nil -> 60_000 value -> value end diff --git a/lib/actors/actor/entity/entity.ex b/lib/actors/actor/entity/entity.ex index a42e850d6..a3d137e4a 100644 --- a/lib/actors/actor/entity/entity.ex +++ b/lib/actors/actor/entity/entity.ex @@ -76,9 +76,14 @@ defmodule Actors.Actor.Entity do alias Eigr.Functions.Protocol.Actors.Healthcheck.HealthCheckReply alias Eigr.Functions.Protocol.Actors.Healthcheck.Status, as: HealthcheckStatus + alias Eigr.Functions.Protocol.ActorInvocationResponse + alias Eigr.Functions.Protocol.State.Checkpoint alias Eigr.Functions.Protocol.State.Revision + alias Spawn.Cluster.Provisioner.Scheduler, as: FlameScheduler + alias Spawn.Cluster.Provisioner.SpawnTask + import Spawn.Utils.Common, only: [return_and_maybe_hibernate: 1] @default_call_timeout :infinity @@ -123,21 +128,140 @@ defmodule Actors.Actor.Entity do |> return_and_maybe_hibernate() end + @doc """ + Handles different types of incoming `call` actions for an actor process. + + This function is responsible for processing actions sent to the actor. It distinguishes between invocation requests + and other default actions. The main action handled is the `:invocation_request`, which includes logic for task-based + actors and synchronous execution. + + ### Parameters: + - `action`: Represents the action to be handled by the actor. It can be a tuple of `{:invocation_request, invocation, opts}` + or other actions. + - `from`: The caller process that made the request, usually a tuple containing the PID and a reference. + - `state`: The current state of the actor, typically passed in a packed format and unpacked at the start of the function. + + ### Action Handling: + - `{:invocation_request, invocation, opts}`: + - If the actor is of kind `:TASK`, the function schedules the task execution remotely using `FlameScheduler.schedule_and_invoke/2`. + - For non-task actors, the function directly invokes the action using `Invocation.invoke/2`. + - Default actions are delegated to `do_handle_defaults/3`. + + ### Return Value: + The function returns a packed response, which is either the result of an invocation or the default action handling. + The response is further processed by `parse_packed_response/1`. + + ### Workflow: + 1. The actor's state is unpacked via `EntityState.unpack/1`. + 2. Based on the `action`: + - For `{:invocation_request, invocation, opts}`, the function checks if the actor is of kind `:TASK`: + - If true, the invocation is handled remotely with the remote task scheduler (`FlameScheduler`), ensuring proper remote invocation while maintaining local state consistency. + - Otherwise, the action is invoked locally. + - For other actions, `do_handle_defaults/3` is called to manage additional behaviors. + 3. The final response is returned as a packed structure after being processed by `parse_packed_response/1`. + + ### See Also: + - `handle_invocation_request/4` + - `schedule_task_invocation/3` + - `FlameScheduler.schedule_and_invoke/2` + - `Invocation.invoke/2` + """ @impl true def handle_call(action, from, state) do state = EntityState.unpack(state) case action do {:invocation_request, invocation, opts} -> - opts = Keyword.merge(opts, from_pid: from) - Invocation.invoke({invocation, opts}, state) + handle_invocation_request(invocation, opts, from, state) - action -> + _ -> do_handle_defaults(action, from, state) end |> parse_packed_response() end + defp handle_invocation_request(invocation, opts, from, state) do + opts = Keyword.merge(opts, from_pid: from) + + case state.actor.settings.kind do + :TASK -> + schedule_task_invocation(invocation, opts, state) + + _ -> + Invocation.invoke({invocation, opts}, state) + |> then(fn + {:ok, source_request, dest_response, updated_state, source_opts} -> + Invocation.handle_response(source_request, dest_response, updated_state, source_opts) + + res -> + res + end) + end + end + + defp schedule_task_invocation(invocation, opts, state) do + task_opts = Keyword.merge(opts, timeout: :infinity) + request_type = Keyword.get(opts, :async, false) + + %SpawnTask{ + actor_name: state.actor.id.name, + invocation: invocation, + opts: task_opts, + state: state, + async: request_type + } + |> FlameScheduler.schedule_and_invoke(&Invocation.invoke/2) + |> then(fn + {:ok, source_request, dest_response, updated_state, source_opts} -> + # Handle response here ensures that the full response behavior will be given by the Actor + # that initiated the remote call and not the target POD, + # which could cause unwanted side effects. + # For this works we need to disable track_resources: false + Invocation.handle_response(source_request, dest_response, updated_state, source_opts) + + res -> + res + end) + |> handle_scheduler_response() + end + + defp handle_scheduler_response( + {:reply, {:ok, %ActorInvocationResponse{} = resp}, %EntityState{} = _state} = + payload + ) do + Logger.debug("Remoting Scheduler response for invocation: #{inspect(resp)}") + payload + end + + defp handle_scheduler_response( + {:reply, {:ok, %ActorInvocationResponse{} = resp}, %EntityState{} = _state, _signal} = + payload + ) do + Logger.debug("Remoting Scheduler response for invocation: #{inspect(resp)}") + payload + end + + defp handle_scheduler_response( + {:noreply, %EntityState{} = _state} = + payload + ) do + Logger.debug("Remoting Scheduler response for invocation ok") + payload + end + + defp handle_scheduler_response( + {:noreply, %EntityState{} = _state, _signal} = + payload + ) do + Logger.debug("Remoting Scheduler response for invocation ok") + payload + end + + defp handle_scheduler_response(another) do + Logger.error("Error during Remoting Scheduler invocation. Details: #{inspect(another)}") + another + end + defp do_handle_defaults(action, from, state) do case action do :get_state -> @@ -289,7 +413,8 @@ defmodule Actors.Actor.Entity do case action do {:invocation_request, invocation, opts} -> - Invocation.invoke({invocation, opts}, state) + opts = Keyword.merge(opts, async: true) + handle_invocation_request(invocation, opts, nil, state) |> reply_to_noreply() action -> diff --git a/lib/actors/actor/entity/invocation.ex b/lib/actors/actor/entity/invocation.ex index dde4b78da..ade6b7400 100644 --- a/lib/actors/actor/entity/invocation.ex +++ b/lib/actors/actor/entity/invocation.ex @@ -285,6 +285,28 @@ defmodule Actors.Actor.Entity.Invocation do end end + def handle_response( + request, + %ActorInvocationResponse{checkpoint: checkpoint} = response, + %EntityState{ + revision: revision + } = state, + opts + ) do + response = + case do_response(request, response, state, opts) do + :noreply -> + {:noreply, state} + |> return_and_maybe_hibernate() + + response -> + {:reply, {:ok, response}, state} + |> return_and_maybe_hibernate() + end + + response_checkpoint(response, checkpoint, revision, state) + end + defp is_authorized?(invocation, actions, timers) do acl_manager = get_acl_manager() @@ -315,10 +337,13 @@ defmodule Actors.Actor.Entity.Invocation do Tracer.with_span "invoke-host" do case interface.invoke_host(request, state, @default_actions) do {:ok, response, new_state} -> - handle_response(request, response, new_state, opts) + {:ok, request, response, new_state, opts} + + # handle_response(request, response, new_state, opts) {:error, reason, new_state} -> - {:reply, {:error, reason}, new_state} |> return_and_maybe_hibernate() + {:reply, {:error, reason}, new_state} + |> return_and_maybe_hibernate() end end end @@ -366,28 +391,6 @@ defmodule Actors.Actor.Entity.Invocation do } end - defp handle_response( - request, - %ActorInvocationResponse{checkpoint: checkpoint} = response, - %EntityState{ - revision: revision - } = state, - opts - ) do - response = - case do_response(request, response, state, opts) do - :noreply -> - {:noreply, state} - |> return_and_maybe_hibernate() - - response -> - {:reply, {:ok, response}, state} - |> return_and_maybe_hibernate() - end - - response_checkpoint(response, checkpoint, revision, state) - end - defp response_checkpoint(response, checkpoint, revision, state) do if checkpoint do Lifecycle.checkpoint(revision, state) diff --git a/lib/sidecar/grpc/dispatcher.ex b/lib/sidecar/grpc/dispatcher.ex index 863df9d23..4ec4c2b6b 100644 --- a/lib/sidecar/grpc/dispatcher.ex +++ b/lib/sidecar/grpc/dispatcher.ex @@ -315,7 +315,6 @@ defmodule Sidecar.GRPC.Dispatcher do __pb_extensions__: ext } } -> - Map.get(ext, {Eigr.Functions.Protocol.Actors.PbExtension, :actor_id}, false) && {ctype, String.to_atom(name)} end) diff --git a/lib/spawn/actors/eigr/functions/protocol/actors/actor.pb.ex b/lib/spawn/actors/eigr/functions/protocol/actors/actor.pb.ex index 747f92068..deef8b158 100644 --- a/lib/spawn/actors/eigr/functions/protocol/actors/actor.pb.ex +++ b/lib/spawn/actors/eigr/functions/protocol/actors/actor.pb.ex @@ -1,6 +1,6 @@ defmodule Eigr.Functions.Protocol.Actors.Kind do @moduledoc false - use Protobuf, enum: true, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, enum: true, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -36,6 +36,12 @@ defmodule Eigr.Functions.Protocol.Actors.Kind do number: 4, options: nil, __unknown_fields__: [] + }, + %Google.Protobuf.EnumValueDescriptorProto{ + name: "TASK", + number: 5, + options: nil, + __unknown_fields__: [] } ], options: nil, @@ -50,11 +56,12 @@ defmodule Eigr.Functions.Protocol.Actors.Kind do field(:UNNAMED, 2) field(:POOLED, 3) field(:PROXY, 4) + field(:TASK, 5) end defmodule Eigr.Functions.Protocol.Actors.Registry.ActorsEntry do @moduledoc false - use Protobuf, map: true, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, map: true, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -100,7 +107,6 @@ defmodule Eigr.Functions.Protocol.Actors.Registry.ActorsEntry do deprecated: false, map_entry: true, deprecated_legacy_json_field_conflicts: nil, - # features: nil, uninterpreted_option: [], __pb_extensions__: %{}, __unknown_fields__: [] @@ -118,7 +124,7 @@ end defmodule Eigr.Functions.Protocol.Actors.Registry do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -183,7 +189,6 @@ defmodule Eigr.Functions.Protocol.Actors.Registry do deprecated: false, map_entry: true, deprecated_legacy_json_field_conflicts: nil, - # features: nil, uninterpreted_option: [], __pb_extensions__: %{}, __unknown_fields__: [] @@ -214,7 +219,7 @@ end defmodule Eigr.Functions.Protocol.Actors.ActorSystem do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -268,7 +273,7 @@ end defmodule Eigr.Functions.Protocol.Actors.ActorSnapshotStrategy do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -315,7 +320,7 @@ end defmodule Eigr.Functions.Protocol.Actors.ActorDeactivationStrategy do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -362,7 +367,7 @@ end defmodule Eigr.Functions.Protocol.Actors.TimeoutStrategy do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -401,7 +406,7 @@ end defmodule Eigr.Functions.Protocol.Actors.Action do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -440,7 +445,7 @@ end defmodule Eigr.Functions.Protocol.Actors.FixedTimerAction do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -494,7 +499,7 @@ end defmodule Eigr.Functions.Protocol.Actors.ActorState.TagsEntry do @moduledoc false - use Protobuf, map: true, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, map: true, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -540,7 +545,6 @@ defmodule Eigr.Functions.Protocol.Actors.ActorState.TagsEntry do deprecated: false, map_entry: true, deprecated_legacy_json_field_conflicts: nil, - # features: nil, uninterpreted_option: [], __pb_extensions__: %{}, __unknown_fields__: [] @@ -558,7 +562,7 @@ end defmodule Eigr.Functions.Protocol.Actors.ActorState do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -637,7 +641,6 @@ defmodule Eigr.Functions.Protocol.Actors.ActorState do deprecated: false, map_entry: true, deprecated_legacy_json_field_conflicts: nil, - # features: nil, uninterpreted_option: [], __pb_extensions__: %{}, __unknown_fields__: [] @@ -670,7 +673,7 @@ end defmodule Eigr.Functions.Protocol.Actors.Metadata.TagsEntry do @moduledoc false - use Protobuf, map: true, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, map: true, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -716,7 +719,6 @@ defmodule Eigr.Functions.Protocol.Actors.Metadata.TagsEntry do deprecated: false, map_entry: true, deprecated_legacy_json_field_conflicts: nil, - # features: nil, uninterpreted_option: [], __pb_extensions__: %{}, __unknown_fields__: [] @@ -734,7 +736,7 @@ end defmodule Eigr.Functions.Protocol.Actors.Metadata do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -813,7 +815,6 @@ defmodule Eigr.Functions.Protocol.Actors.Metadata do deprecated: false, map_entry: true, deprecated_legacy_json_field_conflicts: nil, - # features: nil, uninterpreted_option: [], __pb_extensions__: %{}, __unknown_fields__: [] @@ -850,7 +851,7 @@ end defmodule Eigr.Functions.Protocol.Actors.Channel do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -904,7 +905,7 @@ end defmodule Eigr.Functions.Protocol.Actors.ActorSettings do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1027,7 +1028,7 @@ end defmodule Eigr.Functions.Protocol.Actors.ActorId do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1096,7 +1097,7 @@ end defmodule Eigr.Functions.Protocol.Actors.Actor do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line diff --git a/lib/spawn/actors/eigr/functions/protocol/actors/extensions.pb.ex b/lib/spawn/actors/eigr/functions/protocol/actors/extensions.pb.ex index 2f02de63e..55549a425 100644 --- a/lib/spawn/actors/eigr/functions/protocol/actors/extensions.pb.ex +++ b/lib/spawn/actors/eigr/functions/protocol/actors/extensions.pb.ex @@ -1,6 +1,6 @@ defmodule Eigr.Functions.Protocol.Actors.PbExtension do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 extend(Google.Protobuf.FieldOptions, :actor_id, 9999, optional: true, diff --git a/lib/spawn/actors/eigr/functions/protocol/actors/healthcheck.pb.ex b/lib/spawn/actors/eigr/functions/protocol/actors/healthcheck.pb.ex index d95c85a50..ff2b7d2db 100644 --- a/lib/spawn/actors/eigr/functions/protocol/actors/healthcheck.pb.ex +++ b/lib/spawn/actors/eigr/functions/protocol/actors/healthcheck.pb.ex @@ -1,6 +1,6 @@ defmodule Eigr.Functions.Protocol.Actors.Healthcheck.Status do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -69,7 +69,7 @@ end defmodule Eigr.Functions.Protocol.Actors.Healthcheck.HealthCheckReply do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -108,9 +108,10 @@ end defmodule Eigr.Functions.Protocol.Actors.Healthcheck.HealthCheckActor.Service do @moduledoc false + use GRPC.Service, name: "eigr.functions.protocol.actors.healthcheck.HealthCheckActor", - protoc_gen_elixir_version: "0.12.0" + protoc_gen_elixir_version: "0.13.0" def descriptor do # credo:disable-for-next-line @@ -221,7 +222,6 @@ defmodule Eigr.Functions.Protocol.Actors.Healthcheck.HealthCheckActor.Service do options: %Google.Protobuf.MethodOptions{ deprecated: false, idempotency_level: :IDEMPOTENCY_UNKNOWN, - # features: nil, uninterpreted_option: [], __pb_extensions__: %{ {Google.Api.PbExtension, :http} => %Google.Api.HttpRule{ @@ -246,7 +246,6 @@ defmodule Eigr.Functions.Protocol.Actors.Healthcheck.HealthCheckActor.Service do options: %Google.Protobuf.MethodOptions{ deprecated: false, idempotency_level: :IDEMPOTENCY_UNKNOWN, - # features: nil, uninterpreted_option: [], __pb_extensions__: %{ {Google.Api.PbExtension, :http} => %Google.Api.HttpRule{ @@ -288,9 +287,9 @@ defmodule Eigr.Functions.Protocol.Actors.Healthcheck.HealthCheckActor.Service do swift_prefix: nil, php_class_prefix: nil, php_namespace: nil, + php_generic_services: false, php_metadata_namespace: nil, ruby_package: nil, - # features: nil, uninterpreted_option: [], __pb_extensions__: %{}, __unknown_fields__: [] @@ -690,3 +689,9 @@ defmodule Eigr.Functions.Protocol.Actors.Healthcheck.HealthCheckActor.Service do } ) end + +defmodule Eigr.Functions.Protocol.Actors.Healthcheck.HealthCheckActor.Stub do + @moduledoc false + + use GRPC.Stub, service: Eigr.Functions.Protocol.Actors.Healthcheck.HealthCheckActor.Service +end diff --git a/lib/spawn/actors/eigr/functions/protocol/actors/protocol.pb.ex b/lib/spawn/actors/eigr/functions/protocol/actors/protocol.pb.ex index 7bc475d88..aabfd02fc 100644 --- a/lib/spawn/actors/eigr/functions/protocol/actors/protocol.pb.ex +++ b/lib/spawn/actors/eigr/functions/protocol/actors/protocol.pb.ex @@ -1,6 +1,6 @@ defmodule Eigr.Functions.Protocol.Status do @moduledoc false - use Protobuf, enum: true, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, enum: true, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -47,7 +47,7 @@ end defmodule Eigr.Functions.Protocol.Context.MetadataEntry do @moduledoc false - use Protobuf, map: true, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, map: true, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -93,7 +93,6 @@ defmodule Eigr.Functions.Protocol.Context.MetadataEntry do deprecated: false, map_entry: true, deprecated_legacy_json_field_conflicts: nil, - # features: nil, uninterpreted_option: [], __pb_extensions__: %{}, __unknown_fields__: [] @@ -111,7 +110,7 @@ end defmodule Eigr.Functions.Protocol.Context.TagsEntry do @moduledoc false - use Protobuf, map: true, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, map: true, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -157,7 +156,6 @@ defmodule Eigr.Functions.Protocol.Context.TagsEntry do deprecated: false, map_entry: true, deprecated_legacy_json_field_conflicts: nil, - # features: nil, uninterpreted_option: [], __pb_extensions__: %{}, __unknown_fields__: [] @@ -175,7 +173,7 @@ end defmodule Eigr.Functions.Protocol.Context do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -296,7 +294,6 @@ defmodule Eigr.Functions.Protocol.Context do deprecated: false, map_entry: true, deprecated_legacy_json_field_conflicts: nil, - # features: nil, uninterpreted_option: [], __pb_extensions__: %{}, __unknown_fields__: [] @@ -348,7 +345,6 @@ defmodule Eigr.Functions.Protocol.Context do deprecated: false, map_entry: true, deprecated_legacy_json_field_conflicts: nil, - # features: nil, uninterpreted_option: [], __pb_extensions__: %{}, __unknown_fields__: [] @@ -385,7 +381,7 @@ end defmodule Eigr.Functions.Protocol.Noop do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -407,7 +403,7 @@ end defmodule Eigr.Functions.Protocol.JSONType do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -446,7 +442,7 @@ end defmodule Eigr.Functions.Protocol.RegistrationRequest do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -504,7 +500,7 @@ end defmodule Eigr.Functions.Protocol.RegistrationResponse do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -558,7 +554,7 @@ end defmodule Eigr.Functions.Protocol.ServiceInfo do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -687,7 +683,7 @@ end defmodule Eigr.Functions.Protocol.SpawnRequest do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -726,7 +722,7 @@ end defmodule Eigr.Functions.Protocol.SpawnResponse do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -765,7 +761,7 @@ end defmodule Eigr.Functions.Protocol.ProxyInfo do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -849,7 +845,7 @@ end defmodule Eigr.Functions.Protocol.SideEffect do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -888,7 +884,7 @@ end defmodule Eigr.Functions.Protocol.Broadcast do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -965,7 +961,7 @@ end defmodule Eigr.Functions.Protocol.Pipe do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1019,7 +1015,7 @@ end defmodule Eigr.Functions.Protocol.Forward do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1073,7 +1069,7 @@ end defmodule Eigr.Functions.Protocol.Workflow do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1165,7 +1161,7 @@ end defmodule Eigr.Functions.Protocol.InvocationRequest.MetadataEntry do @moduledoc false - use Protobuf, map: true, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, map: true, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1211,7 +1207,6 @@ defmodule Eigr.Functions.Protocol.InvocationRequest.MetadataEntry do deprecated: false, map_entry: true, deprecated_legacy_json_field_conflicts: nil, - # features: nil, uninterpreted_option: [], __pb_extensions__: %{}, __unknown_fields__: [] @@ -1229,7 +1224,7 @@ end defmodule Eigr.Functions.Protocol.InvocationRequest do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1434,7 +1429,6 @@ defmodule Eigr.Functions.Protocol.InvocationRequest do deprecated: false, map_entry: true, deprecated_legacy_json_field_conflicts: nil, - # features: nil, uninterpreted_option: [], __pb_extensions__: %{}, __unknown_fields__: [] @@ -1485,7 +1479,7 @@ end defmodule Eigr.Functions.Protocol.ActorInvocation do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1607,7 +1601,7 @@ end defmodule Eigr.Functions.Protocol.ActorInvocationResponse do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1744,7 +1738,7 @@ end defmodule Eigr.Functions.Protocol.InvocationResponse do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -1851,7 +1845,7 @@ end defmodule Eigr.Functions.Protocol.RequestStatus do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line diff --git a/lib/spawn/actors/eigr/functions/protocol/actors/state.pb.ex b/lib/spawn/actors/eigr/functions/protocol/actors/state.pb.ex index 1b3b9b948..92915b44a 100644 --- a/lib/spawn/actors/eigr/functions/protocol/actors/state.pb.ex +++ b/lib/spawn/actors/eigr/functions/protocol/actors/state.pb.ex @@ -1,6 +1,6 @@ defmodule Eigr.Functions.Protocol.State.Revision do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line @@ -39,7 +39,7 @@ end defmodule Eigr.Functions.Protocol.State.Checkpoint do @moduledoc false - use Protobuf, protoc_gen_elixir_version: "0.12.0", syntax: :proto3 + use Protobuf, protoc_gen_elixir_version: "0.13.0", syntax: :proto3 def descriptor do # credo:disable-for-next-line diff --git a/lib/spawn/cluster/cluster_supervisor.ex b/lib/spawn/cluster/cluster_supervisor.ex index a238740ef..6df2beb74 100644 --- a/lib/spawn/cluster/cluster_supervisor.ex +++ b/lib/spawn/cluster/cluster_supervisor.ex @@ -15,14 +15,21 @@ defmodule Spawn.Cluster.ClusterSupervisor do @impl true def init(opts) do - children = [ - supervisor_process_logger(__MODULE__), - cluster_supervisor(opts) - ] + children = + [ + supervisor_process_logger(__MODULE__), + cluster_supervisor(opts) + ] + |> maybe_add_provisioner(opts) Supervisor.init(children, strategy: :one_for_one) end + defp maybe_add_provisioner(children, opts) do + # TODO check if is production env + children ++ [{Spawn.Cluster.ProvisionerPoolSupervisor, opts}] + end + defp cluster_supervisor(opts) do cluster_strategy = Config.get(:proxy_cluster_strategy) @@ -90,7 +97,7 @@ defmodule Spawn.Cluster.ClusterSupervisor do strategy: Elixir.Cluster.Strategy.Kubernetes.DNS, config: [ service: Config.get(:proxy_headless_service), - application_name: "spawn", + application_name: "proxy", polling_interval: Config.get(:proxy_cluster_polling_interval) ] ] diff --git a/lib/spawn/cluster/provisioner/executor.ex b/lib/spawn/cluster/provisioner/executor.ex new file mode 100644 index 000000000..01feb3fae --- /dev/null +++ b/lib/spawn/cluster/provisioner/executor.ex @@ -0,0 +1,4 @@ +defprotocol Spawn.Cluster.Provisioner.Executor do + @doc "Executes a task" + def execute(task, func) +end diff --git a/lib/spawn/cluster/provisioner/scheduler.ex b/lib/spawn/cluster/provisioner/scheduler.ex new file mode 100644 index 000000000..79eb1544a --- /dev/null +++ b/lib/spawn/cluster/provisioner/scheduler.ex @@ -0,0 +1,85 @@ +defmodule Spawn.Cluster.Provisioner.Scheduler do + @moduledoc """ + The `Spawn.Cluster.Provisioner.Scheduler` module is responsible for scheduling tasks and invoking functions in a distributed actor system. + It handles creating worker pools and executing functions with the given task configuration. + + This module also contains an implementation of the `Executor` protocol for the `SpawnTask` struct, + defining the execution behavior for tasks in the context of provisioning actors in the cluster. + """ + + alias Spawn.Cluster.Provisioner.SpawnTask + alias Spawn.Cluster.ProvisionerPoolSupervisor + import Spawn.Utils.Common, only: [build_worker_pool_name: 2] + + defimpl Spawn.Cluster.Provisioner.Executor, for: Spawn.Cluster.Provisioner.SpawnTask do + @doc """ + Defines the `Executor` protocol for the `SpawnTask` struct. + + This implementation handles the execution of a given function (`func`) in the context of a task, + using the specified actor name, invocation details, options (`opts`), and state. + + The task is executed through a worker pool, created using the `build_worker_pool_name/2` function, + and the function is invoked with the `{invocation, opts}` tuple and the current state. + + ## Parameters + + - `%SpawnTask{}`: The task struct containing details about the actor provisioning process. + - `func`: The function to be invoked, which takes the task's `invocation`, `opts`, and `state`. + + ## Returns + + The result of executing the provided function within the context of the actor provisioning system. + """ + def execute( + %SpawnTask{actor_name: actor_name, invocation: invocation, opts: opts, state: state, async: false}, + func + ) + when is_function(func) do + opts = Keyword.merge(opts, link: false) + + build_worker_pool_name(ProvisionerPoolSupervisor, actor_name) + |> FLAME.call(fn -> func.({invocation, opts}, state) end, opts) + end + + def execute( + %SpawnTask{actor_name: actor_name, invocation: invocation, opts: opts, state: state, async: true}, + func + ) + when is_function(func) do + opts = Keyword.merge(opts, link: false) + + build_worker_pool_name(ProvisionerPoolSupervisor, actor_name) + |> FLAME.cast(fn -> func.({invocation, opts}, state) end, opts) + end + end + + @doc """ + Schedules and invokes a task for actor provisioning in another k8s POD. + + This function wraps the scheduling logic by leveraging the `Executor` protocol to execute the provided + function (`func`). The function is called with the `invocation`, `opts`, and `state` details encapsulated in a `SpawnTask` struct. + + ## Parameters + + - `actor_name`: The actor name reference used to create the worker pool for the task execution. + - `invocation`: The details of the invocation, typically containing metadata about the actor's execution. + - `opts`: Options passed along with the task, which may modify how the invocation is performed. + - `state`: The current state of the process, to be passed to the function being invoked. + - `func`: A function that will be called with the `{invocation, opts}` tuple and the current `state`. + + ## Example + + ```elixir + task = %SpawnTask{ + actor: actor, + invocation: invocation, + opts: opts, + state: state + } + + Spawn.Cluster.Provisioner.Scheduler.schedule_and_invoke(task, &some_function/2) + """ + def schedule_and_invoke(task, func) when is_function(func) do + Spawn.Cluster.Provisioner.Executor.execute(task, func) + end +end diff --git a/lib/spawn/cluster/provisioner/spawn_task.ex b/lib/spawn/cluster/provisioner/spawn_task.ex new file mode 100644 index 000000000..3944d7379 --- /dev/null +++ b/lib/spawn/cluster/provisioner/spawn_task.ex @@ -0,0 +1,3 @@ +defmodule Spawn.Cluster.Provisioner.SpawnTask do + defstruct [:actor_name, :invocation, :opts, :state, :async] +end diff --git a/lib/spawn/cluster/provisioner_pool_supervisor.ex b/lib/spawn/cluster/provisioner_pool_supervisor.ex new file mode 100644 index 000000000..6a66bf8c1 --- /dev/null +++ b/lib/spawn/cluster/provisioner_pool_supervisor.ex @@ -0,0 +1,154 @@ +defmodule Spawn.Cluster.ProvisionerPoolSupervisor do + @moduledoc false + use Supervisor + require Logger + + import Spawn.Utils.Common, only: [build_worker_pool_name: 2] + + def start_link(opts) do + Supervisor.start_link(__MODULE__, opts, name: __MODULE__) + end + + @impl true + def init(_opts) do + actor_configs = + System.get_env("SPAWN_PROXY_TASK_CONFIG", "") + |> parse_config() + + env = get_environment() + + children = + Enum.map(actor_configs, fn cfg -> + Logger.info("Setup Task Actor with config: #{inspect(cfg)}") + build_flame_pool(cfg, env) + end) + + Supervisor.init(children, strategy: :one_for_one) + end + + defp build_flame_pool(%{"actorName" => name} = cfg, env) do + pool_name = build_worker_pool_name(__MODULE__, name) + Logger.info("Creating pool for Actor #{name}. Pool Name: #{inspect(pool_name)}") + + opts = + [ + name: pool_name, + backend: pool_backend(cfg, env), + log: :debug + ] ++ get_worker_pool_config(cfg) + + {FLAME.Pool, opts} + end + + defp pool_backend(cfg, :prod) do + {FLAMEK8sBackend, + app_container_name: "sidecar", + runner_pod_tpl: fn current_manifest -> build_pod_template(cfg, current_manifest) end} + end + + defp pool_backend(_, _env), do: FLAME.LocalBackend + + defp get_worker_pool_config(cfg) do + worker_pool_config = Map.get(cfg, "workerPool", %{}) + + [ + min: Map.get(worker_pool_config, "min", 0), + max: Map.get(worker_pool_config, "max", 10), + max_concurrency: Map.get(worker_pool_config, "maxConcurrency", 100), + single_use: Map.get(worker_pool_config, "oneOff", "false"), + timeout: Map.get(worker_pool_config, "callTimeout", :infinity), + boot_timeout: Map.get(worker_pool_config, "bootTimeout", :timer.minutes(3)), + idle_shutdown_after: Map.get(worker_pool_config, "idleShutdownAfter", :timer.minutes(1)), + track_resources: false + ] + end + + defp build_pod_template(cfg, template) do + Logger.debug("Building pod template...") + + template + |> update_pod_metadata() + |> update_pod_spec() + |> remove_probes_from_containers() + |> maybe_put_node_selector(cfg) + |> maybe_put_toleration(cfg) + end + + defp update_pod_metadata(template) do + target_metadata = %{ + "name" => "target-pod", + "namespace" => Access.get(template, "metadata")["namespace"], + "annotations" => %{ + "prometheus.io/path" => "/metrics", + "prometheus.io/port" => "9001", + "prometheus.io/scrape" => "true" + } + } + + put_in(template["metadata"], target_metadata) + end + + defp update_pod_spec(template) do + spec = template["spec"] + + target_spec = %{ + "initContainers" => spec["initContainers"], + "containers" => spec["containers"], + "volumes" => spec["volumes"], + "serviceAccount" => spec["serviceAccount"], + "serviceAccountName" => spec["serviceAccountName"], + "terminationGracePeriodSeconds" => spec["terminationGracePeriodSeconds"], + "restartPolicy" => "Never" + } + + put_in(template["spec"], target_spec) + end + + defp remove_probes_from_containers(template) do + updated_containers = + template["spec"]["containers"] + |> Enum.map(&Map.drop(&1, ["readinessProbe", "livenessProbe"])) + + put_in(template["spec"]["containers"], updated_containers) + end + + defp maybe_put_node_selector(template, %{"topology" => topology}) do + update_metadata_with_labels(template) + |> put_in(["spec", "nodeSelector"], topology["nodeSelector"]) + end + + defp maybe_put_node_selector(template, _cfg), do: template + + defp maybe_put_toleration(template, %{"topology" => topology}) do + update_metadata_with_labels(template) + |> put_in(["spec", "tolerations"], topology["tolerations"]) + end + + defp maybe_put_toleration(template, _cfg), do: template + + defp update_metadata_with_labels(template) do + new_label_map = + template + |> get_in(["metadata", "labels"]) + |> Kernel.||(%{}) + |> Map.merge(%{"io.eigr.spawn/worker" => "true"}) + + put_in(template["metadata"]["labels"], new_label_map) + end + + defp parse_config(""), do: [] + + defp parse_config(encoded_cfg) do + encoded_cfg + |> Base.decode32!() + |> Jason.decode!() + |> Map.get("taskActors", []) + end + + defp get_environment do + case System.get_env("MIX_ENV", "dev") do + "prod" -> :prod + env -> String.to_atom(env) + end + end +end diff --git a/lib/spawn/utils/common.ex b/lib/spawn/utils/common.ex index 7fa8e52b7..0876a63d7 100644 --- a/lib/spawn/utils/common.ex +++ b/lib/spawn/utils/common.ex @@ -5,6 +5,19 @@ defmodule Spawn.Utils.Common do alias Actors.Config.PersistentTermConfig, as: Config alias Eigr.Functions.Protocol.Actors.ActorId + def build_worker_pool_name(module, parent) do + System.get_env("SPAWN_PROXY_USE_DEFAULT_FLAME_POOL", "false") + |> do_build_worker_pool_name(module, parent) + end + + defp do_build_worker_pool_name("false", module, parent) do + Module.concat(module, String.upcase(parent)) + end + + defp do_build_worker_pool_name("true", module, _parent) do + Module.concat(module, "default") + end + @spec actor_host_hash() :: integer() def actor_host_hash() do system = Config.get(:actor_system_name) diff --git a/mix.exs b/mix.exs index 2582fb83a..63a668445 100644 --- a/mix.exs +++ b/mix.exs @@ -80,6 +80,7 @@ defmodule Spawn.MixProject do {:grpc, "~> 0.8"}, {:grpc_reflection, "~> 0.1"}, {:finch, "~> 0.18"}, + {:flame_k8s_backend, "~> 0.5"}, {:retry, "~> 0.17"}, {:flow, "~> 1.2"}, {:libcluster, "~> 3.3"}, diff --git a/mix.lock b/mix.lock index 354676ff2..4459b78a9 100644 --- a/mix.lock +++ b/mix.lock @@ -27,6 +27,8 @@ "exqlite": {:hex, :exqlite, "0.19.0", "0f3ee29e35bed38552dd0ed59600aa81c78f867f5b5ff0e17d330148e0465483", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "55a8fbb0443f03d4a256e3458bd1203eff5037a6624b76460eaaa9080f462b06"}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, + "flame": {:hex, :flame, "0.5.1", "339130ed9dff761efc1b2c001839e6d16aa9af291a1e155d8c14fa9b42c03caa", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, ">= 0.0.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "b09ac62b187f40fa7f959d6faca58aae0e575ff21435a8afd79727b8e5631085"}, + "flame_k8s_backend": {:hex, :flame_k8s_backend, "0.5.6", "91b9e2816366ed23020e05fead38a992b86230b112b6d4be2ef2776d586aea99", [:mix], [{:flame, "~> 0.4.0 or ~> 0.5.0", [hex: :flame, repo: "hexpm", optional: false]}], "hexpm", "e403571de233045a858ba9efcfcc0cfc68a5aa1e8c48b16428cdbad2f8dff72d"}, "flow": {:hex, :flow, "1.2.4", "1dd58918287eb286656008777cb32714b5123d3855956f29aa141ebae456922d", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "874adde96368e71870f3510b91e35bc31652291858c86c0e75359cbdd35eb211"}, "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, "gnat": {:hex, :gnat, "1.8.5", "31d7f75dcca90164b2d811bcbc1a94b9049a0f586c219594687d6bd9493043a5", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:cowlib, "~> 2.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:nkeys, "~> 0.2", [hex: :nkeys, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5e62a8f6f02a12191a9ca79ea94f5899db7ec32a0e8554cac9930196298e8cb6"}, diff --git a/priv/protos/eigr/functions/protocol/actors/actor.proto b/priv/protos/eigr/functions/protocol/actors/actor.proto index 6d015d4f0..d49b3a60f 100644 --- a/priv/protos/eigr/functions/protocol/actors/actor.proto +++ b/priv/protos/eigr/functions/protocol/actors/actor.proto @@ -7,164 +7,169 @@ import "google/protobuf/any.proto"; option java_package = "io.eigr.functions.protocol.actors"; option go_package = "github.com/eigr/go-support/eigr/actors;actors"; - -message Registry { - map actors = 1; -} +message Registry { map actors = 1; } message ActorSystem { - string name = 1; - Registry registry = 2; + string name = 1; + Registry registry = 2; } // A strategy for save state. message ActorSnapshotStrategy { - oneof strategy { - // the timeout strategy. - TimeoutStrategy timeout = 1; - } + oneof strategy { + // the timeout strategy. + TimeoutStrategy timeout = 1; + } } // A strategy which a user function's entity is passivated. message ActorDeactivationStrategy { - oneof strategy { - // the timeout strategy. - TimeoutStrategy timeout = 1; - } + oneof strategy { + // the timeout strategy. + TimeoutStrategy timeout = 1; + } } -// A strategy based on a timeout. +// A strategy based on a timeout. message TimeoutStrategy { - // The timeout in millis - int64 timeout = 1; + // The timeout in millis + int64 timeout = 1; } -// A action represents an action that the user can perform on an Actor. +// A action represents an action that the user can perform on an Actor. // Actions in supporting languages are represented by functions or methods. -// An Actor action has nothing to do with the semantics of Actions in a CQRS/EventSourced system. -// It just represents an action that supporting languages can invoke. +// An Actor action has nothing to do with the semantics of Actions in a +// CQRS/EventSourced system. It just represents an action that supporting +// languages can invoke. message Action { - // The name of the function or method in the supporting language that has been registered in Ator. - string name = 1; + // The name of the function or method in the supporting language that has been + // registered in Ator. + string name = 1; } -// A FixedTimerAction is similar to a regular Action, its main differences are that it is scheduled to run at regular intervals -// and only takes the actor's state as an argument. -// Timer Actions are good for executing loops that manipulate the actor's own state. -// In Elixir or other languages in BEAM it would be similar to invoking Process.send_after(self(), atom, msg, timeout) +// A FixedTimerAction is similar to a regular Action, its main differences are +// that it is scheduled to run at regular intervals and only takes the actor's +// state as an argument. Timer Actions are good for executing loops that +// manipulate the actor's own state. In Elixir or other languages in BEAM it +// would be similar to invoking Process.send_after(self(), atom, msg, timeout) message FixedTimerAction { - // The time to wait until the action is triggered - int32 seconds = 1; + // The time to wait until the action is triggered + int32 seconds = 1; - // See Action description Above - Action action = 2; + // See Action description Above + Action action = 2; } message ActorState { - map tags = 1; - google.protobuf.Any state = 2; + map tags = 1; + google.protobuf.Any state = 2; } -// Metadata represents a set of key-value pairs that can be used to +// Metadata represents a set of key-value pairs that can be used to // provide additional information about an Actor. message Metadata { - // A channel group represents a way to send actions to various actors - // that belong to a certain semantic group. Following the Pub-Sub pattern. - repeated Channel channel_group = 1; + // A channel group represents a way to send actions to various actors + // that belong to a certain semantic group. Following the Pub-Sub pattern. + repeated Channel channel_group = 1; - map tags = 2; + map tags = 2; } -// Represents a Pub-Sub binding, where a actor can be subscribed to a channel +// Represents a Pub-Sub binding, where a actor can be subscribed to a channel // and map a specific action to a specific topic if necessary // if the action is not informed, the default action will be "receive". message Channel { - string topic = 1; - string action = 2; + string topic = 1; + string action = 2; } // The type that defines the runtime characteristics of the Actor. -// Regardless of the type of actor it is important that +// Regardless of the type of actor it is important that // all actors are registered during the proxy and host initialization phase. enum Kind { - // When no type is informed, the default to be assumed will be the Named pattern. - UNKNOW_KIND = 0; - - // NAMED actors as the name suggests have only one real instance of themselves running - // during their entire lifecycle. That is, they are the opposite of the UNNAMED type Actors. - NAMED = 1; - - // UNNAMED actors are used to create children of this based actor at runtime - UNNAMED = 2; - - // Pooled Actors are similar to Unnamed actors, but unlike them, - // their identifying name will always be the one registered at the system initialization stage. - // The great advantage of Pooled actors is that they have multiple instances of themselves - // acting as a request service pool. - // Pooled actors are also stateless actors, that is, they will not have their - // in-memory state persisted via Statesstore. This is done to avoid problems - // with the correctness of the stored state. - // Pooled Actors are generally used for tasks where the Actor Model would perform worse - // than other concurrency models and for tasks that do not require state concerns. - // Integration flows, data caching, proxies are good examples of use cases - // for this type of Actor. - POOLED = 3; - - // Reserved for future use - PROXY = 4; + // When no type is informed, the default to be assumed will be the Named + // pattern. + UNKNOW_KIND = 0; + + // NAMED actors as the name suggests have only one real instance of themselves + // running during their entire lifecycle. That is, they are the opposite of + // the UNNAMED type Actors. + NAMED = 1; + + // UNNAMED actors are used to create children of this based actor at runtime + UNNAMED = 2; + + // Pooled Actors are similar to Unnamed actors, but unlike them, + // their identifying name will always be the one registered at the system + // initialization stage. The great advantage of Pooled actors is that they + // have multiple instances of themselves acting as a request service pool. + // Pooled actors are also stateless actors, that is, they will not have their + // in-memory state persisted via Statesstore. This is done to avoid problems + // with the correctness of the stored state. + // Pooled Actors are generally used for tasks where the Actor Model would + // perform worse than other concurrency models and for tasks that do not + // require state concerns. Integration flows, data caching, proxies are good + // examples of use cases for this type of Actor. + POOLED = 3; + + // Reserved for future use + PROXY = 4; + + TASK = 5; } message ActorSettings { - // Indicates the type of Actor to be configured. - Kind kind = 1; + // Indicates the type of Actor to be configured. + Kind kind = 1; - // Indicates whether an actor's state should be persisted in a definitive store. - bool stateful = 2; + // Indicates whether an actor's state should be persisted in a definitive + // store. + bool stateful = 2; - // Snapshot strategy - ActorSnapshotStrategy snapshot_strategy = 3; + // Snapshot strategy + ActorSnapshotStrategy snapshot_strategy = 3; - // Deactivate strategy - ActorDeactivationStrategy deactivation_strategy = 4; + // Deactivate strategy + ActorDeactivationStrategy deactivation_strategy = 4; - // When kind is POOLED this is used to define minimun actor instances - int32 min_pool_size = 5; + // When kind is POOLED this is used to define minimun actor instances + int32 min_pool_size = 5; - // When kind is POOLED this is used to define maximum actor instances - int32 max_pool_size = 6; + // When kind is POOLED this is used to define maximum actor instances + int32 max_pool_size = 6; } message ActorId { - // The name of a Actor Entity. - string name = 1; + // The name of a Actor Entity. + string name = 1; - // Name of a ActorSystem - string system = 2; + // Name of a ActorSystem + string system = 2; - // When the Actor is of the Unnamed type, - // the name of the parent Actor must be informed here. - string parent = 3; + // When the Actor is of the Unnamed type, + // the name of the parent Actor must be informed here. + string parent = 3; } message Actor { - // Actor Identification - ActorId id = 1; + // Actor Identification + ActorId id = 1; - // A Actor state. - ActorState state = 2; + // A Actor state. + ActorState state = 2; - // Actor metadata - Metadata metadata = 6; + // Actor metadata + Metadata metadata = 6; - // Actor settings. - ActorSettings settings = 3; + // Actor settings. + ActorSettings settings = 3; - // The actions registered for an actor - repeated Action actions = 4; + // The actions registered for an actor + repeated Action actions = 4; - // The registered timer actions for an actor. - repeated FixedTimerAction timer_actions = 5; + // The registered timer actions for an actor. + repeated FixedTimerAction timer_actions = 5; } \ No newline at end of file diff --git a/spawn_operator/spawn_operator/lib/spawn_operator.ex b/spawn_operator/spawn_operator/lib/spawn_operator.ex index a7309af42..2fb158c59 100644 --- a/spawn_operator/spawn_operator/lib/spawn_operator.ex +++ b/spawn_operator/spawn_operator/lib/spawn_operator.ex @@ -52,6 +52,12 @@ defmodule SpawnOperator do "spawn-eigr.io/sidecar-image-tag", "ghcr.io/eigr/spawn-proxy:1.4.3" ), + proxy_init_container_image_tag: + Map.get( + annotations, + "spawn-eigr.io/sidecar-init-container-image-tag", + "ghcr.io/eigr/spawn-initializer:1.4.3" + ), proxy_uds_enabled: Map.get(annotations, "spawn-eigr.io/sidecar-uds-enabled", "false"), proxy_uds_address: Map.get(annotations, "spawn-eigr.io/sidecar-uds-socket-path", "/var/run/spawn.sock"), diff --git a/spawn_operator/spawn_operator/lib/spawn_operator/handler/actor_host_handler.ex b/spawn_operator/spawn_operator/lib/spawn_operator/handler/actor_host_handler.ex index 51e39f7e6..734cea350 100644 --- a/spawn_operator/spawn_operator/lib/spawn_operator/handler/actor_host_handler.ex +++ b/spawn_operator/spawn_operator/lib/spawn_operator/handler/actor_host_handler.ex @@ -19,7 +19,7 @@ defmodule SpawnOperator.Handler.ActorHostHandler do spawn-eigr.io.sidecar.deploymentMode: "sidecar" # Optional - spawn-eigr.io.sidecar.containerImage: "docker.io/eigr/spawn-proxy" + spawn-eigr.io.sidecar.containerImage: "ghcr.io/eigr/spawn-proxy" # Optional spawn-eigr.io.sidecar.containerVersion: "1.4.3" @@ -82,7 +82,7 @@ defmodule SpawnOperator.Handler.ActorHostHandler do replicas: 1 # Optional. If negative number than autoscaling is enable host: # Mandatory - image: docker.io/eigr/spawn-springboot-examples:latest # Mandatory + image: ghcr.io/eigr/spawn-springboot-examples:latest # Mandatory embedded: false # Optional. Default false. True only when the SDK supports a native connection to the Spawn mesh network ports: - containerPort: 80 diff --git a/spawn_operator/spawn_operator/lib/spawn_operator/k8s/proxy/deployment.ex b/spawn_operator/spawn_operator/lib/spawn_operator/k8s/proxy/deployment.ex index e9c342b52..813a7992d 100644 --- a/spawn_operator/spawn_operator/lib/spawn_operator/k8s/proxy/deployment.ex +++ b/spawn_operator/spawn_operator/lib/spawn_operator/k8s/proxy/deployment.ex @@ -8,12 +8,20 @@ defmodule SpawnOperator.K8s.Proxy.Deployment do @default_actor_host_function_env [ %{ "name" => "RELEASE_NAME", - "value" => "spawn" + "value" => "proxy" }, %{ "name" => "NAMESPACE", "valueFrom" => %{"fieldRef" => %{"fieldPath" => "metadata.namespace"}} }, + %{ + "name" => "POD_NAME", + "valueFrom" => %{"fieldRef" => %{"fieldPath" => "metadata.name"}} + }, + %{ + "name" => "POD_NAMESPACE", + "valueFrom" => %{"fieldRef" => %{"fieldPath" => "metadata.namespace"}} + }, %{ "name" => "POD_IP", "valueFrom" => %{"fieldRef" => %{"fieldPath" => "status.podIP"}} @@ -70,6 +78,9 @@ defmodule SpawnOperator.K8s.Proxy.Deployment do } = _resource ) do host_params = Map.get(params, "host") + task_actors_config = %{"taskActors" => Map.get(host_params, "taskActors", %{})} + topology = Map.get(params, "topology", %{}) + replicas = max(1, Map.get(params, "replicas", @default_actor_host_function_replicas)) embedded = Map.get(host_params, "embedded", false) @@ -86,7 +97,7 @@ defmodule SpawnOperator.K8s.Proxy.Deployment do "spec" => %{ "replicas" => replicas, "selector" => %{ - "matchLabels" => %{"app" => name, "actor-system" => system} + "matchLabels" => %{"actor-system" => system} }, "strategy" => %{ "type" => "RollingUpdate", @@ -109,12 +120,23 @@ defmodule SpawnOperator.K8s.Proxy.Deployment do }, "spec" => %{ - "affinity" => Map.get(host_params, "affinity", build_affinity(system, name)), - "containers" => get_containers(embedded, system, name, host_params, annotations), + "affinity" => Map.get(topology, "affinity", build_affinity(system, name)), + "containers" => + get_containers( + embedded, + system, + name, + host_params, + annotations, + task_actors_config + ), "initContainers" => [ %{ "name" => "init-certificates", - "image" => "ghcr.io/eigr/spawn-initializer:1.4.3", + "image" => "#{annotations.proxy_init_container_image_tag}", + "env" => [ + %{"containerPort" => 4369, "name" => "epmd"} + ], "args" => [ "--environment", :prod, @@ -126,11 +148,19 @@ defmodule SpawnOperator.K8s.Proxy.Deployment do "#{system}", "--to", "#{ns}" + ], + "env" => [ + %{ + "name" => "RELEASE_DISTRIBUTION", + "value" => "none" + } ] } ], "serviceAccountName" => "#{system}-sa" } + |> maybe_put_node_selector(topology) + |> maybe_put_node_tolerations(topology) |> maybe_put_volumes(params) |> maybe_set_termination_period(params) } @@ -185,10 +215,28 @@ defmodule SpawnOperator.K8s.Proxy.Deployment do } end - defp get_containers(true, system, name, host_params, annotations) do + defp get_containers(true, system, name, host_params, annotations, task_actors_config) do actor_host_function_image = Map.get(host_params, "image") - actor_host_function_envs = Map.get(host_params, "env", []) ++ @default_actor_host_function_env + updated_default_envs = + @default_actor_host_function_env ++ + [ + %{ + "name" => "RELEASE_COOKIE", + "valueFrom" => %{ + "secretKeyRef" => %{"name" => "#{system}-secret", "key" => "RELEASE_COOKIE"} + } + } + ] + + actor_host_function_envs = + if is_nil(task_actors_config) || List.first(Map.values(task_actors_config)) == %{} do + Map.get(host_params, "env", []) ++ updated_default_envs + else + Map.get(host_params, "env", []) ++ + updated_default_envs ++ + build_task_env(task_actors_config) + end proxy_http_port = String.to_integer(annotations.proxy_http_port) @@ -230,12 +278,23 @@ defmodule SpawnOperator.K8s.Proxy.Deployment do ] end - defp get_containers(false, system, name, host_params, annotations) do + defp get_containers(false, system, name, host_params, annotations, task_actors_config) do actor_host_function_image = Map.get(host_params, "image") + updated_default_envs = + @default_actor_host_function_env ++ + [ + %{ + "name" => "RELEASE_COOKIE", + "valueFrom" => %{ + "secretKeyRef" => %{"name" => "#{system}-secret", "key" => "RELEASE_COOKIE"} + } + } + ] + actor_host_function_envs = Map.get(host_params, "env", []) ++ - @default_actor_host_function_env + updated_default_envs actor_host_function_resources = Map.get(host_params, "resources", @default_actor_host_resources) @@ -247,12 +306,19 @@ defmodule SpawnOperator.K8s.Proxy.Deployment do %{"containerPort" => proxy_http_port, "name" => "proxy-http"} ] + envs = + if is_nil(task_actors_config) || List.first(Map.values(task_actors_config)) == %{} do + updated_default_envs + else + updated_default_envs ++ build_task_env(task_actors_config) + end + proxy_container = %{ "name" => "sidecar", "image" => "#{annotations.proxy_image_tag}", "imagePullPolicy" => "Always", - "env" => @default_actor_host_function_env, + "env" => envs, "ports" => proxy_actor_host_function_ports, "livenessProbe" => %{ "httpGet" => %{ @@ -310,6 +376,29 @@ defmodule SpawnOperator.K8s.Proxy.Deployment do ] end + defp build_task_env(task_actors_config) do + value = + task_actors_config + |> Jason.encode!() + |> Base.encode32() + + [ + %{"name" => "SPAWN_PROXY_TASK_CONFIG", "value" => value} + ] + end + + defp maybe_put_node_selector(spec, %{"nodeSelector" => selectors} = _topology) do + Map.merge(spec, %{"nodeSelector" => selectors}) + end + + defp maybe_put_node_selector(spec, _), do: spec + + defp maybe_put_node_tolerations(spec, %{"tolerations" => tolerations} = _topology) do + Map.merge(spec, %{"tolerations" => tolerations}) + end + + defp maybe_put_node_tolerations(spec, _), do: spec + defp maybe_put_ports_to_host_container(spec, %{"ports" => ports}) do Map.put(spec, "ports", ports) end diff --git a/spawn_operator/spawn_operator/lib/spawn_operator/k8s/system/role.ex b/spawn_operator/spawn_operator/lib/spawn_operator/k8s/system/role.ex index 34b6a269d..825c268f8 100644 --- a/spawn_operator/spawn_operator/lib/spawn_operator/k8s/system/role.ex +++ b/spawn_operator/spawn_operator/lib/spawn_operator/k8s/system/role.ex @@ -31,6 +31,11 @@ defmodule SpawnOperator.K8s.System.Role do "apiGroups" => [""], "resources" => ["configmaps", "secrets"], "verbs" => ["*"] + }, + %{ + "apiGroups" => [""], + "resources" => ["pods"], + "verbs" => ["create", "delete", "get", "list", "patch"] } ] } diff --git a/spawn_operator/spawn_operator/lib/spawn_operator/k8s/system/secret/actor_system_secret.ex b/spawn_operator/spawn_operator/lib/spawn_operator/k8s/system/secret/actor_system_secret.ex index 22ace7b8b..6e740710c 100644 --- a/spawn_operator/spawn_operator/lib/spawn_operator/k8s/system/secret/actor_system_secret.ex +++ b/spawn_operator/spawn_operator/lib/spawn_operator/k8s/system/secret/actor_system_secret.ex @@ -96,7 +96,7 @@ defmodule SpawnOperator.K8s.System.Secret.ActorSystemSecret do cluster_heartbeat = "240000" |> Base.encode64() %{ - "NODE_COOKIE" => cookie, + "RELEASE_COOKIE" => cookie, "PROXY_ACTOR_SYSTEM_NAME" => Base.encode64(system), "PROXY_CLUSTER_POLLING" => cluster_poolling, "PROXY_CLUSTER_STRATEGY" => cluster_strategy, diff --git a/spawn_operator/spawn_operator/manifest.yaml b/spawn_operator/spawn_operator/manifest.yaml index dc821092b..553d4e1c4 100644 --- a/spawn_operator/spawn_operator/manifest.yaml +++ b/spawn_operator/spawn_operator/manifest.yaml @@ -38,7 +38,7 @@ spec: valueFrom: fieldRef: fieldPath: spec.serviceAccountName - image: ghcr.io/eigr/spawn-operator:1.4.3 + image: docker.io/eigr/spawn-operator:1.4.4-rc.38 livenessProbe: failureThreshold: 3 httpGet: @@ -64,10 +64,10 @@ spec: timeoutSeconds: 5 resources: limits: - cpu: 200m - memory: 380Mi + cpu: 1 + memory: 1024Mi requests: - cpu: 200m + cpu: 10m memory: 380Mi securityContext: allowPrivilegeEscalation: false diff --git a/spawn_operator/spawn_operator/mix.exs b/spawn_operator/spawn_operator/mix.exs index 05cdff658..fa4ecd88a 100644 --- a/spawn_operator/spawn_operator/mix.exs +++ b/spawn_operator/spawn_operator/mix.exs @@ -45,6 +45,8 @@ defmodule Operator.MixProject do {:bakeware, ">= 0.0.0", runtime: false}, {:bonny, "~> 1.1"}, {:castore, "~> 1.0"}, + {:flame, "~> 0.5", runtime: false}, + {:flame_k8s_backend, "~> 0.5", runtime: false}, {:spawn, path: "../../"} ] end diff --git a/spawn_operator/spawn_operator/mix.lock b/spawn_operator/spawn_operator/mix.lock index 21e310055..3ece58900 100644 --- a/spawn_operator/spawn_operator/mix.lock +++ b/spawn_operator/spawn_operator/mix.lock @@ -23,6 +23,8 @@ "elixir_make": {:hex, :elixir_make, "0.8.3", "d38d7ee1578d722d89b4d452a3e36bcfdc644c618f0d063b874661876e708683", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "5c99a18571a756d4af7a4d89ca75c28ac899e6103af6f223982f09ce44942cc9"}, "exqlite": {:hex, :exqlite, "0.21.0", "8d06c60b3d6df42bb4cdeb4dce4bc804788e227cead7dc190c3ffaba50bffbb4", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "b177180bb2788b761ddd5949763640aef92ed06db80d70a1130b6bede180b45f"}, "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, + "flame": {:hex, :flame, "0.5.1", "339130ed9dff761efc1b2c001839e6d16aa9af291a1e155d8c14fa9b42c03caa", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, ">= 0.0.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "b09ac62b187f40fa7f959d6faca58aae0e575ff21435a8afd79727b8e5631085"}, + "flame_k8s_backend": {:hex, :flame_k8s_backend, "0.5.4", "4a14e5dd40ae5d26c47407e15acf81e322835fbbfb9e4b9c9e200ac5bf2cfaa8", [:mix], [{:flame, "~> 0.4.0 or ~> 0.5.0", [hex: :flame, repo: "hexpm", optional: false]}], "hexpm", "2a7973223e49d869d032e732425ae67283fa779d9c5e2f759d7f113473b86c42"}, "flow": {:hex, :flow, "1.2.4", "1dd58918287eb286656008777cb32714b5123d3855956f29aa141ebae456922d", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "874adde96368e71870f3510b91e35bc31652291858c86c0e75359cbdd35eb211"}, "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, "gnat": {:hex, :gnat, "1.8.1", "f029e0bf2c073775524df939b4a4294a383199fe378d55476ca06d1717fee44e", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:cowlib, "~> 2.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:nkeys, "~> 0.2", [hex: :nkeys, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d4a9b4e90c5d457edab8161d8f2145655f1bf0abb50f2b492dd07187b7cd0376"}, @@ -56,7 +58,9 @@ "nkeys": {:hex, :nkeys, "0.2.2", "b1ab3324ed4f3a2c9658d7e80feeef86b4d15fbfd12ca5c8cf068289f582fcfa", [:mix], [{:ed25519, "~> 1.3", [hex: :ed25519, repo: "hexpm", optional: false]}], "hexpm", "3578802427b8d1d11ea6dd785c2ab774f527e2c3e449e67bd34612ab71ca471d"}, "opentelemetry": {:hex, :opentelemetry, "1.4.0", "f928923ed80adb5eb7894bac22e9a198478e6a8f04020ae1d6f289fdcad0b498", [:rebar3], [{:opentelemetry_api, "~> 1.3.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "50b32ce127413e5d87b092b4d210a3449ea80cd8224090fe68d73d576a3faa15"}, "opentelemetry_api": {:hex, :opentelemetry_api, "1.3.0", "03e2177f28dd8d11aaa88e8522c81c2f6a788170fe52f7a65262340961e663f9", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "b9e5ff775fd064fa098dba3c398490b77649a352b40b0b730a6b7dc0bdd68858"}, + "opentelemetry_ecto": {:hex, :opentelemetry_ecto, "1.2.0", "2382cb47ddc231f953d3b8263ed029d87fbf217915a1da82f49159d122b64865", [:mix], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "70dfa2e79932e86f209df00e36c980b17a32f82d175f0068bf7ef9a96cf080cf"}, "opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.7.0", "dec4e90c0667cf11a3642f7fe71982dbc0c6bfbb8725a0b13766830718cf0d98", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.4.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.3.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "d0f25f6439ec43f2561537c3fabbe177b38547cddaa3a692cbb8f4770dbefc1e"}, + "opentelemetry_process_propagator": {:hex, :opentelemetry_process_propagator, "0.3.0", "ef5b2059403a1e2b2d2c65914e6962e56371570b8c3ab5323d7a8d3444fb7f84", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "7243cb6de1523c473cba5b1aefa3f85e1ff8cc75d08f367104c1e11919c8c029"}, "opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "0.2.0", "b67fe459c2938fcab341cb0951c44860c62347c005ace1b50f8402576f241435", [:mix, :rebar3], [], "hexpm", "d61fa1f5639ee8668d74b527e6806e0503efc55a42db7b5f39939d84c07d6895"}, "owl": {:hex, :owl, "0.8.0", "0ef925cb784311093d4e3734822960cbdbdb13b095d748bb5bc82abcd5b56732", [:mix], [], "hexpm", "0a5586ceb1a12f4bbda90e330c20e6ea034552335d09466c10e4218c98977529"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, diff --git a/spawn_operator/spawn_operator/namespaced-roles.yaml b/spawn_operator/spawn_operator/namespaced-roles.yaml index 4f7812086..9b00132eb 100644 --- a/spawn_operator/spawn_operator/namespaced-roles.yaml +++ b/spawn_operator/spawn_operator/namespaced-roles.yaml @@ -11,6 +11,16 @@ rules: - secrets verbs: - "*" + - apiGroups: + - "" + resources: + - "pods" + verbs: + - "create" + - "get" + - "list" + - "delete" + - "patch" --- apiVersion: v1 diff --git a/spawn_operator/spawn_operator/test/resources/actorhost/deployment_test.exs b/spawn_operator/spawn_operator/test/resources/actorhost/deployment_test.exs index 8b3b7ad5a..1d92f13e9 100644 --- a/spawn_operator/spawn_operator/test/resources/actorhost/deployment_test.exs +++ b/spawn_operator/spawn_operator/test/resources/actorhost/deployment_test.exs @@ -11,6 +11,9 @@ defmodule DeploymentTest do simple_host_with_ports = build_simple_actor_host_with_ports() simple_actor_host_with_volume_mounts = build_simple_actor_host_with_volume_mounts() embedded_actor_host = build_embedded_actor_host() + + embedded_actor_host_with_node_selector = build_embedded_actor_host_with_node_selector() + embedded_actor_host_with_task_actors = build_embedded_actor_host_with_task_actors() embedded_actor_host_with_volume_mounts = build_embedded_actor_host_with_volume_mounts() %{ @@ -18,6 +21,8 @@ defmodule DeploymentTest do simple_host_with_ports: simple_host_with_ports, simple_actor_host_with_volume_mounts: simple_actor_host_with_volume_mounts, embedded_actor_host: embedded_actor_host, + embedded_actor_host_with_node_selector: embedded_actor_host_with_node_selector, + embedded_actor_host_with_task_actors: embedded_actor_host_with_task_actors, embedded_actor_host_with_volume_mounts: embedded_actor_host_with_volume_mounts } end @@ -170,6 +175,307 @@ defmodule DeploymentTest do } == build_host_deploy(embedded_actor_host) end + test "generate embedded deployment with defaults and node selector", ctx do + %{ + embedded_actor_host_with_node_selector: embedded_actor_host_with_node_selector + } = ctx + + assert %{ + "apiVersion" => "apps/v1", + "kind" => "Deployment", + "metadata" => %{ + "labels" => %{"actor-system" => "spawn-system", "app" => "spawn-test"}, + "name" => "spawn-test", + "namespace" => "default" + }, + "spec" => %{ + "replicas" => 1, + "selector" => %{ + "matchLabels" => %{"actor-system" => "spawn-system", "app" => "spawn-test"} + }, + "strategy" => %{ + "rollingUpdate" => %{"maxSurge" => "50%", "maxUnavailable" => 0}, + "type" => "RollingUpdate" + }, + "template" => %{ + "metadata" => %{ + "annotations" => %{ + "prometheus.io/path" => "/metrics", + "prometheus.io/port" => "9001", + "prometheus.io/scrape" => "true" + }, + "labels" => %{"actor-system" => "spawn-system", "app" => "spawn-test"} + }, + "spec" => %{ + "affinity" => %{ + "podAffinity" => %{ + "preferredDuringSchedulingIgnoredDuringExecution" => [ + %{ + "weight" => 50, + "podAffinityTerm" => %{ + "labelSelector" => %{ + "matchExpressions" => [ + %{ + "key" => "actor-system", + "operator" => "In", + "values" => [ + "spawn-system" + ] + } + ] + }, + "topologyKey" => "kubernetes.io/hostname" + } + } + ] + }, + "podAntiAffinity" => %{ + "preferredDuringSchedulingIgnoredDuringExecution" => [ + %{ + "weight" => 100, + "podAffinityTerm" => %{ + "labelSelector" => %{ + "matchExpressions" => [ + %{ + "key" => "app", + "operator" => "In", + "values" => [ + "spawn-test" + ] + } + ] + }, + "topologyKey" => "kubernetes.io/hostname" + } + } + ] + } + }, + "containers" => [ + %{ + "env" => [ + %{"name" => "RELEASE_NAME", "value" => "spawn"}, + %{ + "name" => "NAMESPACE", + "valueFrom" => %{ + "fieldRef" => %{"fieldPath" => "metadata.namespace"} + } + }, + %{ + "name" => "POD_IP", + "valueFrom" => %{"fieldRef" => %{"fieldPath" => "status.podIP"}} + }, + %{"name" => "SPAWN_PROXY_PORT", "value" => "9001"}, + %{"name" => "SPAWN_PROXY_INTERFACE", "value" => "0.0.0.0"}, + %{"name" => "RELEASE_DISTRIBUTION", "value" => "name"}, + %{"name" => "RELEASE_NODE", "value" => "$(RELEASE_NAME)@$(POD_IP)"} + ], + "envFrom" => [ + %{"configMapRef" => %{"name" => "spawn-test-sidecar-cm"}}, + %{"secretRef" => %{"name" => "spawn-system-secret"}} + ], + "image" => "eigr/spawn-test:latest", + "name" => "actorhost", + "ports" => [ + %{"containerPort" => 4369, "name" => "epmd"}, + %{"containerPort" => 9001, "name" => "proxy-http"} + ], + "resources" => %{ + "requests" => %{ + "cpu" => "100m", + "ephemeral-storage" => "1M", + "memory" => "80Mi" + } + }, + "volumeMounts" => [%{"mountPath" => "/app/certs", "name" => "certs"}] + } + ], + "terminationGracePeriodSeconds" => 405, + "initContainers" => [ + %{ + "args" => [ + "--environment", + :prod, + "--secret", + "tls-certs", + "--namespace", + "default", + "--service", + "spawn-system", + "--to", + "default" + ], + "image" => "ghcr.io/eigr/spawn-initializer:1.4.3", + "name" => "init-certificates" + } + ], + "serviceAccountName" => "spawn-system-sa", + "volumes" => [ + %{ + "name" => "certs", + "secret" => %{"optional" => true, "secretName" => "tls-certs"} + } + ], + "nodeSelector" => %{"gpu" => "false"} + } + } + } + } == build_host_deploy(embedded_actor_host_with_node_selector) + end + + test "generate embedded deployment with defaults and node selector and task actors", ctx do + %{ + embedded_actor_host_with_task_actors: embedded_actor_host_with_task_actors + } = ctx + + assert %{ + "apiVersion" => "apps/v1", + "kind" => "Deployment", + "metadata" => %{ + "labels" => %{"actor-system" => "spawn-system", "app" => "spawn-test"}, + "name" => "spawn-test", + "namespace" => "default" + }, + "spec" => %{ + "replicas" => 1, + "selector" => %{ + "matchLabels" => %{"actor-system" => "spawn-system", "app" => "spawn-test"} + }, + "strategy" => %{ + "rollingUpdate" => %{"maxSurge" => "50%", "maxUnavailable" => 0}, + "type" => "RollingUpdate" + }, + "template" => %{ + "metadata" => %{ + "annotations" => %{ + "prometheus.io/path" => "/metrics", + "prometheus.io/port" => "9001", + "prometheus.io/scrape" => "true" + }, + "labels" => %{"actor-system" => "spawn-system", "app" => "spawn-test"} + }, + "spec" => %{ + "affinity" => %{ + "podAffinity" => %{ + "preferredDuringSchedulingIgnoredDuringExecution" => [ + %{ + "weight" => 50, + "podAffinityTerm" => %{ + "labelSelector" => %{ + "matchExpressions" => [ + %{ + "key" => "actor-system", + "operator" => "In", + "values" => [ + "spawn-system" + ] + } + ] + }, + "topologyKey" => "kubernetes.io/hostname" + } + } + ] + }, + "podAntiAffinity" => %{ + "preferredDuringSchedulingIgnoredDuringExecution" => [ + %{ + "weight" => 100, + "podAffinityTerm" => %{ + "labelSelector" => %{ + "matchExpressions" => [ + %{ + "key" => "app", + "operator" => "In", + "values" => [ + "spawn-test" + ] + } + ] + }, + "topologyKey" => "kubernetes.io/hostname" + } + } + ] + } + }, + "containers" => [ + %{ + "env" => [ + %{"name" => "RELEASE_NAME", "value" => "spawn"}, + %{ + "name" => "NAMESPACE", + "valueFrom" => %{ + "fieldRef" => %{"fieldPath" => "metadata.namespace"} + } + }, + %{ + "name" => "POD_IP", + "valueFrom" => %{"fieldRef" => %{"fieldPath" => "status.podIP"}} + }, + %{"name" => "SPAWN_PROXY_PORT", "value" => "9001"}, + %{"name" => "SPAWN_PROXY_INTERFACE", "value" => "0.0.0.0"}, + %{"name" => "RELEASE_DISTRIBUTION", "value" => "name"}, + %{"name" => "RELEASE_NODE", "value" => "$(RELEASE_NAME)@$(POD_IP)"}, + %{ + "name" => "SPAWN_PROXY_TASK_CONFIG", + "value" => + "PMRHIYLTNNAWG5DPOJZSEOS3PMRGCY3UN5ZE4YLNMURDUISKN5ZWKIRMEJ2G64DPNRXWO6JCHJ5SE3TPMRSVGZLMMVRXI33SEI5HWITHOB2SEORCMZQWY43FEJ6X27K5PU======" + } + ], + "envFrom" => [ + %{"configMapRef" => %{"name" => "spawn-test-sidecar-cm"}}, + %{"secretRef" => %{"name" => "spawn-system-secret"}} + ], + "image" => "eigr/spawn-test:latest", + "name" => "actorhost", + "ports" => [ + %{"containerPort" => 4369, "name" => "epmd"}, + %{"containerPort" => 9001, "name" => "proxy-http"} + ], + "resources" => %{ + "requests" => %{ + "cpu" => "100m", + "ephemeral-storage" => "1M", + "memory" => "80Mi" + } + }, + "volumeMounts" => [%{"mountPath" => "/app/certs", "name" => "certs"}] + } + ], + "terminationGracePeriodSeconds" => 405, + "initContainers" => [ + %{ + "args" => [ + "--environment", + :prod, + "--secret", + "tls-certs", + "--namespace", + "default", + "--service", + "spawn-system", + "--to", + "default" + ], + "image" => "ghcr.io/eigr/spawn-initializer:1.4.3", + "name" => "init-certificates" + } + ], + "serviceAccountName" => "spawn-system-sa", + "volumes" => [ + %{ + "name" => "certs", + "secret" => %{"optional" => true, "secretName" => "tls-certs"} + } + ], + "nodeSelector" => %{"gpu" => "false"} + } + } + } + } == build_host_deploy(embedded_actor_host_with_task_actors) + end + test "generate embedded deployment with volumeMount", ctx do %{ embedded_actor_host_with_volume_mounts: embedded_actor_host_with_volume_mounts diff --git a/spawn_operator/spawn_operator/test/support/factory.ex b/spawn_operator/spawn_operator/test/support/factory.ex index 221f9cb45..80b93205b 100644 --- a/spawn_operator/spawn_operator/test/support/factory.ex +++ b/spawn_operator/spawn_operator/test/support/factory.ex @@ -20,6 +20,64 @@ defmodule SpawnOperator.FactoryTest do } end + def build_embedded_actor_host_with_node_selector(attrs \\ []) do + %{ + "apiVersion" => "spawn-eigr.io/v1", + "kind" => "ActorHost", + "metadata" => %{ + "name" => attrs[:name] || "spawn-test", + "system" => "spawn-system", + "namespace" => "default", + "generation" => 1 + }, + "spec" => %{ + "topology" => %{ + "nodeSelector" => %{ + "gpu" => "false" + } + }, + "host" => %{ + "embedded" => true, + "image" => attrs[:host_image] || "eigr/spawn-test:latest" + } + } + } + end + + def build_embedded_actor_host_with_task_actors(attrs \\ []) do + %{ + "apiVersion" => "spawn-eigr.io/v1", + "kind" => "ActorHost", + "metadata" => %{ + "name" => attrs[:name] || "spawn-test", + "system" => "spawn-system", + "namespace" => "default", + "generation" => 1 + }, + "spec" => %{ + "topology" => %{ + "nodeSelector" => %{ + "gpu" => "false" + } + }, + "host" => %{ + "embedded" => true, + "image" => attrs[:host_image] || "eigr/spawn-test:latest", + "taskActors" => [ + %{ + "actorName" => "Jose", + "topology" => %{ + "nodeSelector" => %{ + "gpu" => "false" + } + } + } + ] + } + } + } + end + def build_embedded_actor_host_with_volume_mounts(attrs \\ []) do %{ "apiVersion" => "spawn-eigr.io/v1", diff --git a/spawn_proxy/proxy/lib/proxy/application.ex b/spawn_proxy/proxy/lib/proxy/application.ex index a6d9ad7f5..cc8dbd432 100644 --- a/spawn_proxy/proxy/lib/proxy/application.ex +++ b/spawn_proxy/proxy/lib/proxy/application.ex @@ -17,6 +17,7 @@ defmodule Proxy.Application do OpentelemetryEcto.setup([:spawn_statestores, :repo]) Config.load() + Logger.configure(level: Config.get(:logger_level)) children = [ diff --git a/spawn_sdk/spawn_sdk/lib/system/spawn_system.ex b/spawn_sdk/spawn_sdk/lib/system/spawn_system.ex index 4c16792a8..420ced8fd 100644 --- a/spawn_sdk/spawn_sdk/lib/system/spawn_system.ex +++ b/spawn_sdk/spawn_sdk/lib/system/spawn_system.ex @@ -566,6 +566,8 @@ defmodule SpawnSdk.System.SpawnSystem do def decode_kind(:singleton), do: :NAMED def decode_kind(:named), do: :NAMED def decode_kind(:NAMED), do: :NAMED + def decode_kind(:TASK), do: :TASK + def decode_kind(:task), do: :TASK def decode_kind(:pooled), do: :POOLED def decode_kind(:POOLED), do: :POOLED def decode_kind(_), do: :UNKNOW_KIND diff --git a/spawn_sdk/spawn_sdk/test/actor/actor_test.exs b/spawn_sdk/spawn_sdk/test/actor/actor_test.exs index 6e43d64a2..036ca9a0d 100644 --- a/spawn_sdk/spawn_sdk/test/actor/actor_test.exs +++ b/spawn_sdk/spawn_sdk/test/actor/actor_test.exs @@ -3,6 +3,32 @@ defmodule Actor.ActorTest do require Logger + alias Eigr.Spawn.Actor.{MyMessageRequest, MyMessageResponse} + + defmodule Actor.TaskActor do + use SpawnSdk.Actor, + name: "task_actor_ref", + kind: :task, + stateful: true, + state_type: Eigr.Spawn.Actor.MyState, + tags: [{"foo", "none"}, {"bar", "unchanged"}] + + defact init(_) do + Value.noreply_state!(%{value: 0}) + end + + action("sum", fn %Context{} = ctx, %MyMessageRequest{id: id, data: data} -> + current_state = ctx.state + new_state = current_state + 1 + + response = %MyMessageResponse{id: id, data: data} + result = %Value{state: new_state, value: response} + + result + |> Value.noreply!() + end) + end + defmodule Actor.MyActor do use SpawnSdk.Actor, name: "my_actor_ref", @@ -225,6 +251,7 @@ defmodule Actor.ActorTest do Actor.PooledActor, Actor.JsonActor, Actor.BroadcastActor, + Actor.TaskActor, Actor.TimerActor ] } @@ -271,6 +298,24 @@ defmodule Actor.ActorTest do end end + describe "invoke task actors" do + test "simple invoke task actor", ctx do + system = ctx.system + actor_name = "task_actor_ref" + + SpawnSdk.invoke(actor_name, + action: "sum", + system: system, + payload: %{value: 999} + ) + + assert SpawnSdk.invoke(actor_name, + action: "getState", + system: system + ) == {:ok, %{value: 999}} + end + end + describe "invoke json actor" do test "simple default function call returning only map without payload", ctx do system = ctx.system diff --git a/test/actors/actors_test.exs b/test/actors/actors_test.exs index 770ba2a9e..845d731fd 100644 --- a/test/actors/actors_test.exs +++ b/test/actors/actors_test.exs @@ -111,6 +111,30 @@ defmodule ActorsTest do Actors.invoke(invoke_request) end + test "invoke task actor function for a newly registered actor" do + actor_name = "Jose" + + actor = build_actor(name: actor_name, kind: :TASK) + actor_entry = build_actor_entry(name: actor_name, actor: actor) + registry = build_registry_with_actors(actors: actor_entry) + system = build_system(registry: registry) + + request = build_registration_request(actor_system: system) + + {:ok, %RegistrationResponse{}} = Actors.register(request) + + # invoke + invoke_request = build_invocation_request(system: system, actor: actor) + + host_invoke_response = + build_host_invoke_response(actor_name: actor_name, system_name: system.name) + + mock_invoke_host_actor_with_ok_response(host_invoke_response) + + assert {:ok, %ActorInvocationResponse{actor_name: ^actor_name}} = + Actors.invoke(invoke_request) + end + @tag :skip test "invoke actor function for a already registered actor in another node", ctx do %{system: system, actor: actor} = ctx diff --git a/test/support/factory.ex b/test/support/factory.ex index ec731d251..484da6caa 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -99,6 +99,7 @@ defmodule Actors.FactoryTest do id: %ActorId{name: actor_name, system: attrs[:system]}, actions: attrs[:actions] || [build_actor_action()], settings: %ActorSettings{ + kind: Keyword.get(attrs, :kind, :NAMED), stateful: Keyword.get(attrs, :stateful, true), snapshot_strategy: attrs[:snapshot_strategy] || build_actor_snapshot_strategy(), deactivation_strategy: