Skip to content

Commit

Permalink
feat(metadata): Implement GET /deviceprofile/model/{model} V2 API (ed…
Browse files Browse the repository at this point in the history
…gexfoundry#2914)

* feat(metadata): Implement GET /deviceprofile/model/{model} V2 API
Implement GET /deviceprofile/model/{model} V2 API according to the doc https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-metadata/2.x#/default/get_deviceprofile_model__model_
Fix edgexfoundry#2099

* refactor(data): Upgrade go-mod-core-contract version
Upgrade go-mod-core-contract version and modify API doc and testing for new changes.

* refactor(metadata): Use v2 instead of the contractsV2 alias name
Remove the duplicate import and use v2 instead of the contractsV2 alias name
Fix edgexfoundry#2915

Signed-off-by: weichou <weichou1229@gmail.com>
  • Loading branch information
weichou1229 authored and jim-wang-intel committed Dec 15, 2020
1 parent 8ffb7ff commit 464f00e
Show file tree
Hide file tree
Showing 13 changed files with 197 additions and 34 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/edgexfoundry/go-mod-bootstrap v0.0.60
github.com/edgexfoundry/go-mod-configuration v0.0.8
github.com/edgexfoundry/go-mod-core-contracts v0.1.118
github.com/edgexfoundry/go-mod-core-contracts v0.1.119
github.com/edgexfoundry/go-mod-messaging v0.1.28
github.com/edgexfoundry/go-mod-registry v0.1.26
github.com/edgexfoundry/go-mod-secrets v0.0.26
Expand Down
1 change: 1 addition & 0 deletions internal/core/data/v2/controller/http/const_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package http
const (
ExampleUUID = "82eb2e26-0f24-48aa-ae4c-de9dac3fb9bc"
TestDeviceName = "TestDevice"
TestProfileName = "TestProfileName"
TestPushedTime = 1600666231295
TestCreatedTime = 1600666214495
TestOriginTime = 1600666185705354000
Expand Down
5 changes: 0 additions & 5 deletions internal/core/data/v2/controller/http/event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,6 @@ func TestAddEvent(t *testing.T) {
noSimpleValue := validRequest
noSimpleValue.Event.Readings = []dtos.BaseReading{testReading}
noSimpleValue.Event.Readings[0].Value = ""
noSimpleFloatEnconding := validRequest
noSimpleFloatEnconding.Event.Readings = []dtos.BaseReading{testReading}
noSimpleFloatEnconding.Event.Readings[0].ValueType = dtos.ValueTypeFloat32
noSimpleFloatEnconding.Event.Readings[0].FloatEncoding = ""
noBinaryValue := validRequest
noBinaryValue.Event.Readings = []dtos.BaseReading{{
DeviceName: TestDeviceName,
Expand Down Expand Up @@ -196,7 +192,6 @@ func TestAddEvent(t *testing.T) {
{"Invalid - No Reading ValueType", []requests.AddEventRequest{noReadingValueType}, true, http.StatusBadRequest},
{"Invalid - Invalid Reading ValueType", []requests.AddEventRequest{invalidReadingInvalidValueType}, true, http.StatusBadRequest},
{"Invalid - No SimpleReading Value", []requests.AddEventRequest{noSimpleValue}, true, http.StatusBadRequest},
{"Invalid - No SimpleReading FloatEncoding", []requests.AddEventRequest{noSimpleFloatEnconding}, true, http.StatusBadRequest},
{"Invalid - No BinaryReading BinaryValue", []requests.AddEventRequest{noBinaryValue}, true, http.StatusBadRequest},
{"Invalid - No BinaryReading MediaType", []requests.AddEventRequest{noBinaryMediaType}, true, http.StatusBadRequest},
}
Expand Down
17 changes: 17 additions & 0 deletions internal/core/metadata/v2/application/deviceprofile.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,20 @@ func AllDeviceProfiles(offset int, limit int, labels []string, dic *di.Container
}
return deviceProfiles, nil
}

// DeviceProfilesByModel query the device profiles with offset, limit and model
func DeviceProfilesByModel(offset int, limit int, model string, dic *di.Container) (deviceProfiles []dtos.DeviceProfile, err errors.EdgeX) {
if model == "" {
return deviceProfiles, errors.NewCommonEdgeX(errors.KindContractInvalid, "model is empty", nil)
}
dbClient := v2MetadataContainer.DBClientFrom(dic.Get)
dps, err := dbClient.DeviceProfilesByModel(offset, limit, model)
if err != nil {
return deviceProfiles, errors.NewCommonEdgeXWrapper(err)
}
deviceProfiles = make([]dtos.DeviceProfile, len(dps))
for i, dp := range dps {
deviceProfiles[i] = dtos.FromDeviceProfileModelToDTO(dp)
}
return deviceProfiles, nil
}
46 changes: 42 additions & 4 deletions internal/core/metadata/v2/controller/http/deviceprofile.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"github.com/edgexfoundry/go-mod-bootstrap/di"
"github.com/edgexfoundry/go-mod-core-contracts/clients"
"github.com/edgexfoundry/go-mod-core-contracts/errors"
contractsV2 "github.com/edgexfoundry/go-mod-core-contracts/v2"
"github.com/edgexfoundry/go-mod-core-contracts/v2"
"github.com/edgexfoundry/go-mod-core-contracts/v2/dtos"
commonDTO "github.com/edgexfoundry/go-mod-core-contracts/v2/dtos/common"
requestDTO "github.com/edgexfoundry/go-mod-core-contracts/v2/dtos/requests"
Expand Down Expand Up @@ -238,7 +238,7 @@ func (dc *DeviceProfileController) DeviceProfileByName(w http.ResponseWriter, r

// URL parameters
vars := mux.Vars(r)
name := vars[contractsV2.Name]
name := vars[v2.Name]

var response interface{}
var statusCode int
Expand Down Expand Up @@ -267,7 +267,7 @@ func (dc *DeviceProfileController) DeleteDeviceProfileById(w http.ResponseWriter

// URL parameters
vars := mux.Vars(r)
id := vars[contractsV2.Id]
id := vars[v2.Id]

var response interface{}
var statusCode int
Expand Down Expand Up @@ -297,7 +297,7 @@ func (dc *DeviceProfileController) DeleteDeviceProfileByName(w http.ResponseWrit

// URL parameters
vars := mux.Vars(r)
name := vars[contractsV2.Name]
name := vars[v2.Name]

var response interface{}
var statusCode int
Expand Down Expand Up @@ -354,3 +354,41 @@ func (dc *DeviceProfileController) AllDeviceProfiles(w http.ResponseWriter, r *h
utils.WriteHttpHeader(w, ctx, statusCode)
pkg.Encode(response, w, lc)
}

func (dc *DeviceProfileController) DeviceProfilesByModel(w http.ResponseWriter, r *http.Request) {
lc := container.LoggingClientFrom(dc.dic.Get)
ctx := r.Context()
correlationId := correlation.FromContext(ctx)
config := metadataContainer.ConfigurationFrom(dc.dic.Get)

vars := mux.Vars(r)
model := vars[v2.Model]

var response interface{}
var statusCode int

// parse URL query string for offset, limit
offset, limit, _, err := utils.ParseGetAllObjectsRequestQueryString(r, 0, math.MaxInt32, -1, config.Service.MaxResultCount)
if err != nil {
lc.Error(err.Error(), clients.CorrelationHeader, correlationId)
lc.Debug(err.DebugMessages(), clients.CorrelationHeader, correlationId)
response = commonDTO.NewBaseResponse("", err.Message(), err.Code())
statusCode = err.Code()
} else {
deviceProfiles, err := application.DeviceProfilesByModel(offset, limit, model, dc.dic)
if err != nil {
if errors.Kind(err) != errors.KindEntityDoesNotExist {
lc.Error(err.Error(), clients.CorrelationHeader, correlationId)
}
lc.Debug(err.DebugMessages(), clients.CorrelationHeader, correlationId)
response = commonDTO.NewBaseResponse("", err.Message(), err.Code())
statusCode = err.Code()
} else {
response = responseDTO.NewMultiDeviceProfilesResponse("", "", http.StatusOK, deviceProfiles)
statusCode = http.StatusOK
}
}

utils.WriteHttpHeader(w, ctx, statusCode)
pkg.Encode(response, w, lc)
}
69 changes: 69 additions & 0 deletions internal/core/metadata/v2/controller/http/deviceprofile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -987,3 +987,72 @@ func TestAllDeviceProfiles(t *testing.T) {
})
}
}

func TestDeviceProfilesByModel(t *testing.T) {
deviceProfile := dtos.ToDeviceProfileModel(buildTestDeviceProfileRequest().Profile)
deviceProfiles := []models.DeviceProfile{deviceProfile, deviceProfile, deviceProfile}

dic := mockDic()
dbClientMock := &dbMock.DBClient{}
dbClientMock.On("DeviceProfilesByModel", 0, 10, TestModel).Return(deviceProfiles, nil)
dbClientMock.On("DeviceProfilesByModel", 1, 2, TestModel).Return([]models.DeviceProfile{deviceProfiles[1], deviceProfiles[2]}, nil)
dbClientMock.On("DeviceProfilesByModel", 4, 1, TestModel).Return([]models.DeviceProfile{}, errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, "query objects bounds out of range.", nil))
dic.Update(di.ServiceConstructorMap{
v2MetadataContainer.DBClientInterfaceName: func(get di.Get) interface{} {
return dbClientMock
},
})
controller := NewDeviceProfileController(dic)
assert.NotNil(t, controller)

tests := []struct {
name string
offset string
limit string
model string
errorExpected bool
expectedCount int
expectedStatusCode int
}{
{"Valid - get device profiles by model", "0", "10", TestModel, false, 3, http.StatusOK},
{"Valid - get device profiles by model with offset and limit", "1", "2", TestModel, false, 2, http.StatusOK},
{"Invalid - offset out of range", "4", "1", TestModel, true, 0, http.StatusNotFound},
{"Invalid - model is empty", "0", "10", "", true, 0, http.StatusBadRequest},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, contractsV2.ApiDeviceProfileByModelRoute, http.NoBody)
req = mux.SetURLVars(req, map[string]string{contractsV2.Model: testCase.model})
query := req.URL.Query()
query.Add(contractsV2.Offset, testCase.offset)
query.Add(contractsV2.Limit, testCase.limit)
req.URL.RawQuery = query.Encode()
require.NoError(t, err)

// Act
recorder := httptest.NewRecorder()
handler := http.HandlerFunc(controller.DeviceProfilesByModel)
handler.ServeHTTP(recorder, req)

// Assert
if testCase.errorExpected {
var res common.BaseResponse
err = json.Unmarshal(recorder.Body.Bytes(), &res)
require.NoError(t, err)
assert.Equal(t, contractsV2.ApiVersion, res.ApiVersion, "API Version not as expected")
assert.Equal(t, testCase.expectedStatusCode, recorder.Result().StatusCode, "HTTP status code not as expected")
assert.Equal(t, testCase.expectedStatusCode, int(res.StatusCode), "Response status code not as expected")
assert.NotEmpty(t, res.Message, "Response message doesn't contain the error message")
} else {
var res responseDTO.MultiDeviceProfilesResponse
err = json.Unmarshal(recorder.Body.Bytes(), &res)
require.NoError(t, err)
assert.Equal(t, contractsV2.ApiVersion, res.ApiVersion, "API Version not as expected")
assert.Equal(t, testCase.expectedStatusCode, recorder.Result().StatusCode, "HTTP status code not as expected")
assert.Equal(t, testCase.expectedStatusCode, int(res.StatusCode), "Response status code not as expected")
assert.Equal(t, testCase.expectedCount, len(res.Profiles), "Profile count not as expected")
assert.Empty(t, res.Message, "Message should be empty when it is successful")
}
})
}
}
1 change: 1 addition & 0 deletions internal/core/metadata/v2/infrastructure/interfaces/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type DBClient interface {
DeleteDeviceProfileByName(name string) errors.EdgeX
DeviceProfileNameExists(name string) (bool, errors.EdgeX)
AllDeviceProfiles(offset int, limit int, labels []string) ([]model.DeviceProfile, errors.EdgeX)
DeviceProfilesByModel(offset int, limit int, model string) ([]model.DeviceProfile, errors.EdgeX)

AddDeviceService(e model.DeviceService) (model.DeviceService, errors.EdgeX)
DeviceServiceById(id string) (model.DeviceService, errors.EdgeX)
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions internal/core/metadata/v2/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func LoadRestRoutes(r *mux.Router, dic *di.Container) {
r.HandleFunc(v2Constant.ApiDeviceProfileByIdRoute, dc.DeleteDeviceProfileById).Methods(http.MethodDelete)
r.HandleFunc(v2Constant.ApiDeviceProfileByNameRoute, dc.DeleteDeviceProfileByName).Methods(http.MethodDelete)
r.HandleFunc(v2Constant.ApiAllDeviceProfileRoute, dc.AllDeviceProfiles).Methods(http.MethodGet)
r.HandleFunc(v2Constant.ApiDeviceProfileByModelRoute, dc.DeviceProfilesByModel).Methods(http.MethodGet)

// Device Service
ds := metadataController.NewDeviceServiceController(dic)
Expand Down
12 changes: 12 additions & 0 deletions internal/pkg/v2/infrastructure/redis/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,18 @@ func (c *Client) AllDeviceProfiles(offset int, limit int, labels []string) ([]mo
return deviceProfiles, nil
}

// DeviceProfilesByModel query device profiles with offset, limit and model
func (c *Client) DeviceProfilesByModel(offset int, limit int, model string) ([]model.DeviceProfile, errors.EdgeX) {
conn := c.Pool.Get()
defer conn.Close()

deviceProfiles, edgeXerr := deviceProfilesByModel(conn, offset, limit, model)
if edgeXerr != nil {
return deviceProfiles, errors.NewCommonEdgeXWrapper(edgeXerr)
}
return deviceProfiles, nil
}

// EventTotalCount returns the total count of Event from the database
func (c *Client) EventTotalCount() (uint32, errors.EdgeX) {
conn := c.Pool.Get()
Expand Down
30 changes: 28 additions & 2 deletions internal/pkg/v2/infrastructure/redis/device_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import (
"github.com/gomodule/redigo/redis"
)

const DeviceProfileCollection = "v2:deviceProfile"
const (
DeviceProfileCollection = "v2:deviceProfile"
DeviceProfileCollectionModel = DeviceProfileCollection + ":" + v2.Model
)

// deviceProfileStoredKey return the device profile's stored key which combines the collection name and object id
func deviceProfileStoredKey(id string) string {
Expand Down Expand Up @@ -79,7 +82,7 @@ func addDeviceProfile(conn redis.Conn, dp models.DeviceProfile) (addedDeviceProf
_ = conn.Send(ZADD, DeviceProfileCollection, 0, storedKey)
_ = conn.Send(HSET, fmt.Sprintf("%s:%s", DeviceProfileCollection, v2.Name), dp.Name, storedKey)
_ = conn.Send(SADD, fmt.Sprintf("%s:%s:%s", DeviceProfileCollection, v2.Manufacturer, dp.Manufacturer), storedKey)
_ = conn.Send(SADD, fmt.Sprintf("%s:%s:%s", DeviceProfileCollection, v2.Model, dp.Model), storedKey)
_ = conn.Send(ZADD, fmt.Sprintf("%s:%s", DeviceProfileCollectionModel, dp.Model), dp.Modified, storedKey)
for _, label := range dp.Labels {
_ = conn.Send(ZADD, fmt.Sprintf("%s:%s:%s", DeviceProfileCollection, v2.Label, label), dp.Modified, storedKey)
}
Expand Down Expand Up @@ -209,3 +212,26 @@ func deviceProfilesByLabels(conn redis.Conn, offset int, limit int, labels []str
}
return deviceProfiles, nil
}

// deviceProfilesByModel query device profiles by offset, limit and model
func deviceProfilesByModel(conn redis.Conn, offset int, limit int, model string) (deviceProfiles []models.DeviceProfile, edgeXerr errors.EdgeX) {
end := offset + limit - 1
if limit == -1 { //-1 limit means that clients want to retrieve all remaining records after offset from DB, so specifying -1 for end
end = limit
}
objects, err := getObjectsByRevRange(conn, fmt.Sprintf("%s:%s", DeviceProfileCollectionModel, model), offset, end)
if err != nil {
return deviceProfiles, errors.NewCommonEdgeXWrapper(err)
}

deviceProfiles = make([]models.DeviceProfile, len(objects))
for i, in := range objects {
dp := models.DeviceProfile{}
err := json.Unmarshal(in, &dp)
if err != nil {
return deviceProfiles, errors.NewCommonEdgeX(errors.KindContractInvalid, "device profile parsing failed", err)
}
deviceProfiles[i] = dp
}
return deviceProfiles, nil
}
Loading

0 comments on commit 464f00e

Please sign in to comment.