Skip to content

Commit

Permalink
Support module CRUD operations in device client
Browse files Browse the repository at this point in the history
This change adds all the CRUD module operations to the HTTP device
client.

Note that the `Module` model definitions are the same as for the
`iotservice` client and they have been reused instead of redefining them.
Ideally, this model definitions should be in the common package but that
would break backward compatibility.
  • Loading branch information
beandrad committed May 12, 2022
1 parent df8442d commit 84c60d0
Show file tree
Hide file tree
Showing 7 changed files with 495 additions and 52 deletions.
44 changes: 0 additions & 44 deletions common/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,47 +60,3 @@ type ConnectionAuthMethod struct {
Type string `json:"type"`
Issuer string `json:"issuer"`
}

// ModuleIdentity - an entity to create or update a module identity device-to-cloud
type ModuleIdentity struct {
// ModuleId - The unique identifier of the module.
ModuleId string `json:"moduleId"`

// DeviceId - The unique identifier of the device.
DeviceId string `json:"deviceId"`

// Authentication - The authentication mechanism used by the module when connecting to the service and edge hub.
Authentication struct {
Type string `json:"type"`
SymmetricKey struct {
PrimaryKey string `json:"primaryKey"`
SecondaryKey string `json:"secondaryKey"`
} `json:"symmetricKey"`
X509Thumbprint struct {
PrimaryThumbprint string `json:"primaryThumbprint"`
SecondaryThumbprint string `json:"secondaryThumbprint"`
} `json:"x509Thumbprint"`
} `json:"authentication"`

// ManagedBy - Identifies who manages this module. For instance, this value is "IotEdge" if the edge runtime owns this module.
// If not specified, will be null.
ManagedBy string `json:"managedBy"`

// LastActivityTime - The date and time the device last connected, received, or sent a message.
LastActivityTime string `json:"lastActivityTime"`

// CloudToDeviceMessageCount - The number of cloud-to-module messages currently queued to be sent to the module.
CloudToDeviceMessageCount int `json:"cloudToDeviceMessageCount"`

// ConnectionState - The connection state of the device.
ConnectionState string `json:"connectionState"`

// ConnectionStateUpdatedTime - The date and time the connection state was last updated
ConnectionStateUpdatedTime string `json:"connectionStateUpdatedTime"`

// Etag - The string representing a weak ETag for the module identity, as per RFC7232.
Etag string `json:"etag"`

// GenerationId - The IoT Hub generated, case-sensitive string up to 128 characters long. This value is used to distinguish modules with the same moduleId, when they have been deleted and re-created.
GenerationId string `json:"generationId"`
}
46 changes: 46 additions & 0 deletions iotdevice/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/amenzhinsky/iothub/common"
"github.com/amenzhinsky/iothub/iotdevice/transport"
"github.com/amenzhinsky/iothub/iotservice"
"github.com/amenzhinsky/iothub/logger"
)

Expand Down Expand Up @@ -390,3 +391,48 @@ func (c *Client) UploadFile(ctx context.Context, blobName string, file io.Reader

return err
}

// ListModules list all the registered modules on the device.
func (c *Client) ListModules(ctx context.Context) ([]*iotservice.Module, error) {
if err := c.checkConnection(ctx); err != nil {
return nil, err
}

return c.tr.ListModules(ctx)
}

// CreateModule Creates adds the given module to the registry.
func (c *Client) CreateModule(ctx context.Context, m *iotservice.Module) (*iotservice.Module, error) {
if err := c.checkConnection(ctx); err != nil {
return nil, err
}

return c.tr.CreateModule(ctx, m)
}

// GetModule retrieves the named module.
func (c *Client) GetModule(ctx context.Context, moduleID string) (*iotservice.Module, error) {
if err := c.checkConnection(ctx); err != nil {
return nil, err
}

return c.tr.GetModule(ctx, moduleID)
}

// UpdateModule updates the given module.
func (c *Client) UpdateModule(ctx context.Context, m *iotservice.Module) (*iotservice.Module, error) {
if err := c.checkConnection(ctx); err != nil {
return nil, err
}

return c.tr.UpdateModule(ctx, m)
}

// DeleteModule removes the named device module.
func (c *Client) DeleteModule(ctx context.Context, m *iotservice.Module) error {
if err := c.checkConnection(ctx); err != nil {
return err
}

return c.tr.DeleteModule(ctx, m)
}
129 changes: 129 additions & 0 deletions iotdevice/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package iotdevice

import (
"context"
"os"
"strconv"
"testing"
"time"

"github.com/amenzhinsky/iothub/iotdevice/transport/http"
"github.com/amenzhinsky/iothub/iotservice"
)

var testRunID = strconv.Itoa(int(time.Now().Unix()))

func newServiceClient(t *testing.T) *iotservice.Client {
t.Helper()
cs := os.Getenv("TEST_IOTHUB_SERVICE_CONNECTION_STRING")
if cs == "" {
t.Fatal("$TEST_IOTHUB_SERVICE_CONNECTION_STRING is empty")
}
c, err := iotservice.NewFromConnectionString(cs)
if err != nil {
t.Fatal(err)
}
return c
}

func newDevice(t *testing.T, c *iotservice.Client) *iotservice.Device {
t.Helper()

device := &iotservice.Device{
DeviceID: "test-device-" + testRunID,
}
device, err := c.CreateDevice(context.Background(), device)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
device.ETag = ""
if err := c.DeleteDevice(context.Background(), device); err != nil {
t.Fatal(err)
}
})
return device
}

func newDeviceClient(t *testing.T) *Client {
t.Helper()
sc := newServiceClient(t)
device := newDevice(t, sc)

dcs, err := sc.DeviceConnectionString(device, false)
if err != nil {
t.Fatal(err)
}

dc, err := NewFromConnectionString(http.New(), dcs)
if err != nil {
t.Fatal(err)
}

if err := dc.Connect(context.Background()); err != nil {
t.Fatal(err)
}

return dc
}

func newModule(t *testing.T, c *Client) *iotservice.Module {
module := &iotservice.Module{
DeviceID: c.DeviceID(),
ModuleID: "test-module-" + testRunID,
ManagedBy: "admin",
}
module, err := c.CreateModule(context.Background(), module)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
module.ETag = ""
if err := c.DeleteModule(context.Background(), module); err != nil {
t.Fatal(err)
}
})
return module
}

func TestListModules(t *testing.T) {
c := newDeviceClient(t)
module := newModule(t, c)
modules, err := c.ListModules(context.Background())
if err != nil {
t.Fatal(err)
}

if len(modules) != 1 {
t.Errorf("module count = %d, want 1", len(modules))
}

if modules[0].ModuleID != module.ModuleID {
t.Errorf("moduleID = %s, want %s", modules[0].ModuleID, module.ModuleID)
}
}

func TestGetModule(t *testing.T) {
c := newDeviceClient(t)
module := newModule(t, c)
if _, err := c.GetModule(
context.Background(), module.ModuleID,
); err != nil {
t.Fatal(err)
}
}

func TestUpdateModule(t *testing.T) {
c := newDeviceClient(t)
module := newModule(t, c)
module.Authentication.Type = iotservice.AuthSAS
updatedModule, err := c.UpdateModule(context.Background(), module)

if err != nil {
t.Fatal(err)
}

if updatedModule.Authentication.Type != iotservice.AuthSAS {
t.Errorf("authentication type = `%s`, want `%s`", updatedModule.Authentication.Type, iotservice.AuthSAS)
}
}
Loading

0 comments on commit 84c60d0

Please sign in to comment.