-
Notifications
You must be signed in to change notification settings - Fork 126
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
refactoring for Delhi release from IOTech #74
Conversation
This looks largely okay to me. I suggest we approve, and consider the following issues for subsequent work:
Also, I didn't see an implementation of #23 here, and with respect to #20 is a literal string comparison what is required? I had assumed that the assertion string would be an expression of some sort. |
@iain-anderson , thanks for your review. Please see my feedback below:
|
Yes, good point. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just reviewed half of the work, I will try to review the rest in this week.
Very nice work in this huge PR!
internal/cache/profiles.go
Outdated
} | ||
|
||
type profileCache struct { | ||
dpMap map[string]models.DeviceProfile //in dpMap, key is Device name, and value is DeviceProfile instance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
key is Device Profile name, not Device name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it and modified.
internal/cache/profiles.go
Outdated
setResult := make(map[string][]models.ResourceOperation, len(profileResources)) | ||
for _, pr := range profileResources { | ||
getResult[pr.Name] = pr.Get | ||
setResult[pr.Name] = pr.Set |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't be checked if pr.Set or pr.Get is not defined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it and added checking
internal/cache/profiles.go
Outdated
} | ||
|
||
func (p *profileCache) Update(profile models.DeviceProfile) error { | ||
name, ok := p.nameMap[profile.Id.Hex()] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just call RemoveByName() and then Add()? I think this will simplify the code maintenance.
This can be applied to all other caches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds better. I am modifying it, but it should use RemoveById instead of RemoveByName because the name might be changed.
internal/cache/profiles.go
Outdated
if rosMap, ok = p.getOpMap[prfName]; !ok { | ||
return nil, fmt.Errorf("profiles: ResourceOperations: specified profile: %s not found", prfName) | ||
} | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't be safer to do } else if strings.ToLower(method) == "set" {
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's safer. Modified it.
if common.UseRegistry { | ||
if !checkServiceAvailableByConsul(common.CurrentConfig.Clients[serviceId].Name) { | ||
time.Sleep(10 * time.Second) | ||
checkServiceAvailable(serviceId) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of calling itself, why not use a for loop like this one?
for {
if common.UseRegistry {
if checkServiceAvailableByConsul(common.CurrentConfig.Clients[serviceId].Name) == true {
return
}
} else {
if checkServiceAvailableByPing(serviceId) == nil {
return
}
}
time.Sleep(10 * time.Second)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree, and modified
internal/common/consts.go
Outdated
APIScheduleEventRoute = APIPrefix + "/scheduleevent" | ||
APIEventRoute = APIPrefix + "/event" | ||
APILoggingRoute = APIPrefix + "/logs" | ||
APIPingRoute = APIPrefix + "/ping" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using those const, should be better to use the ones defined in github.com/edgexfoundry/edgex-go/pkg/clients/constants.go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it and modified
@jduranf , thanks for the review |
@jduranf |
I'm working my way through your commit. I'm traveling tomorrow, so should have plenty of time on the plane to finish my review. |
just pushed a scheduling mechanism in a separated commit to fix #79 |
async.go
Outdated
if common.CurrentConfig.Device.DataTransform { | ||
err := transformer.TransformReadResult(cv, do.Properties.Value) | ||
if err != nil { | ||
common.LogCli.Error(fmt.Sprintf("CommandValue (%s) transformed failed: %v", cv.String(), err)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the first two log messages use the function name as a prefix, but this one doesn't.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if the transform fails, why push?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks, modified the log message
by the way, I opened an issue about unifying the log message format, and will fix all of them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you are right.
I skip the transform failed reading now
async.go
Outdated
svc.lc.Error(msg) | ||
err := transformer.CheckAssertion(cv, do.Properties.Value.Assertion, &device) | ||
if err != nil { | ||
common.LogCli.Error(fmt.Sprintf("Assertion failed for Device Resource: %s, with value: %s", cv.String(), err)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the first two log messages use the function name as a prefix, but this one doesn't. also, if the assertion fails, why push?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you are right, modified as the previous conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, the rewrite looks really good (aside from the size of the PR)! Please review my comments and and make changes where appropriate.
I would also like to see a more informative commit message. Please review the "Top four things to do (or not do) on the following page:
https://wiki.edgexfoundry.org/display/FA/Committing+Code+Guidelines
If you need help with suggested wording of the commit message, please let me know. It also might be a good idea to include the "fixes issues #x" comments in the body of the commit message vs. just in the PR description.
this way causes a bug
the initial elements of the slice are empty and new element will be appended to the end of the slice. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a couple of additional problems noted which I think should be fine to track via issues. The only things I'd still really like to see fixed are:
-
the commit message (see below for a suggested wording)
-
requiring use of the startup package
-
service name in the config file
Let's see if we can work these out during tomorrow's WG meeting.
As for the commit message, here's a suggested wording:
Major re-factor of the SDK for initial release
This commit is a major re-factor of the SDK code prior to it's first
release. Major changes include:
- re-organization of packaging structure, private code moved under /internal
- new public functions for add/remove/update devices & profiles
- scheduling
- assertion, transform, and mapping support
You can leave the "fixed" line as is at below the above text (or just remove it), although in the future, you need to prefix each issue # with a keyword for it to be auto-closed (eg. Fixes #X, fixes #Y, and fixes #Z). Otherwise, you'll need to manually close the issues using the github UI.
async.go
Outdated
err := transformer.CheckAssertion(cv, do.Properties.Value.Assertion, &device) | ||
if err != nil { | ||
common.LoggingClient.Error(fmt.Sprintf("processAsyncResults - Assertion failed for Device Resource: %s, with value: %s, %v", cv.RO.Object, cv.String(), err)) | ||
continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per the device requirements, when an assertion fails, the result in the Reading should be set to the string "Assertion failed for device resource: , with value: ". We might want to also add the assertion string as used in the message logged by CheckAssertion itself.
I'm fine if we track this via a new issue...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, we still add the reading when assertion failed.
modified as your suggestion. The string in the reading is:
"Assertion failed for device resource, with value: %s and assertion: %s"
newCV, ok := transformer.MapCommandValue(cv) | ||
if ok { | ||
cv = newCV | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there's no mapping for a reading, is it really supposed to fail, or does the reading just pass thru "as is"? This was never really defined anywhere AFAIK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The whole code is as follows:
if len(cv.RO.Mappings) > 0 {
newCV, ok := transformer.MapCommandValue(cv)
if ok {
cv = newCV
} else {
common.LoggingClient.Error(fmt.Sprintf("processAsyncResults - Mapping failed for Device Resource Operation: %v, with value: %s, %v", cv.RO, cv.String(), err))
continue
}
}
if there is no mapping defined, the map transform won't happen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add an issue to track this? I'm still not sure what the behavior should be if there's no mapping found for a reading. I would think that the reading should just pass thru then "as is", but here we're logging a message and then continuing on to the next result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like I misunderstood your comment. I've removed "continue" for now, so it will be "as is" and modified the Log to Warning level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#89 is opened
async.go
Outdated
|
||
// push to Core Data | ||
event := &models.Event{Device: acv.DeviceName, Readings: readings} | ||
go common.SendEvent(event) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If SendEvent fails, you can't tell from the resulting log message that it was triggered from an async reading (vs. a command endpoint reading).
I'm fine if we track this with an issue...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SendEvent function logs the error message when it failed
func SendEvent(event *models.Event) {
_, err := EventClient.Add(event)
if err != nil {
LoggingClient.Error(fmt.Sprintf("Failed to push event for device %s: %s", event.Device, err))
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but as I mentioned, you can't tell from the log message whether the send failed due to an async result or from a call to the command endpoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I modified the code to send the event in the async.go to replace go common.SendEvent()
_, err := common.EventClient.Add(event)
if err != nil {
common.LoggingClient.Error(fmt.Sprintf("processAsyncResults - Failed to push event %v: %s", event, err))
}
valueFloat64 = v | ||
} | ||
|
||
valueFloat64 = math.Log(valueFloat64) / math.Log(b) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this is doing the inverse of a base transform for a read value. As this wasn't documented anywhere (i.e. the device requirements document), it would be nice to at least add comments inline explaining why this is so. Same applies for offset & scale.
If you want to track this by issue, that's fine w/me. We should also add this into the device requirements document and release a new version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added comment inline
we could discuss whether we should remove this in the WG meeting
math.Log only accepts float64
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated to use internal constant in profile cache
return value, err | ||
} | ||
ns := uint8(s) | ||
value = v * ns |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if the result is too large to fit into value? The device services requirements document states that similar to an assertion, the result string should be set to "Overflow failed for device resource: ". This applies to offsets too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
opened #88 for this
return dc | ||
} | ||
|
||
func Devices() DeviceCache { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accessing the caches this way means there's always a function call (unless the compiler can optimize this) whenever a cache is accessed vs. the old way of just using globals that were created via singletons. If you create an issue for my previous comment about sync.Once vars, you should include this in the same issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's a trade off when I made it as a separated package. I don't think it is an issue, but please feel free to open it.
pkg/startup/bootstrap.go
Outdated
|
||
//flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) // clean up existing flag defined by other code | ||
flag.BoolVar(&common.UseRegistry, "registry", false, "Indicates the service should use the registry.") | ||
flag.BoolVar(&common.UseRegistry, "r", false, "Indicates the service should use registry.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Directly setting an internal var like this means that it's not even possible to use device.NewService without using the boostrap package! Usage of registry should be a parameter to NewService like it was previously.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I missed it. Added it back now.
This commit is a major re-factor of the SDK code prior to it's first release. Major changes include: * re-organization of packaging structure, private code moved under /internal * new public functions for add/remove/update devices & profiles scheduling * assertion, transform, and mapping support fixed issue #67 #27 #25 #24 #22 #21 #20 #16 #12 #10 #9 #7 #79 Signed-off-by: Cloud Tsai <cloudxxx8@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approved
err := transformer.TransformReadResult(cv, do.Properties.Value) | ||
if err != nil { | ||
common.LoggingClient.Error(fmt.Sprintf("processAsyncResults - CommandValue (%s) transformed failed: %v", cv.String(), err)) | ||
continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want to do the same as assertions here (i.e. set the reading result to "Transformation failed..."). Probably worth a new issue...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#94 opened for this topic
} | ||
newDeviceCache(ds) | ||
|
||
dps := make([]models.DeviceProfile, len(ds)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think this is wrong... please an open an issue to track.
|
||
err = transformer.CheckAssertion(cv, do.Properties.Value.Assertion, device) | ||
if err != nil { | ||
common.LoggingClient.Error(fmt.Sprintf("Handler - execReadCmd: Assertion failed for device resource: %s, with value: %s", cv.String(), err)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note we need to treat assertion failures in the command endpoint in the same way we handle them in async.go. Please open an issue for this...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#92 opened for this issue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#93 opened for the init cache topic
@@ -4,7 +4,7 @@ | |||
// | |||
// SPDX-License-Identifier: Apache-2.0 | |||
|
|||
package device | |||
package models | |||
|
|||
import "github.com/edgexfoundry/edgex-go/pkg/models" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know it was my suggestion that maybe we put the models under pkg/models vs. just models, however now I'm beginning to think this was incorrect, as all the other public code isn't included underneath pkg. This is simple enough to change back, but maybe worth discussion via email vs. an issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#91 opened for this topic
fix #67 #27 #25 #24 #22 #21 #20 #16 #12 #10 #9 #7