-
Notifications
You must be signed in to change notification settings - Fork 113
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Loading branch information
Showing
10 changed files
with
607 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package discovery | ||
|
||
import ( | ||
"context" | ||
|
||
"go.opencensus.io/trace" | ||
pb "go.viam.com/api/service/discovery/v1" | ||
"go.viam.com/utils/protoutils" | ||
"go.viam.com/utils/rpc" | ||
|
||
"go.viam.com/rdk/config" | ||
"go.viam.com/rdk/logging" | ||
rprotoutils "go.viam.com/rdk/protoutils" | ||
"go.viam.com/rdk/resource" | ||
) | ||
|
||
// client implements DiscoveryServiceClient. | ||
type client struct { | ||
resource.Named | ||
resource.TriviallyReconfigurable | ||
resource.TriviallyCloseable | ||
name string | ||
client pb.DiscoveryServiceClient | ||
logger logging.Logger | ||
} | ||
|
||
// NewClientFromConn constructs a new Client from the connection passed in. | ||
func NewClientFromConn( | ||
ctx context.Context, | ||
conn rpc.ClientConn, | ||
remoteName string, | ||
name resource.Name, | ||
logger logging.Logger, | ||
) (Service, error) { | ||
grpcClient := pb.NewDiscoveryServiceClient(conn) | ||
c := &client{ | ||
Named: name.PrependRemote(remoteName).AsNamed(), | ||
name: name.ShortName(), | ||
client: grpcClient, | ||
logger: logger, | ||
} | ||
return c, nil | ||
} | ||
|
||
func (c *client) DiscoverResources(ctx context.Context, extra map[string]any) ([]resource.Config, error) { | ||
ctx, span := trace.StartSpan(ctx, "discovery::client::DoCommand") | ||
defer span.End() | ||
ext, err := protoutils.StructToStructPb(extra) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
req := &pb.DiscoverResourcesRequest{Name: c.name, Extra: ext} | ||
resp, err := c.client.DiscoverResources(ctx, req) | ||
if err != nil { | ||
return nil, err | ||
} | ||
protoConfigs := resp.GetDiscoveries() | ||
if protoConfigs == nil { | ||
return nil, ErrNilResponse | ||
} | ||
|
||
discoveredConfigs := []resource.Config{} | ||
for _, proto := range protoConfigs { | ||
config, err := config.ComponentConfigFromProto(proto) | ||
if err != nil { | ||
return nil, err | ||
} | ||
discoveredConfigs = append(discoveredConfigs, *config) | ||
} | ||
return discoveredConfigs, nil | ||
} | ||
|
||
func (c *client) DoCommand(ctx context.Context, cmd map[string]interface{}) (map[string]interface{}, error) { | ||
ctx, span := trace.StartSpan(ctx, "discovery::client::DoCommand") | ||
defer span.End() | ||
|
||
return rprotoutils.DoFromResourceClient(ctx, c.client, c.name, cmd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package discovery_test | ||
|
||
import ( | ||
"context" | ||
"net" | ||
"testing" | ||
|
||
"go.viam.com/test" | ||
"go.viam.com/utils/rpc" | ||
|
||
viamgrpc "go.viam.com/rdk/grpc" | ||
"go.viam.com/rdk/logging" | ||
"go.viam.com/rdk/resource" | ||
"go.viam.com/rdk/services/discovery" | ||
"go.viam.com/rdk/testutils" | ||
"go.viam.com/rdk/testutils/inject" | ||
) | ||
|
||
func TestClient(t *testing.T) { | ||
logger := logging.NewTestLogger(t) | ||
listener1, err := net.Listen("tcp", "localhost:0") | ||
test.That(t, err, test.ShouldBeNil) | ||
rpcServer, err := rpc.NewServer(logger, rpc.WithUnauthenticated()) | ||
test.That(t, err, test.ShouldBeNil) | ||
|
||
testComponents := []resource.Config{createTestComponent("component-1"), createTestComponent("component-2")} | ||
|
||
workingDiscovery := inject.NewDiscoveryService(testDiscoveryName) | ||
workingDiscovery.DiscoverResourcesFunc = func(ctx context.Context, extra map[string]any) ([]resource.Config, error) { | ||
return testComponents, nil | ||
} | ||
workingDiscovery.DoFunc = testutils.EchoFunc | ||
|
||
failingDiscovery := inject.NewDiscoveryService(failDiscoveryName) | ||
failingDiscovery.DiscoverResourcesFunc = func(ctx context.Context, extra map[string]any) ([]resource.Config, error) { | ||
return nil, errDiscoverFailed | ||
} | ||
failingDiscovery.DoFunc = func( | ||
ctx context.Context, | ||
cmd map[string]interface{}, | ||
) ( | ||
map[string]interface{}, | ||
error, | ||
) { | ||
return nil, errDoFailed | ||
} | ||
|
||
resourceMap := map[resource.Name]discovery.Service{ | ||
discovery.Named(testDiscoveryName): workingDiscovery, | ||
discovery.Named(failDiscoveryName): failingDiscovery, | ||
} | ||
discoverySvc, err := resource.NewAPIResourceCollection(discovery.API, resourceMap) | ||
test.That(t, err, test.ShouldBeNil) | ||
resourceAPI, ok, err := resource.LookupAPIRegistration[discovery.Service](discovery.API) | ||
test.That(t, err, test.ShouldBeNil) | ||
test.That(t, ok, test.ShouldBeTrue) | ||
test.That(t, resourceAPI.RegisterRPCService(context.Background(), rpcServer, discoverySvc), test.ShouldBeNil) | ||
|
||
go rpcServer.Serve(listener1) | ||
defer rpcServer.Stop() | ||
|
||
t.Run("Failing client", func(t *testing.T) { | ||
cancelCtx, cancel := context.WithCancel(context.Background()) | ||
cancel() | ||
_, err = viamgrpc.Dial(cancelCtx, listener1.Addr().String(), logger) | ||
test.That(t, err, test.ShouldNotBeNil) | ||
test.That(t, err, test.ShouldBeError, context.Canceled) | ||
}) | ||
|
||
t.Run("client tests for working discovery", func(t *testing.T) { | ||
conn, err := viamgrpc.Dial(context.Background(), listener1.Addr().String(), logger) | ||
test.That(t, err, test.ShouldBeNil) | ||
workingDiscoveryClient, err := discovery.NewClientFromConn(context.Background(), conn, "", discovery.Named(testDiscoveryName), logger) | ||
test.That(t, err, test.ShouldBeNil) | ||
|
||
respDis, err := workingDiscoveryClient.DiscoverResources(context.Background(), nil) | ||
test.That(t, err, test.ShouldBeNil) | ||
test.That(t, len(respDis), test.ShouldEqual, len(testComponents)) | ||
for index, actual := range respDis { | ||
expected := testComponents[index] | ||
validateComponent(t, actual, expected) | ||
} | ||
|
||
resp, err := workingDiscoveryClient.DoCommand(context.Background(), testutils.TestCommand) | ||
test.That(t, err, test.ShouldBeNil) | ||
test.That(t, resp["cmd"], test.ShouldEqual, testutils.TestCommand["cmd"]) | ||
test.That(t, resp["data"], test.ShouldEqual, testutils.TestCommand["data"]) | ||
|
||
test.That(t, workingDiscoveryClient.Close(context.Background()), test.ShouldBeNil) | ||
test.That(t, conn.Close(), test.ShouldBeNil) | ||
}) | ||
|
||
t.Run("client tests for failing discovery", func(t *testing.T) { | ||
conn, err := viamgrpc.Dial(context.Background(), listener1.Addr().String(), logger) | ||
test.That(t, err, test.ShouldBeNil) | ||
failingDiscoveryClient, err := discovery.NewClientFromConn(context.Background(), conn, "", discovery.Named(failDiscoveryName), logger) | ||
test.That(t, err, test.ShouldBeNil) | ||
|
||
_, err = failingDiscoveryClient.DiscoverResources(context.Background(), nil) | ||
test.That(t, err, test.ShouldNotBeNil) | ||
test.That(t, err.Error(), test.ShouldContainSubstring, errDiscoverFailed.Error()) | ||
|
||
_, err = failingDiscoveryClient.DoCommand(context.Background(), testutils.TestCommand) | ||
test.That(t, err, test.ShouldNotBeNil) | ||
test.That(t, err.Error(), test.ShouldContainSubstring, errDoFailed.Error()) | ||
|
||
test.That(t, failingDiscoveryClient.Close(context.Background()), test.ShouldBeNil) | ||
test.That(t, conn.Close(), test.ShouldBeNil) | ||
}) | ||
|
||
t.Run("client tests for failing discovery due to nil response", func(t *testing.T) { | ||
conn, err := viamgrpc.Dial(context.Background(), listener1.Addr().String(), logger) | ||
test.That(t, err, test.ShouldBeNil) | ||
failingDiscoveryClient, err := discovery.NewClientFromConn(context.Background(), conn, "", discovery.Named(failDiscoveryName), logger) | ||
test.That(t, err, test.ShouldBeNil) | ||
|
||
failingDiscovery.DiscoverResourcesFunc = func(ctx context.Context, extra map[string]any) ([]resource.Config, error) { | ||
return nil, nil | ||
} | ||
_, err = failingDiscoveryClient.DiscoverResources(context.Background(), nil) | ||
test.That(t, err, test.ShouldNotBeNil) | ||
test.That(t, err.Error(), test.ShouldContainSubstring, discovery.ErrNilResponse.Error()) | ||
|
||
test.That(t, failingDiscoveryClient.Close(context.Background()), test.ShouldBeNil) | ||
test.That(t, conn.Close(), test.ShouldBeNil) | ||
}) | ||
|
||
t.Run("dialed client tests for working discovery", func(t *testing.T) { | ||
conn, err := viamgrpc.Dial(context.Background(), listener1.Addr().String(), logger) | ||
test.That(t, err, test.ShouldBeNil) | ||
client, err := resourceAPI.RPCClient(context.Background(), conn, "", discovery.Named(testDiscoveryName), logger) | ||
test.That(t, err, test.ShouldBeNil) | ||
|
||
resp, err := client.DoCommand(context.Background(), testutils.TestCommand) | ||
test.That(t, err, test.ShouldBeNil) | ||
test.That(t, resp["cmd"], test.ShouldEqual, testutils.TestCommand["cmd"]) | ||
test.That(t, resp["data"], test.ShouldEqual, testutils.TestCommand["data"]) | ||
|
||
test.That(t, conn.Close(), test.ShouldBeNil) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// Package discovery implements the discovery service, which lets users surface resource configs for their machines to use. | ||
package discovery | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
|
||
pb "go.viam.com/api/service/discovery/v1" | ||
|
||
"go.viam.com/rdk/resource" | ||
"go.viam.com/rdk/robot" | ||
) | ||
|
||
func init() { | ||
resource.RegisterAPI(API, resource.APIRegistration[Service]{ | ||
RPCServiceServerConstructor: NewRPCServiceServer, | ||
RPCServiceHandler: pb.RegisterDiscoveryServiceHandlerFromEndpoint, | ||
RPCServiceDesc: &pb.DiscoveryService_ServiceDesc, | ||
RPCClient: NewClientFromConn, | ||
}) | ||
} | ||
|
||
// SubtypeName is the name of the type of service. | ||
const ( | ||
SubtypeName = "discovery" | ||
) | ||
|
||
// API is a variable that identifies the discovery resource API. | ||
var API = resource.APINamespaceRDK.WithServiceType(SubtypeName) | ||
|
||
// ErrNilResponse is the error for when a nil response is returned from a discovery service. | ||
var ErrNilResponse = errors.New("discovery service returned a nil response") | ||
|
||
// Named is a helper for getting the named service's typed resource name. | ||
func Named(name string) resource.Name { | ||
return resource.NewName(API, name) | ||
} | ||
|
||
// FromRobot is a helper for getting the named discovery service from the given Robot. | ||
func FromRobot(r robot.Robot, name string) (Service, error) { | ||
return robot.ResourceFromRobot[Service](r, Named(name)) | ||
} | ||
|
||
// FromDependencies is a helper for getting the named discovery service from a collection of | ||
// dependencies. | ||
func FromDependencies(deps resource.Dependencies, name string) (Service, error) { | ||
return resource.FromDependencies[Service](deps, Named(name)) | ||
} | ||
|
||
// Service describes the functions that are available to the service. | ||
type Service interface { | ||
resource.Resource | ||
DiscoverResources(ctx context.Context, extra map[string]any) ([]resource.Config, error) | ||
} |
Oops, something went wrong.