From 5c2918163d2ebbbc1a50459a83cfdd489b38c351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Mon, 27 Jul 2026 11:43:43 +0200 Subject: [PATCH 1/6] PMM-1519 Change QAN-PGSM fix for internal error. --- managed/services/inventory/agents.go | 56 ++++++++--- managed/services/inventory/agents_test.go | 109 +++++++++++++++++++++- 2 files changed, 145 insertions(+), 20 deletions(-) diff --git a/managed/services/inventory/agents.go b/managed/services/inventory/agents.go index a8bc0a32e9e..45dfac05241 100644 --- a/managed/services/inventory/agents.go +++ b/managed/services/inventory/agents.go @@ -19,6 +19,7 @@ package inventory import ( "context" "os" + "strconv" "strings" "github.com/AlekSi/pointer" @@ -1178,6 +1179,11 @@ func (as *AgentsService) ChangeQANPostgreSQLPgStatementsAgent( ctx context.Context, agentID string, p *inventoryv1.ChangeQANPostgreSQLPgStatementsAgentParams, ) (*inventoryv1.ChangeAgentResponse, error) { + err := as.checkInternalPgQANEnvOverride(agentID, p.Enable) + if err != nil { + return nil, err + } + // Convert protobuf parameters to model parameters params := &models.ChangeAgentParams{ Enabled: p.Enable, @@ -1213,21 +1219,6 @@ func (as *AgentsService) ChangeQANPostgreSQLPgStatementsAgent( if err != nil { return nil, err } - // Check if we're trying to modify the internal PostgreSQL QAN agent and if the environment variable is set - envVar, exists := os.LookupEnv(env.EnableInternalPgQAN) - if exists && envVar != "" { - a, err := models.FindAgentByID(as.db.Querier, agentID) - if err != nil { - return nil, status.Errorf(codes.NotFound, "agent with ID %q not found", agentID) - } - if pointer.GetString(a.PMMAgentID) == models.PMMServerAgentID { - return nil, status.Errorf( - codes.FailedPrecondition, - "QAN for PMM's internal PostgreSQL server is set to %s via an environment variable.", - envVar, - ) - } - } pgStatementsAgent, ok := agent.(*inventoryv1.QANPostgreSQLPgStatementsAgent) if !ok { @@ -1767,6 +1758,41 @@ func unexpectedAgentTypeError(agent inventoryv1.Agent) error { return status.Errorf(codes.Internal, "unexpected agent type %T", agent) } +// checkInternalPgQANEnvOverride rejects a request that would flip the enabled state of the QAN agent +// of PMM's internal PostgreSQL server while that state is pinned by the PMM_ENABLE_INTERNAL_PG_QAN +// environment variable. Parameters unrelated to the enabled state stay changeable, and the check runs +// before anything is written so that a rejected request leaves the agent untouched. +func (as *AgentsService) checkInternalPgQANEnvOverride(agentID string, enable *bool) error { + if enable == nil { + return nil + } + + envValue, exists := os.LookupEnv(env.EnableInternalPgQAN) + if !exists || envValue == "" { + return nil + } + + enabledByEnv, err := strconv.ParseBool(envValue) + if err != nil || *enable == enabledByEnv { + return nil + } + + agent, err := models.FindAgentByID(as.db.Querier, agentID) + if err != nil { + return err + } + + if agent.AgentType != models.QANPostgreSQLPgStatementsAgentType || pointer.GetString(agent.PMMAgentID) != models.PMMServerAgentID { + return nil + } + + return status.Errorf( + codes.FailedPrecondition, + "QAN for PMM's internal PostgreSQL server is set to %s via an environment variable.", + envValue, + ) +} + // Helper function to convert custom labels from protobuf to model format. func convertCustomLabels(customLabels *common.StringMap) *map[string]string { if customLabels != nil { diff --git a/managed/services/inventory/agents_test.go b/managed/services/inventory/agents_test.go index bf2287a6f2b..6426fd5bc34 100644 --- a/managed/services/inventory/agents_test.go +++ b/managed/services/inventory/agents_test.go @@ -920,6 +920,11 @@ func TestAgents(t *testing.T) { } func TestChangeQANPostgreSQLPgStatementsAgentWithEnvVar(t *testing.T) { + // The QAN agent of PMM's internal PostgreSQL, created by the test fixtures. It runs under + // PMM Server's own pmm-agent and is disabled, because PMM_ENABLE_INTERNAL_PG_QAN is not set + // while the fixtures are created. + const internalPgQANAgentID = "00000000-0000-4000-8000-000000000004" + t.Run("FailWhenEnvVarSet", func(t *testing.T) { _, as, _, teardown, ctx, _ := setup(t) t.Cleanup(func() { teardown(t) }) @@ -927,9 +932,8 @@ func TestChangeQANPostgreSQLPgStatementsAgentWithEnvVar(t *testing.T) { // Set the environment variable t.Setenv(env.EnableInternalPgQAN, "true") - // Try to change the internal PostgreSQL QAN agent (pmm-server's agent) - // The agent with ID "00000000-0000-4000-8000-000000000004" is the internal PostgreSQL QAN agent - _, err := as.ChangeQANPostgreSQLPgStatementsAgent(ctx, "00000000-0000-4000-8000-000000000004", &inventoryv1.ChangeQANPostgreSQLPgStatementsAgentParams{ + // Try to disable the internal PostgreSQL QAN agent while the environment variable enables it + _, err := as.ChangeQANPostgreSQLPgStatementsAgent(ctx, internalPgQANAgentID, &inventoryv1.ChangeQANPostgreSQLPgStatementsAgentParams{ Enable: new(false), }) @@ -937,6 +941,101 @@ func TestChangeQANPostgreSQLPgStatementsAgentWithEnvVar(t *testing.T) { tests.AssertGRPCError(t, status.New(codes.FailedPrecondition, "QAN for PMM's internal PostgreSQL server is set to true via an environment variable."), err) }) + t.Run("KeepAgentIntactWhenRejected", func(t *testing.T) { + _, as, _, teardown, ctx, _ := setup(t) + t.Cleanup(func() { teardown(t) }) + + t.Setenv(env.EnableInternalPgQAN, "false") + + _, err := as.ChangeQANPostgreSQLPgStatementsAgent(ctx, internalPgQANAgentID, &inventoryv1.ChangeQANPostgreSQLPgStatementsAgentParams{ + Enable: new(true), + LogLevel: inventoryv1.LogLevel_LOG_LEVEL_DEBUG.Enum(), + }) + tests.AssertGRPCError(t, status.New(codes.FailedPrecondition, "QAN for PMM's internal PostgreSQL server is set to false via an environment variable."), err) + + // A rejected request must not leave any of the requested changes behind. + agent, err := models.FindAgentByID(as.db.Querier, internalPgQANAgentID) + require.NoError(t, err) + assert.True(t, agent.Disabled) + assert.Nil(t, agent.LogLevel) + }) + + t.Run("SucceedForParametersUnrelatedToEnvVar", func(t *testing.T) { + _, as, _, teardown, ctx, _ := setup(t) + t.Cleanup(func() { teardown(t) }) + + t.Setenv(env.EnableInternalPgQAN, "true") + as.state.(*mockAgentsStateUpdater).On("RequestStateUpdate", ctx, models.PMMServerAgentID) + + // The environment variable pins only the enabled state, everything else stays changeable. + agent, err := as.ChangeQANPostgreSQLPgStatementsAgent(ctx, internalPgQANAgentID, &inventoryv1.ChangeQANPostgreSQLPgStatementsAgentParams{ + LogLevel: inventoryv1.LogLevel_LOG_LEVEL_DEBUG.Enum(), + MaxQueryLength: new(int32(2048)), + }) + + require.NoError(t, err) + assert.Equal(t, inventoryv1.LogLevel_LOG_LEVEL_DEBUG, agent.GetQanPostgresqlPgstatementsAgent().LogLevel) + assert.Equal(t, int32(2048), agent.GetQanPostgresqlPgstatementsAgent().MaxQueryLength) + assert.True(t, agent.GetQanPostgresqlPgstatementsAgent().Disabled) + }) + + t.Run("SucceedWhenRequestedStateMatchesEnvVar", func(t *testing.T) { + _, as, _, teardown, ctx, _ := setup(t) + t.Cleanup(func() { teardown(t) }) + + t.Setenv(env.EnableInternalPgQAN, "true") + as.state.(*mockAgentsStateUpdater).On("RequestStateUpdate", ctx, models.PMMServerAgentID) + + agent, err := as.ChangeQANPostgreSQLPgStatementsAgent(ctx, internalPgQANAgentID, &inventoryv1.ChangeQANPostgreSQLPgStatementsAgentParams{ + Enable: new(true), + }) + + require.NoError(t, err) + assert.False(t, agent.GetQanPostgresqlPgstatementsAgent().Disabled) + }) + + t.Run("SucceedForAgentOutsideOfPMMServer", func(t *testing.T) { + ss, as, _, teardown, ctx, _ := setup(t) + t.Cleanup(func() { teardown(t) }) + + as.r.(*mockAgentsRegistry).On("IsConnected", "00000000-0000-4000-8000-000000000005").Return(true) + // One state update for adding the agent, another one for changing it. + as.state.(*mockAgentsStateUpdater).On("RequestStateUpdate", ctx, "00000000-0000-4000-8000-000000000005").Times(2) + + pmmAgent, err := as.AddPMMAgent(ctx, &inventoryv1.AddPMMAgentParams{ + RunsOnNodeId: models.PMMServerNodeID, + }) + require.NoError(t, err) + + ps, err := ss.AddPostgreSQL(ctx, &models.AddDBMSServiceParams{ + ServiceName: "test-postgres", + NodeID: models.PMMServerNodeID, + Address: new("127.0.0.1"), + Port: new(uint16(5432)), + }) + require.NoError(t, err) + + added, err := as.AddQANPostgreSQLPgStatementsAgent(ctx, &inventoryv1.AddQANPostgreSQLPgStatementsAgentParams{ + PmmAgentId: pmmAgent.GetPmmAgent().AgentId, + ServiceId: ps.ServiceId, + Username: "username", + SkipConnectionCheck: true, + }) + require.NoError(t, err) + + t.Setenv(env.EnableInternalPgQAN, "true") + + // The environment variable only covers PMM's internal PostgreSQL, agents monitoring + // other PostgreSQL services are not affected by it. + agent, err := as.ChangeQANPostgreSQLPgStatementsAgent(ctx, added.GetQanPostgresqlPgstatementsAgent().AgentId, + &inventoryv1.ChangeQANPostgreSQLPgStatementsAgentParams{ + Enable: new(false), + }) + + require.NoError(t, err) + assert.True(t, agent.GetQanPostgresqlPgstatementsAgent().Disabled) + }) + t.Run("SucceedWhenEnvVarNotSet", func(t *testing.T) { _, as, _, teardown, ctx, _ := setup(t) t.Cleanup(func() { teardown(t) }) @@ -949,7 +1048,7 @@ func TestChangeQANPostgreSQLPgStatementsAgentWithEnvVar(t *testing.T) { as.state.(*mockAgentsStateUpdater).On("RequestStateUpdate", ctx, "pmm-server") // Try to change the internal PostgreSQL QAN agent - agent, err := as.ChangeQANPostgreSQLPgStatementsAgent(ctx, "00000000-0000-4000-8000-000000000004", &inventoryv1.ChangeQANPostgreSQLPgStatementsAgentParams{ + agent, err := as.ChangeQANPostgreSQLPgStatementsAgent(ctx, internalPgQANAgentID, &inventoryv1.ChangeQANPostgreSQLPgStatementsAgentParams{ Enable: new(false), }) @@ -959,7 +1058,7 @@ func TestChangeQANPostgreSQLPgStatementsAgentWithEnvVar(t *testing.T) { // Change it back to enabled as.state.(*mockAgentsStateUpdater).On("RequestStateUpdate", ctx, "pmm-server") - agent, err = as.ChangeQANPostgreSQLPgStatementsAgent(ctx, "00000000-0000-4000-8000-000000000004", &inventoryv1.ChangeQANPostgreSQLPgStatementsAgentParams{ + agent, err = as.ChangeQANPostgreSQLPgStatementsAgent(ctx, internalPgQANAgentID, &inventoryv1.ChangeQANPostgreSQLPgStatementsAgentParams{ Enable: new(true), }) From f9589e09a1b8556cacd90e08d2a18d4952b1b683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Mon, 27 Jul 2026 12:08:12 +0200 Subject: [PATCH 2/6] PMM-15191 Refactor, lint. --- managed/services/inventory/agents.go | 31 ++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/managed/services/inventory/agents.go b/managed/services/inventory/agents.go index 45dfac05241..e1e8275411f 100644 --- a/managed/services/inventory/agents.go +++ b/managed/services/inventory/agents.go @@ -1767,13 +1767,8 @@ func (as *AgentsService) checkInternalPgQANEnvOverride(agentID string, enable *b return nil } - envValue, exists := os.LookupEnv(env.EnableInternalPgQAN) - if !exists || envValue == "" { - return nil - } - - enabledByEnv, err := strconv.ParseBool(envValue) - if err != nil || *enable == enabledByEnv { + enabledByEnv := internalPgQANEnabledByEnv() + if enabledByEnv == nil || *enable == *enabledByEnv { return nil } @@ -1788,11 +1783,29 @@ func (as *AgentsService) checkInternalPgQANEnvOverride(agentID string, enable *b return status.Errorf( codes.FailedPrecondition, - "QAN for PMM's internal PostgreSQL server is set to %s via an environment variable.", - envValue, + "QAN for PMM's internal PostgreSQL server is set to %t via an environment variable.", + *enabledByEnv, ) } +// internalPgQANEnabledByEnv returns the state that PMM_ENABLE_INTERNAL_PG_QAN pins QAN for PMM's +// internal PostgreSQL server to, or nil when the variable is unset or does not hold a boolean. +// Invalid values are reported by the environment variable parser during startup, so they are +// treated here as if the variable was not set at all. +func internalPgQANEnabledByEnv() *bool { + value, exists := os.LookupEnv(env.EnableInternalPgQAN) + if !exists || value == "" { + return nil + } + + enabled, err := strconv.ParseBool(value) + if err != nil { + return nil + } + + return &enabled +} + // Helper function to convert custom labels from protobuf to model format. func convertCustomLabels(customLabels *common.StringMap) *map[string]string { if customLabels != nil { From 15b7b99a71f0bb4e0c159928de7832b1839f255e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Thu, 30 Jul 2026 09:45:27 +0200 Subject: [PATCH 3/6] PMM-15191 Move optional bool env parsing into the env package. --- managed/services/inventory/agents.go | 24 +++--------------------- managed/utils/env/env.go | 22 +++++++++++++++++----- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/managed/services/inventory/agents.go b/managed/services/inventory/agents.go index 7685c3d2d7e..4319deb46ad 100644 --- a/managed/services/inventory/agents.go +++ b/managed/services/inventory/agents.go @@ -18,8 +18,6 @@ package inventory import ( "context" - "os" - "strconv" "strings" "github.com/AlekSi/pointer" @@ -1768,7 +1766,9 @@ func (as *AgentsService) checkInternalPgQANEnvOverride(agentID string, enable *b return nil } - enabledByEnv := internalPgQANEnabledByEnv() + // An invalid value is reported by the environment variable parser during startup, so + // env.LookupBool treats it as if the variable was not set at all. + enabledByEnv := env.LookupBool(env.EnableInternalPgQAN) if enabledByEnv == nil || *enable == *enabledByEnv { return nil } @@ -1789,24 +1789,6 @@ func (as *AgentsService) checkInternalPgQANEnvOverride(agentID string, enable *b ) } -// internalPgQANEnabledByEnv returns the state that PMM_ENABLE_INTERNAL_PG_QAN pins QAN for PMM's -// internal PostgreSQL server to, or nil when the variable is unset or does not hold a boolean. -// Invalid values are reported by the environment variable parser during startup, so they are -// treated here as if the variable was not set at all. -func internalPgQANEnabledByEnv() *bool { - value, exists := os.LookupEnv(env.EnableInternalPgQAN) - if !exists || value == "" { - return nil - } - - enabled, err := strconv.ParseBool(value) - if err != nil { - return nil - } - - return &enabled -} - // Helper function to convert custom labels from protobuf to model format. func convertCustomLabels(customLabels *common.StringMap) *map[string]string { if customLabels != nil { diff --git a/managed/utils/env/env.go b/managed/utils/env/env.go index 5e641cdcaef..e02bfe90ed9 100644 --- a/managed/utils/env/env.go +++ b/managed/utils/env/env.go @@ -48,19 +48,31 @@ const ( ClickHouseConfig = "PMM_CLICKHOUSE_CONFIG" ) -// GetBool returns the boolean value of the environment variable. -// Returns false if the variable is not set or cannot be parsed as boolean. +// LookupBool returns the boolean value of the environment variable, or nil if the variable +// is not set or cannot be parsed as boolean. Use it to tell "not configured" apart from +// "configured as false". // It does not return errors since it assumes that validation has already been done during startup. -func GetBool(key string) bool { +func LookupBool(key string) *bool { v, ok := os.LookupEnv(key) if !ok { - return false + return nil } b, err := strconv.ParseBool(v) if err != nil { + return nil + } + return &b +} + +// GetBool returns the boolean value of the environment variable. +// Returns false if the variable is not set or cannot be parsed as boolean. +// It does not return errors since it assumes that validation has already been done during startup. +func GetBool(key string) bool { + b := LookupBool(key) + if b == nil { return false } - return b + return *b } // GetStringSlice returns the string slice value of the environment variable. From a0e8d9ce482e6dbfe3dfd4f2874c20e301b03e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Thu, 30 Jul 2026 09:48:13 +0200 Subject: [PATCH 4/6] PMM-15191 Enforce the internal PG QAN env override on the agent row. --- managed/services/inventory/agents.go | 33 +++++++++++------------ managed/services/inventory/agents_test.go | 21 +++++++++++++++ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/managed/services/inventory/agents.go b/managed/services/inventory/agents.go index 4319deb46ad..d506ed3ae49 100644 --- a/managed/services/inventory/agents.go +++ b/managed/services/inventory/agents.go @@ -1178,11 +1178,6 @@ func (as *AgentsService) ChangeQANPostgreSQLPgStatementsAgent( ctx context.Context, agentID string, p *inventoryv1.ChangeQANPostgreSQLPgStatementsAgentParams, ) (*inventoryv1.ChangeAgentResponse, error) { - err := as.checkInternalPgQANEnvOverride(agentID, p.Enable) - if err != nil { - return nil, err - } - // Convert protobuf parameters to model parameters params := &models.ChangeAgentParams{ Enabled: p.Enable, @@ -1759,13 +1754,20 @@ func unexpectedAgentTypeError(agent inventoryv1.Agent) error { // checkInternalPgQANEnvOverride rejects a request that would flip the enabled state of the QAN agent // of PMM's internal PostgreSQL server while that state is pinned by the PMM_ENABLE_INTERNAL_PG_QAN -// environment variable. Parameters unrelated to the enabled state stay changeable, and the check runs -// before anything is written so that a rejected request leaves the agent untouched. -func (as *AgentsService) checkInternalPgQANEnvOverride(agentID string, enable *bool) error { +// environment variable. Parameters unrelated to the enabled state stay changeable. +// +// It keys off the stored agent row rather than the calling method, because the inventory API picks +// the method from the request payload and not from the type of the agent being changed. Any +// Change*Agent method can therefore be pointed at the internal QAN agent. +func checkInternalPgQANEnvOverride(agent *models.Agent, enable *bool) error { if enable == nil { return nil } + if agent.AgentType != models.QANPostgreSQLPgStatementsAgentType || pointer.GetString(agent.PMMAgentID) != models.PMMServerAgentID { + return nil + } + // An invalid value is reported by the environment variable parser during startup, so // env.LookupBool treats it as if the variable was not set at all. enabledByEnv := env.LookupBool(env.EnableInternalPgQAN) @@ -1773,15 +1775,6 @@ func (as *AgentsService) checkInternalPgQANEnvOverride(agentID string, enable *b return nil } - agent, err := models.FindAgentByID(as.db.Querier, agentID) - if err != nil { - return err - } - - if agent.AgentType != models.QANPostgreSQLPgStatementsAgentType || pointer.GetString(agent.PMMAgentID) != models.PMMServerAgentID { - return nil - } - return status.Errorf( codes.FailedPrecondition, "QAN for PMM's internal PostgreSQL server is set to %t via an environment variable.", @@ -1844,6 +1837,12 @@ func (as *AgentsService) executeAgentChange(ctx context.Context, agentID string, return err } + // Returning an error rolls the transaction back, so a rejected request leaves the agent untouched. + err = checkInternalPgQANEnvOverride(updatedAgent, params.Enabled) + if err != nil { + return err + } + if !params.SkipConnectionCheck && params.AffectsConnection() && updatedAgent.ServiceID != nil { service, err := models.FindServiceByID(tx.Querier, pointer.GetString(updatedAgent.ServiceID)) if err != nil { diff --git a/managed/services/inventory/agents_test.go b/managed/services/inventory/agents_test.go index 65516e98ea6..5c7457d8821 100644 --- a/managed/services/inventory/agents_test.go +++ b/managed/services/inventory/agents_test.go @@ -962,6 +962,27 @@ func TestChangeQANPostgreSQLPgStatementsAgentWithEnvVar(t *testing.T) { assert.Nil(t, agent.LogLevel) }) + t.Run("FailWhenRequestedThroughParamsOfAnotherAgentType", func(t *testing.T) { + _, as, _, teardown, ctx, _ := setup(t) + t.Cleanup(func() { teardown(t) }) + + t.Setenv(env.EnableInternalPgQAN, "true") + + // The inventory API picks the Change*Agent method from the request payload and not from + // the type of the agent being changed, so any of them can be pointed at the internal QAN + // agent. The guard must hold no matter which one was called. + _, err := as.ChangeQANPostgreSQLPgStatMonitorAgent(ctx, internalPgQANAgentID, &inventoryv1.ChangeQANPostgreSQLPgStatMonitorAgentParams{ + Enable: new(false), + LogLevel: inventoryv1.LogLevel_LOG_LEVEL_DEBUG.Enum(), + }) + tests.AssertGRPCError(t, status.New(codes.FailedPrecondition, "QAN for PMM's internal PostgreSQL server is set to true via an environment variable."), err) + + agent, err := models.FindAgentByID(as.db.Querier, internalPgQANAgentID) + require.NoError(t, err) + assert.True(t, agent.Disabled) + assert.Nil(t, agent.LogLevel) + }) + t.Run("SucceedForParametersUnrelatedToEnvVar", func(t *testing.T) { _, as, _, teardown, ctx, _ := setup(t) t.Cleanup(func() { teardown(t) }) From 24e32a9f3a015a987252d58e8a35f7695f8282c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Thu, 30 Jul 2026 09:49:06 +0200 Subject: [PATCH 5/6] PMM-15191 Cover the branches that let the env override stand down. --- managed/services/inventory/agents_test.go | 42 +++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/managed/services/inventory/agents_test.go b/managed/services/inventory/agents_test.go index 5c7457d8821..75cba2bcaa3 100644 --- a/managed/services/inventory/agents_test.go +++ b/managed/services/inventory/agents_test.go @@ -1059,6 +1059,48 @@ func TestChangeQANPostgreSQLPgStatementsAgentWithEnvVar(t *testing.T) { assert.True(t, agent.GetQanPostgresqlPgstatementsAgent().Disabled) }) + t.Run("SucceedForOtherAgentTypesOfPMMServer", func(t *testing.T) { + _, as, _, teardown, ctx, _ := setup(t) + t.Cleanup(func() { teardown(t) }) + + // The postgres_exporter of PMM's internal PostgreSQL, running under the same pmm-agent + // as the QAN agent that the environment variable pins. + pgExporters, err := models.FindAgents(as.db.Querier, models.AgentFilters{ + PMMAgentID: models.PMMServerAgentID, + AgentType: new(models.PostgresExporterType), + }) + require.NoError(t, err) + require.Len(t, pgExporters, 1) + + t.Setenv(env.EnableInternalPgQAN, "true") + as.state.(*mockAgentsStateUpdater).On("RequestStateUpdate", ctx, models.PMMServerAgentID) + + // The environment variable covers QAN only, the other agents of PMM Server stay changeable. + agent, err := as.ChangePostgresExporter(ctx, pgExporters[0].AgentID, &inventoryv1.ChangePostgresExporterParams{ + Enable: new(false), + }) + + require.NoError(t, err) + assert.True(t, agent.GetPostgresExporter().Disabled) + }) + + t.Run("SucceedWhenEnvVarValueIsNotABool", func(t *testing.T) { + _, as, _, teardown, ctx, _ := setup(t) + t.Cleanup(func() { teardown(t) }) + + // A value that is not a boolean is reported by the environment variable parser during + // startup and pins nothing, exactly like an unset variable. + t.Setenv(env.EnableInternalPgQAN, "not-a-bool") + as.state.(*mockAgentsStateUpdater).On("RequestStateUpdate", ctx, models.PMMServerAgentID) + + agent, err := as.ChangeQANPostgreSQLPgStatementsAgent(ctx, internalPgQANAgentID, &inventoryv1.ChangeQANPostgreSQLPgStatementsAgentParams{ + Enable: new(true), + }) + + require.NoError(t, err) + assert.False(t, agent.GetQanPostgresqlPgstatementsAgent().Disabled) + }) + t.Run("SucceedWhenEnvVarNotSet", func(t *testing.T) { _, as, _, teardown, ctx, _ := setup(t) t.Cleanup(func() { teardown(t) }) From 8b865ff07358ac6ceb954804e2ca0b086809f593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Thu, 30 Jul 2026 09:50:11 +0200 Subject: [PATCH 6/6] PMM-15191 Stop predicting generated agent IDs in the env var test. --- managed/services/inventory/agents_test.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/managed/services/inventory/agents_test.go b/managed/services/inventory/agents_test.go index 75cba2bcaa3..e7eae5ba9ef 100644 --- a/managed/services/inventory/agents_test.go +++ b/managed/services/inventory/agents_test.go @@ -999,6 +999,9 @@ func TestChangeQANPostgreSQLPgStatementsAgentWithEnvVar(t *testing.T) { require.NoError(t, err) assert.Equal(t, inventoryv1.LogLevel_LOG_LEVEL_DEBUG, agent.GetQanPostgresqlPgstatementsAgent().LogLevel) assert.Equal(t, int32(2048), agent.GetQanPostgresqlPgstatementsAgent().MaxQueryLength) + // The agent stays disabled even though the variable enables QAN: the variable is only read + // when the fixtures create the agent, and nothing reconciles the stored state with it + // afterwards. Asserted to pin down current behaviour, not because it is desirable. assert.True(t, agent.GetQanPostgresqlPgstatementsAgent().Disabled) }) @@ -1021,14 +1024,16 @@ func TestChangeQANPostgreSQLPgStatementsAgentWithEnvVar(t *testing.T) { ss, as, _, teardown, ctx, _ := setup(t) t.Cleanup(func() { teardown(t) }) - as.r.(*mockAgentsRegistry).On("IsConnected", "00000000-0000-4000-8000-000000000005").Return(true) - // One state update for adding the agent, another one for changing it. - as.state.(*mockAgentsStateUpdater).On("RequestStateUpdate", ctx, "00000000-0000-4000-8000-000000000005").Times(2) + as.r.(*mockAgentsRegistry).On("IsConnected", mock.Anything).Return(true) pmmAgent, err := as.AddPMMAgent(ctx, &inventoryv1.AddPMMAgentParams{ RunsOnNodeId: models.PMMServerNodeID, }) require.NoError(t, err) + pmmAgentID := pmmAgent.GetPmmAgent().AgentId + + // One state update for adding the QAN agent, another one for changing it. + as.state.(*mockAgentsStateUpdater).On("RequestStateUpdate", ctx, pmmAgentID).Times(2) ps, err := ss.AddPostgreSQL(ctx, &models.AddDBMSServiceParams{ ServiceName: "test-postgres", @@ -1039,7 +1044,7 @@ func TestChangeQANPostgreSQLPgStatementsAgentWithEnvVar(t *testing.T) { require.NoError(t, err) added, err := as.AddQANPostgreSQLPgStatementsAgent(ctx, &inventoryv1.AddQANPostgreSQLPgStatementsAgentParams{ - PmmAgentId: pmmAgent.GetPmmAgent().AgentId, + PmmAgentId: pmmAgentID, ServiceId: ps.ServiceId, Username: "username", SkipConnectionCheck: true,