Conversation
managed/utils/duration/duration.go
Outdated
| // FromProto converts a protobuf Duration to a time.Duration. | ||
| func FromProto(d *durationpb.Duration) time.Duration { | ||
| if d == nil { | ||
| return 0 | ||
| } | ||
|
|
||
| return d.AsDuration() |
There was a problem hiding this comment.
FromProto returns d.AsDuration() without validating the protobuf Duration or guarding against time.Duration overflow. A user can provide a very large (but protobuf-valid) duration that overflows time.Duration and becomes a negative/small value, leading to incorrect exporter timeouts. Consider validating (CheckValid) and adding an explicit upper bound / overflow-safe conversion (reject or clamp values that can’t fit into time.Duration).
There was a problem hiding this comment.
@copilot d.CheckValid() is already done in pb files.
| if params.ExporterOptions.ConnectionTimeout != nil { | ||
| if pointer.GetDuration(params.ExporterOptions.ConnectionTimeout) == 0 { | ||
| row.ExporterOptions.ConnectionTimeout = nil | ||
| } else { | ||
| row.ExporterOptions.ConnectionTimeout = params.ExporterOptions.ConnectionTimeout | ||
| } | ||
| } |
There was a problem hiding this comment.
| if params.ExporterOptions.ConnectionTimeout != nil { | |
| if pointer.GetDuration(params.ExporterOptions.ConnectionTimeout) == 0 { | |
| row.ExporterOptions.ConnectionTimeout = nil | |
| } else { | |
| row.ExporterOptions.ConnectionTimeout = params.ExporterOptions.ConnectionTimeout | |
| } | |
| } | |
| if params.ExporterOptions.ConnectionTimeout != nil { | |
| row.ExporterOptions.ConnectionTimeout = params.ExporterOptions.ConnectionTimeout | |
| } |
| } | ||
|
|
||
| // EffectiveDialTimeout returns the timeout configured for this agent's exporter. | ||
| // Only for exporters that have connection timeout support in DSN/config generation. |
There was a problem hiding this comment.
looks confusing- for exporters that don't support timeout in DSN/config it returns default constant that may be treated as supported timeout
| } | ||
| env := []string{ | ||
| fmt.Sprintf("MONGODB_URI=%s", exporter.DSN(service, models.DSNParams{DialTimeout: time.Second, Database: database}, tdp, pmmAgentVersion)), | ||
| fmt.Sprintf("MONGODB_URI=%s", exporter.DSN(service, models.DSNParams{DialTimeout: exporter.EffectiveDialTimeout(), Database: database}, tdp, pmmAgentVersion)), |
There was a problem hiding this comment.
what about the rest of Mongo Agents (QAN, RTA)? They support timeout in DSN as well
There was a problem hiding this comment.
Yes, other non exporters agents support timeouts too, but scope in ticket is about exporters only.
| case exporter.AzureOptions.ClientID != "": | ||
| if pointer.GetDuration(exporter.ExporterOptions.ConnectionTimeout) != 0 { | ||
| roundedConnectionTimeout = *exporter.ExporterOptions.ConnectionTimeout | ||
| } else { | ||
| roundedConnectionTimeout = postgresRemoteCloudDefaultDialTimeout | ||
| } | ||
| case node.NodeType == models.RemoteRDSNodeType: | ||
| if pointer.GetDuration(exporter.ExporterOptions.ConnectionTimeout) != 0 { | ||
| roundedConnectionTimeout = *exporter.ExporterOptions.ConnectionTimeout | ||
| } else { | ||
| roundedConnectionTimeout = postgresRemoteCloudDefaultDialTimeout | ||
| } | ||
| default: | ||
| roundedConnectionTimeout = exporter.EffectiveDialTimeout() |
There was a problem hiding this comment.
| case exporter.AzureOptions.ClientID != "": | |
| if pointer.GetDuration(exporter.ExporterOptions.ConnectionTimeout) != 0 { | |
| roundedConnectionTimeout = *exporter.ExporterOptions.ConnectionTimeout | |
| } else { | |
| roundedConnectionTimeout = postgresRemoteCloudDefaultDialTimeout | |
| } | |
| case node.NodeType == models.RemoteRDSNodeType: | |
| if pointer.GetDuration(exporter.ExporterOptions.ConnectionTimeout) != 0 { | |
| roundedConnectionTimeout = *exporter.ExporterOptions.ConnectionTimeout | |
| } else { | |
| roundedConnectionTimeout = postgresRemoteCloudDefaultDialTimeout | |
| } | |
| default: | |
| roundedConnectionTimeout = exporter.EffectiveDialTimeout() | |
| case exporter.AzureOptions.ClientID != "", | |
| node.NodeType == models.RemoteRDSNodeType: | |
| if pointer.GetDuration(exporter.ExporterOptions.ConnectionTimeout) != 0 { | |
| roundedConnectionTimeout = *exporter.ExporterOptions.ConnectionTimeout | |
| } else { | |
| roundedConnectionTimeout = postgresRemoteCloudDefaultDialTimeout | |
| } | |
| default: | |
| roundedConnectionTimeout = exporter.EffectiveDialTimeout() |
| connectionTimeout := exporter.ExporterOptions.ConnectionTimeout | ||
| if pointer.GetDuration(connectionTimeout) == 0 { | ||
| connectionTimeout = pointer.ToDuration(defaultValkeyTimeout) | ||
| } |
There was a problem hiding this comment.
| } | |
| connectionTimeout := &defaultValkeyTimeout | |
| if pointer.GetDuration(exporter.ExporterOptions.ConnectionTimeout) != 0 { | |
| connectionTimeout = exporter.ExporterOptions.ConnectionTimeout | |
| } |
There was a problem hiding this comment.
in general: there are many places where pointer.GetDuration(exporterOptions.ConnectionTimeout) is used.
Q: if we have validation on API level gte:0 it means here on service/model level there can be either exporterOptions.ConnectionTimeout == nil or exporterOptions.ConnectionTimeout > 0. So is there any reason to use pointer.GetDuration() at all?
managed/services/inventory/agents.go
Outdated
| return nil | ||
| } | ||
|
|
||
| return pointer.ToDuration(d.AsDuration()) |
There was a problem hiding this comment.
btw, there is shorter generic method pointer.To[T any](t T) *T that returns pointer to passed type
There was a problem hiding this comment.
Not sure which one is "faster" but changed, because yes it is shorter and simpler to read.
managed/services/inventory/agents.go
Outdated
| } | ||
| } | ||
|
|
||
| func optionalDurationFromProto(d *durationpb.Duration) *time.Duration { |
There was a problem hiding this comment.
move to managed/utils/duration ?
There was a problem hiding this comment.
Moved to utils.
| ServiceID: service.ServiceID, | ||
| AzureOptions: models.AzureOptionsFromRequest(req), | ||
| ExporterOptions: models.ExporterOptions{ | ||
| ConnectionTimeout: pointer.ToDuration(duration.FromProto(req.ConnectionTimeout)), |
There was a problem hiding this comment.
- optionalDurationFromProto ?
- I wonder why some
Create*funds receiveConnectionTimeout *time.Durationbut othersConnectionTimeout time.Duration
There was a problem hiding this comment.
- Yeah right. It was used for inventory. So I moved it from inventory to utils (like you requested even here PMM-12832 Timeouts for exporters. #5134 (comment)) and right now it is used also in management everywhere.
- My fault and inconsistency. Fixed
| LogLevel log_level = 5; | ||
| // Expose the node_exporter process on all public interfaces | ||
| bool expose_exporter = 6; | ||
| // Connection timeout for exporter (if set). |
There was a problem hiding this comment.
If I'm not wrong, the validation below will disallow me to skip the connection timeout, right?
| ConnectionTimeout: &custom, | ||
| }, | ||
| } | ||
| assert.Equal(t, custom, a.EffectiveDialTimeout()) |
There was a problem hiding this comment.
I think we should only use assert in very rare cases, where we want the test to continue running. In this particular case and downwards, IMO we should use require.
| if node.NodeType == models.RemoteRDSNodeType { | ||
| dsnParams.DialTimeout = 5 * time.Second | ||
| } | ||
| var roundedConnectionTimeout time.Duration |
There was a problem hiding this comment.
| var roundedConnectionTimeout time.Duration | |
| var connectionTimeout time.Duration |
I'd keep it short and simple :)
PMM-12832
FB: Percona-Lab/pmm-submodules#4275
Summary