Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RSDK-9178: add provisioning wrappers #4576

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions app/provisioning_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package app

import (
"context"

pb "go.viam.com/api/provisioning/v1"
"go.viam.com/utils/rpc"
)

// ProvisioningInfo holds provisioning info.
type ProvisioningInfo struct {
FragmentID string
Model string
Manufacturer string
}

func provisioningInfoFromProto(info *pb.ProvisioningInfo) *ProvisioningInfo {
return &ProvisioningInfo{
FragmentID: info.FragmentId,
Model: info.Model,
Manufacturer: info.Manufacturer,
}
}

// NetworkInfo holds network information.
type NetworkInfo struct {
Type string
SSID string
Security string
Signal int32
Connected bool
LastError string
}

func networkInfoFromProto(info *pb.NetworkInfo) *NetworkInfo {
return &NetworkInfo{
Type: info.Type,
SSID: info.Ssid,
Security: info.Security,
Signal: info.Signal,
Connected: info.Connected,
LastError: info.LastError,
}
}

// GetSmartMachineStatusResponse contains smart machine status information.
type GetSmartMachineStatusResponse struct {
ProvisioningInfo *ProvisioningInfo
HasSmartMachineCredentials bool
IsOnline bool
LastestConnectionAttempt *NetworkInfo
Errors []string
}

func getSmartMachineStatusResponseFromProto(resp *pb.GetSmartMachineStatusResponse) *GetSmartMachineStatusResponse {
return &GetSmartMachineStatusResponse{
ProvisioningInfo: provisioningInfoFromProto(resp.ProvisioningInfo),
HasSmartMachineCredentials: resp.HasSmartMachineCredentials,
IsOnline: resp.IsOnline,
LastestConnectionAttempt: networkInfoFromProto(resp.LatestConnectionAttempt),
Errors: resp.Errors,
}
}

// CloudConfig is the minimal config to create a /etc/viam.json, containing the smart machine's part ID and secret.
type CloudConfig struct {
ID string
Secret string
AppAddress string
}

func cloudConfigToProto(config *CloudConfig) *pb.CloudConfig {
return &pb.CloudConfig{
Id: config.ID,
Secret: config.Secret,
AppAddress: config.AppAddress,
}
}

// ProvisioningClient is a gRPC client for method calls to the Provisioning API.
type ProvisioningClient struct {
client pb.ProvisioningServiceClient
}

// NewProvisioningClient constructs a new ProvisioningClient using the connection passed in by the Viam client.
func NewProvisioningClient(conn rpc.ClientConn) *ProvisioningClient {
return &ProvisioningClient{client: pb.NewProvisioningServiceClient(conn)}
}

// GetSmartMachineStatus is for retrieving the status of the smart machine including networking.
func (c *ProvisioningClient) GetSmartMachineStatus(ctx context.Context) (*GetSmartMachineStatusResponse, error) {
resp, err := c.client.GetSmartMachineStatus(ctx, &pb.GetSmartMachineStatusRequest{})
if err != nil {
return nil, err
}
return getSmartMachineStatusResponseFromProto(resp), nil
}

// SetNetworkCredentials is to set the wifi credentials.
func (c *ProvisioningClient) SetNetworkCredentials(ctx context.Context, credentialsType, ssid, psk string) error {
_, err := c.client.SetNetworkCredentials(ctx, &pb.SetNetworkCredentialsRequest{
Type: credentialsType,
Ssid: ssid,
Psk: psk,
})
return err
}

// SetSmartMachineCredentials is to set the smart machine credentials.
func (c *ProvisioningClient) SetSmartMachineCredentials(ctx context.Context, cloud *CloudConfig) error {
_, err := c.client.SetSmartMachineCredentials(ctx, &pb.SetSmartMachineCredentialsRequest{
Cloud: cloudConfigToProto(cloud),
})
return err
}

// GetNetworkList is to retrieve the list of networks that are visible to the smart machine.
func (c *ProvisioningClient) GetNetworkList(ctx context.Context) ([]*NetworkInfo, error) {
resp, err := c.client.GetNetworkList(ctx, &pb.GetNetworkListRequest{})
if err != nil {
return nil, err
}
var networks []*NetworkInfo
for _, network := range resp.Networks {
networks = append(networks, networkInfoFromProto(network))
}
return networks, nil
}
134 changes: 134 additions & 0 deletions app/provisioning_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package app

