This is the initial step towards redefining Lumi as a library that runs atop vanilla Node.js/V8, rather than as its own runtime. This change is woefully incomplete but this includes some of the more stable pieces of my current work-in-progress. The new structure is that within the sdk/ directory we will have a client library per language. This client library contains the object model for Lumi (resources, properties, assets, config, etc), in addition to the "language runtime host" components required to interoperate with the Lumi resource monitor. This resource monitor is effectively what we call "Lumi" today, in that it's the thing orchestrating plans and deployments. Inside the sdk/ directory, you will find nodejs/, the Node.js client library, alongside proto/, the definitions for RPC interop between the different pieces of the system. This includes existing RPC definitions for resource providers, etc., in addition to the new ones for hosting different language runtimes from within Lumi. These new interfaces are surprisingly simple. There is effectively a bidirectional RPC channel between the Lumi resource monitor, represented by the lumirpc.ResourceMonitor interface, and each language runtime, represented by the lumirpc.LanguageRuntime interface. The overall orchestration goes as follows: 1) Lumi decides it needs to run a program written in language X, so it dynamically loads the language runtime plugin for language X. 2) Lumi passes that runtime a loopback address to its ResourceMonitor service, while language X will publish a connection back to its LanguageRuntime service, which Lumi will talk to. 3) Lumi then invokes LanguageRuntime.Run, passing information like the desired working directory, program name, arguments, and optional configuration variables to make available to the program. 4) The language X runtime receives this, unpacks it and sets up the necessary context, and then invokes the program. The program then calls into Lumi object model abstractions that internally communicate back to Lumi using the ResourceMonitor interface. 5) The key here is ResourceMonitor.NewResource, which Lumi uses to serialize state about newly allocated resources. Lumi receives these and registers them as part of the plan, doing the usual diffing, etc., to decide how to proceed. This interface is perhaps one of the most subtle parts of the new design, as it necessitates the use of promises internally to allow parallel evaluation of the resource plan, letting dataflow determine the available concurrency. 6) The program exits, and Lumi continues on its merry way. If the program fails, the RunResponse will include information about the failure. Due to (5), all properties on resources are now instances of a new Property<T> type. A Property<T> is just a thin wrapper over a T, but it encodes the special properties of Lumi resource properties. Namely, it is possible to create one out of a T, other Property<T>, Promise<T>, or to freshly allocate one. In all cases, the Property<T> does not "settle" until its final state is known. This cannot occur before the deployment actually completes, and so in general it's not safe to depend on concrete resolutions of values (unlike ordinary Promise<T>s which are usually expected to resolve). As a result, all derived computations are meant to use the `then` function (as in `someValue.then(v => v+x)`). Although this change includes tests that may be run in isolation to test the various RPC interactions, we are nowhere near finished. The remaining work primarily boils down to three things: 1) Wiring all of this up to the Lumi code. 2) Fixing the handful of known loose ends required to make this work, primarily around the serialization of properties (waiting on unresolved ones, serializing assets properly, etc). 3) Implementing lambda closure serialization as a native extension. This ongoing work is part of pulumi/pulumi-fabric#311.
37 lines
1.6 KiB
Bash
Executable file
37 lines
1.6 KiB
Bash
Executable file
#!/bin/bash
|
|
# This script regenerates all Protobuf/gRPC client files.
|
|
#
|
|
# For now, it must be run manually, and the results are checked into source control. Eventually we might choose to
|
|
# automate this process as part of the overall build so that it's less manual and hence error prone.
|
|
#
|
|
# To run this script, the following pre-requisites are necessary:
|
|
#
|
|
# 1) Install the latest Protobuf compiler from https://github.com/google/protobuf/releases.
|
|
# 2) Add the `protoc` binary to your PATH (so that it can be found below).
|
|
# 3) Install the Golang Protobuf compiler by running this command from your Go workspace (also on your PATH):
|
|
# go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
|
|
# 4) Install the Node.js gRPC SDK, which includes the gRPC Node.js compiler plugin
|
|
# npm install -g grpc-tools
|
|
# and add the `grpc_tools_node_protoc_plugin` binary to your PATH.
|
|
#
|
|
# The results are checked into bin/; at this moment, they need to be copied to their final destinations manually.
|
|
set -e
|
|
|
|
PROTOC=$(which protoc || { >&2 echo "error: Protobuf compiler (protoc) not found on PATH"; exit 1; })
|
|
|
|
echo Generating Protobuf/gRPC SDK files:
|
|
|
|
GO_LUMIRPC=./go
|
|
GO_PROTOFLAGS="plugins=grpc"
|
|
echo -e "\tGo: $GO_LUMIRPC [$GO_PROTOFLAGS]"
|
|
mkdir -p $GO_LUMIRPC
|
|
$PROTOC --go_out=$GO_PROTOFLAGS:$GO_LUMIRPC *.proto
|
|
|
|
JS_LUMIRPC=./nodejs
|
|
JS_PROTOFLAGS="import_style=commonjs,binary"
|
|
echo -e "\tJS: $JS_LUMIRPC [$JS_PROTOFLAGS]"
|
|
mkdir -p $JS_LUMIRPC
|
|
$PROTOC --js_out=$JS_PROTOFLAGS:$JS_LUMIRPC --grpc_out=$JS_LUMIRPC --plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin` *.proto
|
|
|
|
echo Done.
|
|
|