Skip to content

Commit

Permalink
#204: Added structure so that a RegistryClient can use different algo…
Browse files Browse the repository at this point in the history
…rithms for retrieving a URL. (#211)

#206: Refactor usage of Context to make idiomatic.

#209: Remove Endpointer structure.

Signed-off-by: Brandon Forster <me@brandonforster.com>
  • Loading branch information
brandonforster authored Feb 10, 2020
1 parent f969157 commit b139196
Show file tree
Hide file tree
Showing 46 changed files with 955 additions and 1,765 deletions.
34 changes: 7 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,15 @@
# go-mod-core-contracts
This module contains the contract models used to describe data as it is passed via Request/Response between various EdgeX Foundry services. It also contains service clients for each service within the [edgex-go](https://github.com/edgexfoundry/edgex-go) repository. The definition of the various models and clients can be found in their respective top-level directories.
This module contains the contract models used to describe data as it is passed via Request/Response between various
EdgeX Foundry services. It also contains service clients for each service within the
[edgex-go](https://github.com/edgexfoundry/edgex-go) repository. The definition of the various models and clients can
be found in their respective top-level directories.

The default encoding for the models is JSON, although in at least one case -- [DeviceProfile](https://github.com/edgexfoundry/go-mod-core-contracts/blob/master/models/deviceprofile.go) -- YAML encoding is also supported since a device profile is defined as a YAML document.
The default encoding for the models is JSON, although in at least one case --
[DeviceProfile](https://github.com/edgexfoundry/go-mod-core-contracts/blob/master/models/deviceprofile.go) --
YAML encoding is also supported since a device profile is defined as a YAML document.

### Installation ###
* Make sure you're using at least Go 1.11.1 and have modules enabled, i.e. have an initialized go.mod file
* If your code is in your GOPATH then make sure ```GO111MODULE=on``` is set
* Run ```go get github.com/edgexfoundry/go-mod-core-contracts```
* This will add the go-mod-core-contracts to the go.mod file and download it into the module cache

### How to Use ###
In order to instantiate a service client, you would do the following:

Let's say you want to utilize a client for the core-metadata service, the first thing you want to do is initialize an instance of [types.EndpointParams](https://github.com/edgexfoundry/go-mod-core-contracts/blob/master/clients/types/endpoint_params.go). This simple struct provides all the properties you need to address a given service endpoint. Population of the relevant properties might look like this:

```
params := types.EndpointParams{
ServiceKey: internal.CoreMetaDataServiceKey,
Path: clients.ApiDeviceRoute,
UseRegistry: useRegistry,
Url: Configuration.Clients["Metadata"].Url() + clients.ApiDeviceRoute,
Interval: Configuration.Service.ClientMonitor,
}
```
From there you simply pass the EndpointParams into the initialization function for the client you wish to use. In the above case, we're trying to initialize a client for the Device endpoint of the core-metadata service.
```
mdc := metadata.NewDeviceClient(params, startup.Endpoint{RegistryClient: &registryClient})
```
_More information on the `RegistryClient` can be found [here](https://github.com/edgexfoundry/go-mod-registry). The `RegistryClient` is only used if the `useRegistry` flag provided to the `EndpointParams` is true._

Once you have a reference to the client, you simply need to call its methods like so:
`_, err := mdc.CheckForDevice(device, ctx)`

Each client has a `Monitor` goroutine in it. If the registry is being used, the Monitor's job is to refresh the protocol, host and port of your service client at some configured interval. The default interval is 15 seconds.
40 changes: 20 additions & 20 deletions clients/command/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,58 +22,58 @@ import (

"github.com/edgexfoundry/go-mod-core-contracts/clients"
"github.com/edgexfoundry/go-mod-core-contracts/clients/interfaces"
"github.com/edgexfoundry/go-mod-core-contracts/clients/types"
"github.com/edgexfoundry/go-mod-core-contracts/clients/urlclient"
)

// CommandClient interface defines interactions with the EdgeX Core Command microservice.
type CommandClient interface {
// Get issues a GET command targeting the specified device, using the specified command id
Get(deviceId string, commandId string, ctx context.Context) (string, error)
Get(ctx context.Context, deviceId string, commandId string) (string, error)
// Put issues a PUT command targeting the specified device, using the specified command id
Put(deviceId string, commandId string, body string, ctx context.Context) (string, error)
Put(ctx context.Context, deviceId string, commandId string, body string) (string, error)
// GetDeviceCommandByNames issues a GET command targeting the specified device, using the specified device and command name
GetDeviceCommandByNames(deviceName string, commandName string, ctx context.Context) (string, error)
GetDeviceCommandByNames(ctx context.Context, deviceName string, commandName string) (string, error)
// PutDeviceCommandByNames issues a PUT command targeting the specified device, using the specified device and command names
PutDeviceCommandByNames(deviceName string, commandName string, body string, ctx context.Context) (string, error)
PutDeviceCommandByNames(ctx context.Context, deviceName string, commandName string, body string) (string, error)
}

type commandRestClient struct {
urlClient interfaces.URLClient
}

// NewCommandClient creates an instance of CommandClient
func NewCommandClient(params types.EndpointParams, m interfaces.Endpointer) CommandClient {
return &commandRestClient{urlClient: urlclient.New(params, m)}
func NewCommandClient(urlClient interfaces.URLClient) CommandClient {
return &commandRestClient{
urlClient: urlClient,
}
}

func (cc *commandRestClient) Get(deviceId string, commandId string, ctx context.Context) (string, error) {
return cc.getRequestJSONBody("/"+deviceId+"/command/"+commandId, ctx)
func (cc *commandRestClient) Get(ctx context.Context, deviceId string, commandId string) (string, error) {
return cc.getRequestJSONBody(ctx, "/"+deviceId+"/command/"+commandId)
}

func (cc *commandRestClient) Put(deviceId string, commandId string, body string, ctx context.Context) (string, error) {
return clients.PutRequest("/"+deviceId+"/command/"+commandId, []byte(body), ctx, cc.urlClient)
func (cc *commandRestClient) Put(ctx context.Context, deviceId string, commandId string, body string) (string, error) {
return clients.PutRequest(ctx, "/"+deviceId+"/command/"+commandId, []byte(body), cc.urlClient)
}

func (cc *commandRestClient) GetDeviceCommandByNames(
ctx context.Context,
deviceName string,
commandName string,
ctx context.Context) (string, error) {
commandName string) (string, error) {

return cc.getRequestJSONBody("/name/"+deviceName+"/command/"+commandName, ctx)
return cc.getRequestJSONBody(ctx, "/name/"+deviceName+"/command/"+commandName)
}

func (cc *commandRestClient) PutDeviceCommandByNames(
ctx context.Context,
deviceName string,
commandName string,
body string,
ctx context.Context) (string, error) {
body string) (string, error) {

return clients.PutRequest("/name/"+deviceName+"/command/"+commandName, []byte(body), ctx, cc.urlClient)
return clients.PutRequest(ctx, "/name/"+deviceName+"/command/"+commandName, []byte(body), cc.urlClient)
}

func (cc *commandRestClient) getRequestJSONBody(urlSuffix string, ctx context.Context) (string, error) {
body, err := clients.GetRequest(urlSuffix, ctx, cc.urlClient)
func (cc *commandRestClient) getRequestJSONBody(ctx context.Context, urlSuffix string) (string, error) {
body, err := clients.GetRequest(ctx, urlSuffix, cc.urlClient)

return string(body), err
}
80 changes: 17 additions & 63 deletions clients/command/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,119 +21,73 @@ import (
"testing"

"github.com/edgexfoundry/go-mod-core-contracts/clients"
"github.com/edgexfoundry/go-mod-core-contracts/clients/types"
"github.com/edgexfoundry/go-mod-core-contracts/clients/interfaces"
"github.com/edgexfoundry/go-mod-core-contracts/clients/urlclient"
)

func TestGetDeviceCommandById(t *testing.T) {
ts := testHttpServer(t, http.MethodGet, clients.ApiDeviceRoute+"/device1/command/command1")
ts := testHTTPServer(t, http.MethodGet, clients.ApiDeviceRoute+"/device1/command/command1")

defer ts.Close()

url := ts.URL + clients.ApiDeviceRoute
cc := NewCommandClient(urlclient.NewLocalClient(interfaces.URLStream(ts.URL + clients.ApiDeviceRoute)))

params := types.EndpointParams{
ServiceKey: clients.CoreCommandServiceKey,
Path: clients.ApiDeviceRoute,
UseRegistry: false,
Url: url,
Interval: clients.ClientMonitorDefault,
}

cc := NewCommandClient(params, MockEndpoint{})

res, _ := cc.Get("device1", "command1", context.Background())
res, _ := cc.Get(context.Background(), "device1", "command1")

if res != "Ok" {
t.Errorf("expected response body \"Ok\", but received %s", res)
}
}

func TestPutDeviceCommandById(t *testing.T) {
ts := testHttpServer(t, http.MethodPut, clients.ApiDeviceRoute+"/device1/command/command1")
ts := testHTTPServer(t, http.MethodPut, clients.ApiDeviceRoute+"/device1/command/command1")

defer ts.Close()

url := ts.URL + clients.ApiDeviceRoute

params := types.EndpointParams{
ServiceKey: clients.CoreCommandServiceKey,
Path: clients.ApiDeviceRoute,
UseRegistry: false,
Url: url,
Interval: clients.ClientMonitorDefault,
}

cc := NewCommandClient(params, MockEndpoint{})
cc := NewCommandClient(urlclient.NewLocalClient(interfaces.URLStream(ts.URL + clients.ApiDeviceRoute)))

res, _ := cc.Put("device1", "command1", "body", context.Background())
res, _ := cc.Put(context.Background(), "device1", "command1", "body")

if res != "Ok" {
t.Errorf("expected response body \"Ok\", but received %s", res)
}
}

func TestGetDeviceByName(t *testing.T) {
ts := testHttpServer(t, http.MethodGet, clients.ApiDeviceRoute+"/name/device1/command/command1")
ts := testHTTPServer(t, http.MethodGet, clients.ApiDeviceRoute+"/name/device1/command/command1")

defer ts.Close()

url := ts.URL + clients.ApiDeviceRoute

params := types.EndpointParams{
ServiceKey: clients.CoreCommandServiceKey,
Path: clients.ApiDeviceRoute,
UseRegistry: false,
Url: url,
Interval: clients.ClientMonitorDefault,
}

cc := NewCommandClient(params, MockEndpoint{})
cc := NewCommandClient(urlclient.NewLocalClient(interfaces.URLStream(ts.URL + clients.ApiDeviceRoute)))

res, _ := cc.GetDeviceCommandByNames("device1", "command1", context.Background())
res, _ := cc.GetDeviceCommandByNames(context.Background(), "device1", "command1")

if res != "Ok" {
t.Errorf("expected response body \"Ok\", but received %s", res)
}
}

func TestPutDeviceCommandByNames(t *testing.T) {
ts := testHttpServer(t, http.MethodPut, clients.ApiDeviceRoute+"/name/device1/command/command1")
ts := testHTTPServer(t, http.MethodPut, clients.ApiDeviceRoute+"/name/device1/command/command1")

defer ts.Close()

url := ts.URL + clients.ApiDeviceRoute

params := types.EndpointParams{
ServiceKey: clients.CoreCommandServiceKey,
Path: clients.ApiDeviceRoute,
UseRegistry: false,
Url: url,
Interval: clients.ClientMonitorDefault,
}

cc := NewCommandClient(params, MockEndpoint{})
cc := NewCommandClient(urlclient.NewLocalClient(interfaces.URLStream(ts.URL + clients.ApiDeviceRoute)))

res, _ := cc.PutDeviceCommandByNames("device1", "command1", "body", context.Background())
res, _ := cc.PutDeviceCommandByNames(context.Background(), "device1", "command1", "body")

if res != "Ok" {
t.Errorf("expected response body \"Ok\", but received %s", res)
}
}

type MockEndpoint struct {
}

func (e MockEndpoint) Monitor(params types.EndpointParams) chan string {
return make(chan string, 1)
}

// testHttpServer instantiates a test HTTP Server to be used for conveniently verifying a client's invocation
func testHttpServer(t *testing.T, matchingRequestMethod string, matchingRequestUri string) *httptest.Server {
// testHTTPServer instantiates a test HTTP Server to be used for conveniently verifying a client's invocation
func testHTTPServer(t *testing.T, matchingRequestMethod string, matchingRequestUri string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)

if r.Method == matchingRequestMethod && r.RequestURI == matchingRequestUri {
w.Write([]byte("Ok"))
_, _ = w.Write([]byte("Ok"))
} else {
t.Errorf("expected endpoint %s to be invoked by client, %s invoked", matchingRequestUri, r.RequestURI)
}
Expand Down
2 changes: 1 addition & 1 deletion clients/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (

// FromContext allows for the retrieval of the specified key's value from the supplied Context.
// If the value is not found, an empty string is returned.
func FromContext(key string, ctx context.Context) string {
func FromContext(ctx context.Context, key string) string {
hdr, ok := ctx.Value(key).(string)
if !ok {
hdr = ""
Expand Down
Loading

0 comments on commit b139196

Please sign in to comment.