import (
"context"
"testing"

pb "go.viam.com/api/provisioning/v1"
"go.viam.com/test"
"google.golang.org/grpc"

"go.viam.com/rdk/testutils/inject"
)

const (
hasSmartMachineCredentials = true
isOnline = true
errorString = "error"
fragmentID = "fragment_id"
model = "model"
manufacturer = "manufacturer"
networkType = "network_type"
ssid = "ssid"
security = "security"
signal int32 = 4
connected = true
credentialsType = "credentials_type"
psk = "psk"
secret = "secret"
)

var (
provisioningInfo = ProvisioningInfo{
FragmentID: fragmentID,
Model: model,
Manufacturer: manufacturer,
}
networkInfo = NetworkInfo{
Type: networkType,
SSID: ssid,
Security: security,
Signal: signal,
Connected: connected,
LastError: errorString,
}
pbNetworkInfo = pb.NetworkInfo{
Type: getSmartMachineStatusResponse.LastestConnectionAttempt.Type,
Ssid: getSmartMachineStatusResponse.LastestConnectionAttempt.SSID,
Security: getSmartMachineStatusResponse.LastestConnectionAttempt.Security,
Signal: getSmartMachineStatusResponse.LastestConnectionAttempt.Signal,
Connected: getSmartMachineStatusResponse.LastestConnectionAttempt.Connected,
LastError: getSmartMachineStatusResponse.LastestConnectionAttempt.LastError,
}
errorList = []string{errorString}
getSmartMachineStatusResponse = GetSmartMachineStatusResponse{
ProvisioningInfo: &provisioningInfo,
HasSmartMachineCredentials: hasSmartMachineCredentials,
IsOnline: isOnline,
LastestConnectionAttempt: &networkInfo,
Errors: errorList,
}
cloudConfig = CloudConfig{
ID: partID,
Secret: secret,
}
)

func createProvisioningGrpcClient() *inject.ProvisioningServiceClient {
return &inject.ProvisioningServiceClient{}
}

