github.com/projecteru2/core/client is the Go client other services use to talk to core. It is
a thin wrapper around the generated pb.CoreRPCClient plus the pieces that make a multi-instance
deployment usable: address resolution, load balancing, and authentication.
import (
"github.com/projecteru2/core/client"
pb "github.com/projecteru2/core/rpc/gen"
"github.com/projecteru2/core/types"
)
cli, err := client.NewClient(ctx, "127.0.0.1:5001", types.AuthConfig{})
if err != nil {
return err
}
rpc := cli.GetRPCClient()
info, err := rpc.Info(ctx, &pb.Empty{})
GetRPCClient() returns the generated CoreRPCClient. The connection is
configured with insecure transport credentials, a 6-minute keepalive ping with a 1-second
timeout, and round_robin load balancing across whatever the resolver produced.
If core requires auth, pass the same username and password as its auth config; the client
installs per-RPC credentials that send them as one metadata pair. Note this travels in the clear —
the client does not enable TLS.
Importing client registers two gRPC resolvers, so addr may be:
| Target | Meaning |
|---|---|
host:port |
one instance, no resolution |
static://_/addr1,addr2,addr3 |
a fixed set of instances, round-robined |
eru:///addr |
bootstrap from one instance, then follow service discovery |
eru:// is the interesting one: the client connects to the given address, subscribes to
WatchServiceStatus, and rewrites the connection’s address list every time core pushes a new set.
Instances that come up join the round-robin pool, instances that go away leave it — no restart, no
config change. A push with no addresses is ignored: a connection with none could never learn new
ones. The outer connection takes its credentials from the types.AuthConfig argument to
NewClient; the eru:// resolver takes the service-discovery credentials from the URL authority.
For callers that would rather hold several independent connections and pick a live one:
pool, err := client.NewCoreRPCClientPool(ctx, &client.PoolConfig{
EruAddrs: []string{"10.0.0.1:5001", "10.0.0.2:5001"},
Auth: types.AuthConfig{},
ConnectionTimeout: 10 * time.Second,
})
if err != nil {
return err
}
rpc := pool.GetClient()
The pool dials every address at construction, drops the ones that fail, and returns
ErrAllConnectionsFailed if none answered. A background loop re-probes every client with Info
every 2 × ConnectionTimeout; GetClient returns the first client currently marked alive, or the
first client at all if everything is down.
If you only want the address list — to feed your own balancer, say:
import "github.com/projecteru2/core/client/servicediscovery"
sd := servicediscovery.New("127.0.0.1:5001", types.AuthConfig{})
ch, err := sd.Watch(ctx)
for addresses := range ch {
// current set of live core instances
}
Watch reconnects on its own: a failed open is retried after 10 seconds, a broken stream is
re-established at once, and if no push arrives within the interval core advertised (twice
grpc.service_discovery_interval) it cancels and re-establishes the watch rather than sitting on
a silent connection.
The client does not retry. Unary calls are never replayed, because a resource operation is not safe to replay blindly, and a broken stream surfaces to its caller as an error.
The two watch streams are the only replayable calls, and each is re-established by its consumer rather than by the connection:
/pb.CoreRPC/WatchServiceStatus — servicediscovery.Watch opens a new stream as soon as the
current one breaks, and a watchdog cancels one that has gone silent./pb.CoreRPC/WorkloadStatusStream — the agent resubscribes.