-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Refactor client fingerprinters to return a diff of node attributes #3781
Conversation
5802f45
to
60b3c3d
Compare
60b3c3d
to
5e8151d
Compare
// and returns if the fingerprint was applicable and a potential error. | ||
Fingerprint(*config.Config, *structs.Node) (bool, error) | ||
// and returns a diff of updated node attributes and a potential error. | ||
Fingerprint(*cstructs.FingerprintRequest, *cstructs.FingerprintResponse) error |
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 make this be Fingerprint(*structs.FingerPrintRequest) (structs.FingerprintResponse, error)
so it returns the response?
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, I think it could be either way. I'm curious what @dadgar thinks as we talked about this signature before.
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 suggested this so it will be easier to move to a remote RPC
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.
For when this moves to a plugin? Makes sense
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.
Yep
client/client.go
Outdated
if applies { | ||
|
||
// remove attributes we are supposed to skip | ||
for _, attr := range skipped { |
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 we don't care about thiese skipped attributes and are going to delete them from the response as soon as the fingerprint call is done, I would suggest pushing that logic one level down. You could have the FingerprintRequest also take a list of skipped(better name would be ignore) and have the implementors of FingerPrint
ignore or remove anything in skipped before it returns.
The current approach works, but it is not well encapsulated - its doing something, then mutating the response before using it.
} | ||
if apply { | ||
t.Fatalf("should not apply with config") | ||
// test with an empty config |
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.
consider turning these into subtests with a test case struct to capture inputs and expected output
client/client.go
Outdated
|
||
// if an attribute should be skipped, remove it from the list which we will | ||
// later apply to the node | ||
for _, e := range skipped { |
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.
These aren't attributes that are being skipped. These are whole fingerprinters so they won't have created any attribute that needs to be deleted.
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.
Ah, only visible after expanding to get context that skipped is constructed from fingerprinter names. The diff made it look like it was mutating the output based on the skipped list.
client/client.go
Outdated
delete(response.Attributes, e) | ||
} | ||
|
||
for name := range response.Attributes { |
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 applied list is applied fingerprinters not the attributes.
func (c *Client) updateNodeFromFingerprint(response *cstructs.FingerprintResponse) { | ||
c.configLock.Lock() | ||
defer c.configLock.Unlock() | ||
for name, val := range response.Attributes { |
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.
How are you handling removing Attributes/Links?
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.
As in fingerprinter_foo adds attribute = {"foo": "bar", "bam": "baz"} and then the next time it is called only returns {"foo": "bar"} because "bam" should be removed.
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.
Good point. One way we can handle this is if the attribute is equal to empty in the fingerprinter's response, we can delete this attribute from the node's attributes. That way we don't need to rely on the fingerprinter modifying the node directly.
client/client.go
Outdated
c.config.Node.Links[name] = val | ||
} | ||
|
||
c.config.Node.Resources.Merge(response.Resources) |
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 am fine with this strategy for now but this doesn't let a fingerprinter report a non-zero value and then set it to zero. The long term solution is that we pass in a different struct type that has all the fields as a pointer and as long as the fingerprinter sets the pointer we merge the value
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'll add that as a tech task to complete after this refactor.
client/client.go
Outdated
c.configLock.Unlock() | ||
|
||
request := &cstructs.FingerprintRequest{Config: c.config, Node: c.config.Node} | ||
response := &cstructs.FingerprintResponse{ |
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.
Would prefer if it looked like:
var resp cstructs.FingerprintResponse
if err := d.Fingerprint(req, &resp); err != nil {
// Handle
}
You can then have methods on the response to be like:
func (f *FingerprintResponse) AddAttribute(attr, value string) {...}
Where it initializes the value.
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.
Good idea
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.
Alternatively, we could have a constructor which would initialize all of these values. I will add getter/setter methods that does initialization for each field, let me know if you think it is cleaner to just do initialization all at once in a constructor.
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.
Yeah the constructor could work but it is just idiomatic go to call an RPC like I showed, which is the direction we will be moving.
1139ae0
to
a19ac99
Compare
5faef04
to
6633c64
Compare
write test for client periodic fingerprinters
6633c64
to
ae889b4
Compare
client/structs/structs.go
Outdated
} | ||
|
||
type FingerprintResponse struct { | ||
attributes map[string]string |
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.
These should be public.
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 getters and setters for these, to prevent against uninitialized fields. Alternatively, we could have a constructor for FingerprintResponse which would do initialization of values all at once.
client/structs/structs.go
Outdated
type FingerprintResponse struct { | ||
attributes map[string]string | ||
links map[string]string | ||
resources *structs.Resources |
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.
Should we have a Applicable bool
in the response so we can maintain our old behavior of displaying whether the fingerprinter is being applied.
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, we could do that, that would probably be cleaner than parsing attribute values after the fact.
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.
On second thought- would logging drivers where fingerprintSuccess
is true achieve the same goal?
client/client.go
Outdated
// Apply the finerprint to our list so that we can log it later | ||
appliedFingerprints = append(appliedFingerprints, name) | ||
|
||
request := &cstructs.FingerprintRequest{Config: c.config, Node: c.config.Node} |
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 don't believe you can get rid of the lock yet. This is a shared data structure that is being mutated/read concurrently. Apples to all places you are doing a fingerprint.
05a8c33
to
db37992
Compare
public fields and remove getter functions
db37992
to
a9447ad
Compare
client/client.go
Outdated
return err | ||
} | ||
if applies { | ||
avail = append(avail, name) | ||
c.configLock.Unlock() |
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.
Need to unlock on error. Should probably just capture the error, unlock and then check if it isn't nil
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.
Good catch
if response.Resources != nil { | ||
c.config.Node.Resources.Merge(response.Resources) | ||
} | ||
} |
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 should change the node watcher from checking periodically to check via a channel trigger and send it when this function is called: https://github.com/hashicorp/nomad/blob/master/client/client.go#L1588
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.
This will be in a follow up PR.
client/client_test.go
Outdated
|
||
c1 := testClient(t, func(c *config.Config) { | ||
c.Options = map[string]string{ | ||
"test.shutdown_periodic_after": "true", |
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 make this a constant on the mock driver.
"test.mock_driver.shutdown_periodic..."
} | ||
t.Logf("Found docker version %s", node.Attributes["driver.docker.version"]) | ||
|
||
t.Logf("Found docker version %s", attributes["driver.docker.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.
Shouldn't this be require.NotEmpty(attributes["driver.docker.version"])
} | ||
|
||
attributes := response.Attributes | ||
if attributes == nil { |
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.
Use require
client/driver/exec_linux.go
Outdated
// Only enable if cgroups are available and we are root | ||
if !cgroupsMounted(node) { | ||
if !cgroupsMounted(req.Node) { | ||
if d.fingerprintSuccess == nil || *d.fingerprintSuccess { | ||
d.logger.Printf("[DEBUG] driver.exec: cgroups unavailable, disabling") |
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 make this an INFO level log
if d.fingerprintSuccess == nil || *d.fingerprintSuccess { | ||
d.logger.Printf("[DEBUG] driver.exec: cgroups unavailable, disabling") | ||
} | ||
d.fingerprintSuccess = helper.BoolToPtr(false) | ||
delete(node.Attributes, execDriverAttr) | ||
return false, nil | ||
resp.RemoveAttribute(execDriverAttr) |
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 both these cases applicable should be true?
client/driver/java.go
Outdated
// Only enable if we are root and cgroups are mounted when running on linux systems. | ||
if runtime.GOOS == "linux" && (syscall.Geteuid() != 0 || !cgroupsMounted(node)) { | ||
if runtime.GOOS == "linux" && (syscall.Geteuid() != 0 || !cgroupsMounted(req.Node)) { | ||
if d.fingerprintSuccess == nil || *d.fingerprintSuccess { | ||
d.logger.Printf("[DEBUG] driver.java: root privileges and mounted cgroups required on linux, disabling") |
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.
INFO?
client/driver/mock_driver.go
Outdated
// if the shutdown configuration options are set, start the timer here. | ||
// This config option defaults to false | ||
if ctx.config != nil && ctx.config.ReadBoolDefault(fingerprint.ShutdownPeriodicAfter, false) { | ||
duration, err := ctx.config.ReadInt(fingerprint.ShutdownPeriodicDuration) |
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.
How about storing the duration and start time as a field on the mock driver and changing the Fingerprint method to be if time.Now().After(d.shutdownFingerprintTime) {
. Avoids the other function and goroutine
client/driver/java.go
Outdated
@@ -119,6 +119,7 @@ func (d *JavaDriver) Fingerprint(req *cstructs.FingerprintRequest, resp *cstruct | |||
} | |||
d.fingerprintSuccess = helper.BoolToPtr(false) | |||
resp.RemoveAttribute(javaDriverAttr) | |||
resp.Detected = true |
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 should probably move this block to below checking for the java binary.
client/client.go
Outdated
@@ -979,11 +990,18 @@ func (c *Client) fingerprintPeriodic(name string, f fingerprint.Fingerprint, d t | |||
for { | |||
select { | |||
case <-time.After(d): | |||
request := &cstructs.FingerprintRequest{Config: c.config, Node: c.config.Node} | |||
var response cstructs.FingerprintResponse |
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 lock should wrap creating the request since it is accessing the config. (not just here)
client/driver/mock_driver.go
Outdated
cstructs "github.com/hashicorp/nomad/client/structs" | ||
"github.com/hashicorp/nomad/nomad/structs" | ||
) | ||
|
||
const ( | ||
|
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.
Remove blank line
client/structs/structs.go
Outdated
@@ -184,3 +187,61 @@ func (d *DriverNetwork) Hash() []byte { | |||
} | |||
return h.Sum(nil) | |||
} | |||
|
|||
type FingerprintRequest struct { |
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.
Comments on all public structs
d9769e2
to
3202200
Compare
I'm going to lock this pull request because it has been closed for 120 days ⏳. This helps our maintainers find and focus on the active contributions. |
No description provided.