func TestProvisioningClient(t *testing.T) {
grpcClient := createProvisioningGrpcClient()
client := ProvisioningClient{client: grpcClient}

t.Run("GetSmartMachineStatus", func(t *testing.T) {
pbResponse := pb.GetSmartMachineStatusResponse{
ProvisioningInfo: &pb.ProvisioningInfo{
FragmentId: getSmartMachineStatusResponse.ProvisioningInfo.FragmentID,
Model: getSmartMachineStatusResponse.ProvisioningInfo.Model,
Manufacturer: getSmartMachineStatusResponse.ProvisioningInfo.Manufacturer,
},
HasSmartMachineCredentials: getSmartMachineStatusResponse.HasSmartMachineCredentials,
IsOnline: getSmartMachineStatusResponse.IsOnline,
LatestConnectionAttempt: &pbNetworkInfo,
Errors: getSmartMachineStatusResponse.Errors,
}
grpcClient.GetSmartMachineStatusFunc = func(
ctx context.Context, in *pb.GetSmartMachineStatusRequest, opts ...grpc.CallOption,
) (*pb.GetSmartMachineStatusResponse, error) {
return &pbResponse, nil
}
resp, err := client.GetSmartMachineStatus(context.Background())
test.That(t, err, test.ShouldBeNil)
test.That(t, resp, test.ShouldResemble, &getSmartMachineStatusResponse)
})

t.Run("SetNetworkCredentials", func(t *testing.T) {
grpcClient.SetNetworkCredentialsFunc = func(
ctx context.Context, in *pb.SetNetworkCredentialsRequest, opts ...grpc.CallOption,
) (*pb.SetNetworkCredentialsResponse, error) {
test.That(t, in.Type, test.ShouldEqual, credentialsType)
test.That(t, in.Ssid, test.ShouldEqual, ssid)
test.That(t, in.Psk, test.ShouldEqual, psk)
return &pb.SetNetworkCredentialsResponse{}, nil
}
err := client.SetNetworkCredentials(context.Background(), credentialsType, ssid, psk)
test.That(t, err, test.ShouldBeNil)
})

t.Run("SetSmartMachineCredentials", func(t *testing.T) {
grpcClient.SetSmartMachineCredentialsFunc = func(
ctx context.Context, in *pb.SetSmartMachineCredentialsRequest, opts ...grpc.CallOption,
) (*pb.SetSmartMachineCredentialsResponse, error) {
test.That(t, in.Cloud, test.ShouldResemble, cloudConfigToProto(&cloudConfig))
return &pb.SetSmartMachineCredentialsResponse{}, nil
}
err := client.SetSmartMachineCredentials(context.Background(), &cloudConfig)
test.That(t, err, test.ShouldBeNil)
})

t.Run("GetNetworkList", func(t *testing.T) {
expectedNetworks := []*NetworkInfo{&networkInfo}
grpcClient.GetNetworkListFunc = func(
ctx context.Context, in *pb.GetNetworkListRequest, opts ...grpc.CallOption,
) (*pb.GetNetworkListResponse, error) {
return &pb.GetNetworkListResponse{
Networks: []*pb.NetworkInfo{&pbNetworkInfo},
}, nil
}
resp, err := client.GetNetworkList(context.Background())
test.That(t, err, test.ShouldBeNil)
test.That(t, resp, test.ShouldResemble, expectedNetworks)
})
}
15 changes: 13 additions & 2 deletions app/viam_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import (

// ViamClient is a gRPC client for method calls to Viam app.
type ViamClient struct {
conn rpc.ClientConn
dataClient *DataClient
conn rpc.ClientConn
dataClient *DataClient
provisioningClient *ProvisioningClient
}

// Options has the options necessary to connect through gRPC.
Expand Down Expand Up @@ -73,6 +74,16 @@ func (c *ViamClient) DataClient() *DataClient {
return c.dataClient
}

// ProvisioningClient initializes and returns a ProvisioningClient instance used to make provisioning method calls.
// To use ProvisioningClient, you must first instantiate a ViamClient.
func (c *ViamClient) ProvisioningClient() *ProvisioningClient {
if c.provisioningClient != nil {
return c.provisioningClient
}
c.provisioningClient = NewProvisioningClient(c.conn)
return c.provisioningClient
}

// Close closes the gRPC connection.
func (c *ViamClient) Close() error {
return c.conn.Close()
Expand Down
19 changes: 15 additions & 4 deletions app/viam_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import (
"testing"

"github.com/viamrobotics/webrtc/v3"
pb "go.viam.com/api/app/data/v1"
datapb "go.viam.com/api/app/data/v1"
provisioningpb "go.viam.com/api/provisioning/v1"
"go.viam.com/test"
"go.viam.com/utils"
"go.viam.com/utils/rpc"
Expand Down Expand Up @@ -119,7 +120,7 @@ func TestCreateViamClientWithAPIKeyTests(t *testing.T) {
}
}

func TestNewDataClient(t *testing.T) {
func TestNewAppClients(t *testing.T) {
originalDialDirectGRPC := dialDirectGRPC
dialDirectGRPC = mockDialDirectGRPC
defer func() { dialDirectGRPC = originalDialDirectGRPC }()
Expand All @@ -138,10 +139,20 @@ func TestNewDataClient(t *testing.T) {
dataClient := client.DataClient()
test.That(t, dataClient, test.ShouldNotBeNil)
test.That(t, dataClient, test.ShouldHaveSameTypeAs, &DataClient{})
test.That(t, dataClient.client, test.ShouldImplement, (*pb.DataServiceClient)(nil))
test.That(t, dataClient.client, test.ShouldImplement, (*datapb.DataServiceClient)(nil))

// Testing that a second call to DataClient() returns the same instance
dataClient2 := client.DataClient()
test.That(t, dataClient2, test.ShouldNotBeNil)
test.That(t, dataClient, test.ShouldResemble, dataClient2)
test.That(t, dataClient, test.ShouldEqual, dataClient2)

provisioningClient := client.ProvisioningClient()
test.That(t, provisioningClient, test.ShouldNotBeNil)
test.That(t, provisioningClient, test.ShouldHaveSameTypeAs, &ProvisioningClient{})
test.That(t, provisioningClient.client, test.ShouldImplement, (*provisioningpb.ProvisioningServiceClient)(nil))

// Testing that a second call to ProvisioningClient() returns the same instance
provisioningClient2 := client.ProvisioningClient()
test.That(t, provisioningClient2, test.ShouldNotBeNil)
test.That(t, provisioningClient, test.ShouldEqual, provisioningClient2)
}
61 changes: 61 additions & 0 deletions testutils/inject/provisioning_service_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package inject

import (
"context"

provisioningPb "go.viam.com/api/provisioning/v1"
"google.golang.org/grpc"
)

// ProvisioningServiceClient represents a fake instance of a provisioning client.
type ProvisioningServiceClient struct {
provisioningPb.ProvisioningServiceClient
GetSmartMachineStatusFunc func(ctx context.Context, in *provisioningPb.GetSmartMachineStatusRequest,
opts ...grpc.CallOption) (*provisioningPb.GetSmartMachineStatusResponse, error)
SetNetworkCredentialsFunc func(ctx context.Context, in *provisioningPb.SetNetworkCredentialsRequest,
opts ...grpc.CallOption) (*provisioningPb.SetNetworkCredentialsResponse, error)
SetSmartMachineCredentialsFunc func(ctx context.Context, in *provisioningPb.SetSmartMachineCredentialsRequest,
opts ...grpc.CallOption) (*provisioningPb.SetSmartMachineCredentialsResponse, error)
GetNetworkListFunc func(ctx context.Context, in *provisioningPb.GetNetworkListRequest,
opts ...grpc.CallOption) (*provisioningPb.GetNetworkListResponse, error)
}

// GetSmartMachineStatus calls the injected GetSmartMachineStatusFunc or the real version.
func (psc *ProvisioningServiceClient) GetSmartMachineStatus(ctx context.Context, in *provisioningPb.GetSmartMachineStatusRequest,
opts ...grpc.CallOption,
) (*provisioningPb.GetSmartMachineStatusResponse, error) {
if psc.GetSmartMachineStatusFunc == nil {
return psc.ProvisioningServiceClient.GetSmartMachineStatus(ctx, in, opts...)
}
return psc.GetSmartMachineStatusFunc(ctx, in, opts...)
}

// SetNetworkCredentials calls the injected SetNetworkCredentialsFunc or the real version.
func (psc *ProvisioningServiceClient) SetNetworkCredentials(ctx context.Context, in *provisioningPb.SetNetworkCredentialsRequest,
opts ...grpc.CallOption,
) (*provisioningPb.SetNetworkCredentialsResponse, error) {
if psc.SetNetworkCredentialsFunc == nil {
return psc.ProvisioningServiceClient.SetNetworkCredentials(ctx, in, opts...)
}
return psc.SetNetworkCredentialsFunc(ctx, in, opts...)
}

// SetSmartMachineCredentials calls the injected SetSmartMachineCredentialsFunc or the real version.
func (psc *ProvisioningServiceClient) SetSmartMachineCredentials(ctx context.Context, in *provisioningPb.SetSmartMachineCredentialsRequest,
opts ...grpc.CallOption,
) (*provisioningPb.SetSmartMachineCredentialsResponse, error) {
if psc.SetSmartMachineCredentialsFunc == nil {
return psc.ProvisioningServiceClient.SetSmartMachineCredentials(ctx, in, opts...)
}
return psc.SetSmartMachineCredentialsFunc(ctx, in, opts...)
}

// GetNetworkList calls the injected GetNetworkListFunc or the real version.
func (psc *ProvisioningServiceClient) GetNetworkList(ctx context.Context, in *provisioningPb.GetNetworkListRequest,
opts ...grpc.CallOption,
) (*provisioningPb.GetNetworkListResponse, error) {
if psc.GetNetworkListFunc == nil {
return psc.ProvisioningServiceClient.GetNetworkList(ctx, in, opts...)
}
return psc.GetNetworkListFunc(ctx, in, opts...)
}
Loading