Skip to content
This repository has been archived by the owner on Jan 22, 2025. It is now read-only.

Commit

Permalink
Merge pull request #151 from presatish/use-command-client
Browse files Browse the repository at this point in the history
refactor: Use command client for as-controller-board-status to send commands instead of using REST calls.
  • Loading branch information
presatish authored Sep 27, 2023
2 parents 47f890b + ede1d2d commit d417a05
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 40 deletions.
3 changes: 2 additions & 1 deletion as-controller-board-status/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ type ControllerBoardStatusConfig struct {
DeviceName string
MaxTemperatureThreshold float64
MinTemperatureThreshold float64
DoorStatusCommandEndpoint string
InferenceDeviceName string
InferenceDoorStatusCmd string
NotificationCategory string
NotificationEmailAddresses string
NotificationLabels string
Expand Down
4 changes: 2 additions & 2 deletions as-controller-board-status/functions/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ const (
DeviceName = "DeviceName"
MaxTemperatureThreshold = "MaxTemperatureThreshold"
MinTemperatureThreshold = "MinTemperatureThreshold"
DoorStatusCommandEndpoint = "DoorStatusCommandEndpoint"
NotificationCategory = "NotificationCategory"
NotificationEmailAddresses = "NotificationEmailAddresses"
NotificationLabels = "NotificationLabels"
Expand All @@ -43,7 +42,8 @@ func GetCommonSuccessConfig() *config.ControllerBoardStatusConfig {
DeviceName: "controller-board",
MaxTemperatureThreshold: 83.0,
MinTemperatureThreshold: 10.0,
DoorStatusCommandEndpoint: "http://localhost:48082/api/v3/device/name/Inference-device/vendingDoorStatus",
InferenceDeviceName: "Inference-device",
InferenceDoorStatusCmd: "inferenceDoorStatus",
NotificationCategory: "HW_HEALTH",
NotificationEmailAddresses: "[email protected],[email protected]",
NotificationLabels: "HW_HEALTH",
Expand Down
2 changes: 1 addition & 1 deletion as-controller-board-status/functions/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ type ControllerBoardStatusAppSettings struct {
DeviceName string
MaxTemperatureThreshold float64
MinTemperatureThreshold float64
DoorStatusCommandEndpoint string
NotificationCategory string
NotificationEmailAddresses []string
NotificationLabels []string
Expand Down Expand Up @@ -70,6 +69,7 @@ type CheckBoardStatus struct {
Configuration *config.ControllerBoardStatusConfig
SubscriptionClient interfaces.SubscriptionClient
NotificationClient interfaces.NotificationClient
CommandClient interfaces.CommandClient
ControllerBoardStatus *ControllerBoardStatus
averageTemperatureMeasurement time.Duration
notificationSubscriptionRESTRetryInterval time.Duration
Expand Down
52 changes: 44 additions & 8 deletions as-controller-board-status/functions/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
package functions

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"sort"
Expand Down Expand Up @@ -227,8 +229,8 @@ func AvgTemp(measurements []TempMeasurement, duration time.Duration) (float64, i
}

// processVendingDoorState checks to see if the vending door state has changed
// and if it has changed, it will then submit EdgeX commands (REST calls)
// to the MQTT device service and the central vending state endpoint.
// and if it has changed, it will then submit the new state to the central vending state endpoint
// and to EdgeX via command client.
func (boardStatus *CheckBoardStatus) processVendingDoorState(lc logger.LoggingClient, doorClosed bool) error {
if boardStatus.DoorClosed != doorClosed {
// Set the boardStatus's DoorClosed value to the new value
Expand All @@ -246,15 +248,49 @@ func (boardStatus *CheckBoardStatus) processVendingDoorState(lc logger.LoggingCl
return fmt.Errorf("failed to submit the controller board's status to the central vending state service: %v", err.Error())
}

// Prepare a message to be sent to the MQTT bus. Depending on the state
// of the door, this message may trigger a CV inference
err = boardStatus.RESTCommandJSON(boardStatus.Configuration.DoorStatusCommandEndpoint, http.MethodPut, VendingDoorStatus{
VendingDoorStatus: strconv.FormatBool(doorClosed),
})
// Prepare and send EdgeX command. Depending on the state of the door, this message may trigger a CV inference
settings := make(map[string]string)
settings["VendingDoorStatus"] = strconv.FormatBool(doorClosed)
err = boardStatus.SendCommand(lc, http.MethodPut, boardStatus.Configuration.InferenceDeviceName, boardStatus.Configuration.InferenceDoorStatusCmd,
settings)
if err != nil {
return fmt.Errorf("failed to submit the vending door state to the MQTT device service: %v", err.Error())
return fmt.Errorf("failed to submit the vending door state to the command client: %v", err.Error())
}
}

return nil
}

// SendCommand issues CommandClient GET and SET command calls, CommandClient takes care of http calls,
// here the requirement are actionName, deviceName, commandName and settings, logger client is needed for logging
func (boardStatus *CheckBoardStatus) SendCommand(lc logger.LoggingClient, actionName string, deviceName string,
commandName string, settings map[string]string) error {
lc.Debug("Sending Command")

switch actionName {
case http.MethodPut:
lc.Debugf("executing %s action", actionName)
lc.Debugf("Issuing SET command '%s' for device '%s'", commandName, deviceName)

response, err := boardStatus.CommandClient.IssueSetCommandByName(context.Background(), deviceName, commandName, settings)
if err != nil {
return fmt.Errorf("failed to issue '%s' set command to '%s' device: %s", commandName, deviceName, err.Error())
}

lc.Debugf("response status: %d", response.StatusCode)

case http.MethodGet:
lc.Debugf("executing %s action", actionName)
lc.Debugf("Issuing GET command '%s' for device '%s'", commandName, deviceName)
response, err := boardStatus.CommandClient.IssueGetCommandByName(context.Background(), deviceName, commandName, false, true)
if err != nil {
return fmt.Errorf("failed to issue '%s' get command to '%s' device: %s", commandName, deviceName, err.Error())
}
lc.Debugf("response status: %d", response.StatusCode)

default:
return errors.New("Invalid action requested: " + actionName)
}

return nil
}
51 changes: 30 additions & 21 deletions as-controller-board-status/functions/output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package functions
import (
"as-controller-board-status/config"
"fmt"
"github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/common"
"net/http"
"net/http/httptest"
"testing"
Expand All @@ -29,7 +30,8 @@ func getCommonApplicationSettingsTyped() *config.ControllerBoardStatusConfig {
DeviceName: "controller-board",
MaxTemperatureThreshold: temp51,
MinTemperatureThreshold: temp49,
DoorStatusCommandEndpoint: "http://localhost:48082/api/v3/device/name/Inference-device/vendingDoorStatus",
InferenceDeviceName: "Inference-device",
InferenceDoorStatusCmd: "inferenceDoorStatus",
NotificationCategory: "HW_HEALTH",
NotificationEmailAddresses: "[email protected],[email protected]",
NotificationLabels: "HW_HEALTH",
Expand Down Expand Up @@ -119,11 +121,22 @@ func prepCheckControllerBoardStatusTest() (testTable []testTableCheckControllerB
mockNotificationClient := &client_mocks.NotificationClient{}
mockNotificationClient.On("SendNotification", mock.Anything, mock.Anything).Return(nil, nil)

resp := common.BaseResponse{
StatusCode: http.StatusOK,
}
mockCommandClient := &client_mocks.CommandClient{}
mockCommandClient.On("IssueSetCommandByName", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(resp, nil)

resp = common.BaseResponse{
StatusCode: http.StatusInternalServerError,
}
mockErrCommandClient := &client_mocks.CommandClient{}
mockErrCommandClient.On("IssueSetCommandByName", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(resp, nil)

// The success condition is ideal, and is configured to use URL's that all
// respond with responses that correspond to successful scenarios.
edgexcontextSuccess := pkg.NewAppFuncContextForTest(correlationID, lc)
configSuccess := getCommonApplicationSettingsTyped()
configSuccess.DoorStatusCommandEndpoint = testServerStatusOK.URL
configSuccess.VendingEndpoint = testServerStatusOK.URL
configSuccess.MinTemperatureThreshold = temp51
configSuccess.MaxTemperatureThreshold = temp49
Expand All @@ -133,15 +146,13 @@ func prepCheckControllerBoardStatusTest() (testTable []testTableCheckControllerB
// board state that has more measurements than the cutoff
edgexcontextSuccessMinThresholdExceeded := pkg.NewAppFuncContextForTest(correlationID, lc)
configSuccessMinThresholdExceeded := getCommonApplicationSettingsTyped()
configSuccessMinThresholdExceeded.DoorStatusCommandEndpoint = testServerStatusOK.URL
configSuccessMinThresholdExceeded.VendingEndpoint = testServerStatusOK.URL
configSuccessMinThresholdExceeded.MinTemperatureThreshold = temp51

// Create the condition of exceeding the maximum temperature threshold,
// and make the VendingEndpoint throw an error.
edgexcontextBadVendingEndpointMaxThresholdExceeded := pkg.NewAppFuncContextForTest(correlationID, lc)
configBadVendingEndpointMaxThresholdExceeded := getCommonApplicationSettingsTyped()
configBadVendingEndpointMaxThresholdExceeded.DoorStatusCommandEndpoint = testServerStatusOK.URL
configBadVendingEndpointMaxThresholdExceeded.VendingEndpoint = testServerThrowError.URL
configBadVendingEndpointMaxThresholdExceeded.MaxTemperatureThreshold = temp49

Expand All @@ -150,7 +161,6 @@ func prepCheckControllerBoardStatusTest() (testTable []testTableCheckControllerB
// we want. We want Accepted, but we're going to get 500
edgexcontextUnacceptingNotificationHostMaxThresholdExceeded := pkg.NewAppFuncContextForTest(correlationID, lc)
configUnacceptingNotificationHostMaxThresholdExceeded := getCommonApplicationSettingsTyped()
configUnacceptingNotificationHostMaxThresholdExceeded.DoorStatusCommandEndpoint = testServerStatusOK.URL
configUnacceptingNotificationHostMaxThresholdExceeded.VendingEndpoint = testServerStatusOK.URL
configUnacceptingNotificationHostMaxThresholdExceeded.MaxTemperatureThreshold = temp49

Expand All @@ -163,20 +173,16 @@ func prepCheckControllerBoardStatusTest() (testTable []testTableCheckControllerB
configBadNotificationHostThresholdsExceeded.VendingEndpoint = testServerStatusOK.URL
configBadNotificationHostThresholdsExceeded.MaxTemperatureThreshold = temp49

// Set bad MQTT and Vending endpoints to produce specific error conditions
// in processTemperature, which first sends a request to MQTT, then
// another request to the vending endpoint
edgexcontextBadDoorStatusCommandEndpoint := pkg.NewAppFuncContextForTest(correlationID, lc)
configBadDoorStatusCommandEndpoint := getCommonApplicationSettingsTyped()
configBadDoorStatusCommandEndpoint.DoorStatusCommandEndpoint = testServerThrowError.URL
configBadDoorStatusCommandEndpoint.VendingEndpoint = testServerStatusOK.URL

// As described above, in order to produce the error condition for
// processTemperature failing to hit the VendingEndpoint, we have to hit
// the DoorStatusCommandEndpoint successfully first
// Setup for Device Command (mockErrCommandClient used in one of the below testcases) to throw error to produce specific error conditions
// in processTemperature, which sends a request to the vending endpoint
edgexcontextBadDoorStatusCommand := pkg.NewAppFuncContextForTest(correlationID, lc)
configBadDoorStatusCommand := getCommonApplicationSettingsTyped()
configBadDoorStatusCommand.VendingEndpoint = testServerStatusOK.URL

// Set the Vending endpoint to throw error for
// processTemperature failing to hit the VendingEndpoint
edgexcontextBadVendingEndpoint := pkg.NewAppFuncContextForTest(correlationID, lc)
configBadVendingEndpoint := getCommonApplicationSettingsTyped()
configBadVendingEndpoint.DoorStatusCommandEndpoint = testServerStatusOK.URL
configBadVendingEndpoint.VendingEndpoint = testServerThrowError.URL

// The expected incoming event reading from the controller board device
Expand Down Expand Up @@ -230,6 +236,7 @@ func prepCheckControllerBoardStatusTest() (testTable []testTableCheckControllerB
LastNotified: time.Now().Add(time.Minute * -3),
Configuration: configSuccess,
NotificationClient: mockNotificationClient,
CommandClient: mockCommandClient,
},
OutputBool: true,
OutputInterface: controllerBoardStatusEventSuccess,
Expand Down Expand Up @@ -321,6 +328,7 @@ func prepCheckControllerBoardStatusTest() (testTable []testTableCheckControllerB
LastNotified: time.Now().Add(time.Minute * -3),
Configuration: configBadNotificationHostThresholdsExceeded,
NotificationClient: mockNotificationClient,
CommandClient: mockCommandClient,
},
OutputBool: true,
OutputInterface: controllerBoardStatusEventSuccess,
Expand All @@ -330,17 +338,18 @@ func prepCheckControllerBoardStatusTest() (testTable []testTableCheckControllerB
ExpectedTemperatureMeasurementSliceLength: 1,
},
{
TestCaseName: "Unsuccessful due to DoorStatusCommandEndpoint not responding with HTTP 200 OK, no temperature notification sent",
InputEdgexContext: edgexcontextBadDoorStatusCommandEndpoint,
TestCaseName: "Unsuccessful due to Device Command not responding with HTTP 200 OK, no temperature notification sent",
InputEdgexContext: edgexcontextBadDoorStatusCommand,
InputData: controllerBoardStatusEventSuccess,
InputCheckBoardStatus: CheckBoardStatus{
LastNotified: time.Now().Add(time.Minute * -3),
Configuration: configBadDoorStatusCommandEndpoint,
Configuration: configBadDoorStatusCommand,
NotificationClient: mockNotificationClient,
CommandClient: mockErrCommandClient,
},
OutputBool: true,
OutputInterface: controllerBoardStatusEventSuccess,
OutputLogs: fmt.Sprintf("Encountered error while checking the open/closed state of the door: failed to submit the vending door state to the MQTT device service: Failed to submit REST PUT request due to error: %v \\\"%v\\\": %v", "Put", configBadDoorStatusCommandEndpoint.DoorStatusCommandEndpoint, "EOF"),
OutputLogs: fmt.Sprintf("Encountered error while checking the open/closed state of the door: failed to submit the vending door state to the device command: Failed to submit REST PUT request due to error: %v : %v", "Put", "EOF"),
ShouldLastNotifiedBeDifferent: false,
ExpectedTemperatureMeasurementSliceLength: 1,
},
Expand Down
6 changes: 6 additions & 0 deletions as-controller-board-status/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ func (app *boardStatusAppService) CreateAndRunAppService(serviceKey string, newS
app.boardStatus.MaxTemperatureThreshold = app.boardStatus.Configuration.MaxTemperatureThreshold
app.boardStatus.MinTemperatureThreshold = app.boardStatus.Configuration.MinTemperatureThreshold

app.boardStatus.CommandClient = app.service.CommandClient()
if app.boardStatus.CommandClient == nil {
app.lc.Error("error command service missing from client's configuration")
return 1
}

// Create the function pipeline to run when an event is read on the device channels
err = app.service.SetDefaultFunctionsPipeline(
transforms.NewFilterFor([]string{app.boardStatus.Configuration.DeviceName}).FilterByDeviceName,
Expand Down
8 changes: 7 additions & 1 deletion as-controller-board-status/res/configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ Clients:
Host: localhost
Port: 59860

core-command:
Protocol: "http"
Host: "localhost"
Port: 59882

MessageBus:
Optional:
ClientId: as-controller-board-status
Expand All @@ -24,7 +29,8 @@ ControllerBoardStatus:
DeviceName: controller-board
MaxTemperatureThreshold: 83.0
MinTemperatureThreshold: 10.0
DoorStatusCommandEndpoint: http://localhost:59882/api/v3/device/name/Inference-device/inferenceDoorStatus
InferenceDeviceName: "Inference-device"
InferenceDoorStatusCmd: "inferenceDoorStatus"
NotificationCategory: HW_HEALTH
NotificationEmailAddresses: [email protected]
NotificationLabels: HW_HEALTH
Expand Down
12 changes: 6 additions & 6 deletions as-vending/functions/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,24 +440,24 @@ func (vendingState *VendingState) SendCommand(lc logger.LoggingClient, actionNam

switch actionName {
case http.MethodPut:
lc.Infof("executing %s action", actionName)
lc.Infof("Issuing SET command '%s' for device '%s'", commandName, deviceName)
lc.Debugf("executing %s action", actionName)
lc.Debugf("Issuing SET command '%s' for device '%s'", commandName, deviceName)

response, err := vendingState.CommandClient.IssueSetCommandByName(context.Background(), deviceName, commandName, settings)
if err != nil {
return fmt.Errorf("failed to issue '%s' set command to '%s' device: %s", commandName, deviceName, err.Error())
}

lc.Infof("response status: %d", response.StatusCode)
lc.Debugf("response status: %d", response.StatusCode)

case http.MethodGet:
lc.Infof("executing %s action", actionName)
lc.Infof("Issuing GET command '%s' for device '%s'", commandName, deviceName)
lc.Debugf("executing %s action", actionName)
lc.Debugf("Issuing GET command '%s' for device '%s'", commandName, deviceName)
response, err := vendingState.CommandClient.IssueGetCommandByName(context.Background(), deviceName, commandName, false, true)
if err != nil {
return fmt.Errorf("failed to issue '%s' get command to '%s' device: %s", commandName, deviceName, err.Error())
}
lc.Infof("response status: %d", response.StatusCode)
lc.Debugf("response status: %d", response.StatusCode)

default:
return errors.New("Invalid action requested: " + actionName)
Expand Down

0 comments on commit d417a05

Please sign in to comment.