diff --git a/api-tests/management/mysql_test.go b/api-tests/management/mysql_test.go index 5f48073b8c..fb489c874f 100644 --- a/api-tests/management/mysql_test.go +++ b/api-tests/management/mysql_test.go @@ -596,6 +596,7 @@ func TestAddMySQL(t *testing.T) { assert.Nil(t, addMySQLOK) }) + // according to defaultFiles parameter, passing empty username is allowed t.Run("Empty username", func(t *testing.T) { nodeName := pmmapitests.TestString(t, "node-name") nodeID, pmmAgentID := RegisterGenericNode(t, node.RegisterNodeBody{ @@ -614,11 +615,16 @@ func TestAddMySQL(t *testing.T) { Address: "10.10.10.10", Port: 3306, PMMAgentID: pmmAgentID, + + SkipConnectionCheck: true, }, } addMySQLOK, err := client.Default.MySQL.AddMySQL(params) - pmmapitests.AssertAPIErrorf(t, err, 400, codes.InvalidArgument, "invalid field Username: value '' must not be an empty string") - assert.Nil(t, addMySQLOK) + require.NoError(t, err) + require.NotNil(t, addMySQLOK) + require.NotNil(t, addMySQLOK.Payload.Service) + serviceID := addMySQLOK.Payload.Service.ServiceID + defer pmmapitests.RemoveServices(t, serviceID) }) t.Run("With MetricsModePush", func(t *testing.T) { diff --git a/go.mod b/go.mod index 2603b4c59c..d81c6be531 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.18 // Use for local development, but do not commit: // replace github.com/percona/pmm => ../pmm +// replace github.com/percona/pmm => ../pmm // replace github.com/percona-platform/saas => ../saas // replace github.com/percona-platform/dbaas-api => ../dbaas-api @@ -36,7 +37,7 @@ require ( github.com/minio/minio-go/v7 v7.0.27 github.com/percona-platform/dbaas-api v0.0.0-20220110092915-5aacd784d472 github.com/percona-platform/saas v0.0.0-20220427162947-f9d246ad0f16 - github.com/percona/pmm v0.0.0-20220607154345-cf9e6085e661 + github.com/percona/pmm v0.0.0-20220609163826-96fb5593d69b github.com/percona/promconfig v0.2.4-0.20211110115058-98687f586f54 github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.0 @@ -138,7 +139,7 @@ require ( golang.org/x/tools v0.1.9 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.6.7 // indirect - gopkg.in/ini.v1 v1.66.4 // indirect + gopkg.in/ini.v1 v1.66.6 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index cf476b9637..e9ef88b99f 100644 --- a/go.sum +++ b/go.sum @@ -467,8 +467,8 @@ github.com/percona-platform/dbaas-api v0.0.0-20220110092915-5aacd784d472 h1:Henk github.com/percona-platform/dbaas-api v0.0.0-20220110092915-5aacd784d472/go.mod h1:WZZ3Hi+lAWCaGWmsrfkkvRQPkIa8n1OZ0s8Su+vbgus= github.com/percona-platform/saas v0.0.0-20220427162947-f9d246ad0f16 h1:0fx16uGtl4MwrBwm9/VSoNEhjL0cXYxS0quEhLthGcc= github.com/percona-platform/saas v0.0.0-20220427162947-f9d246ad0f16/go.mod h1:gFUwaFp6Ugu5qsBwiOVJYbDlzgZ77tmXdXGO7tG5xVI= -github.com/percona/pmm v0.0.0-20220607154345-cf9e6085e661 h1:xgAObd0x67UJdtEwnN5JeZFyCYQ9BCiGCnaBy4gB4F4= -github.com/percona/pmm v0.0.0-20220607154345-cf9e6085e661/go.mod h1:ix7lLnoQysi2ki1TxfWB3GSckkvER4nVRKVHo+zkagg= +github.com/percona/pmm v0.0.0-20220609163826-96fb5593d69b h1:b3MUqjYU1L85FExBgewV9xFluJEFRO129pRHskgxW+0= +github.com/percona/pmm v0.0.0-20220609163826-96fb5593d69b/go.mod h1:c22C8QvyFlcxr61TbqPlLGVC2u4xDptqYuYAoV0Umbs= github.com/percona/promconfig v0.2.4-0.20211110115058-98687f586f54 h1:aI1emmycDTGWKsBdxFPKZqohfBbK4y2ta9G4+RX7gVg= github.com/percona/promconfig v0.2.4-0.20211110115058-98687f586f54/go.mod h1:Y2uXi5QNk71+ceJHuI9poank+0S1kjxd3K105fXKVkg= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -938,8 +938,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= -gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= +gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/reform.v1 v1.5.1 h1:7vhDFW1n1xAPC6oDSvIvVvpRkaRpXlxgJ4QB4s3aDdo= gopkg.in/reform.v1 v1.5.1/go.mod h1:AIv0CbDRJ0ljQwptGeaIXfpDRo02uJwTq92aMFELEeU= gopkg.in/telebot.v3 v3.0.0/go.mod h1:7rExV8/0mDDNu9epSrDm/8j22KLaActH1Tbee6YjzWg= diff --git a/main.go b/main.go index 85726e74a4..35b5a1d4d8 100644 --- a/main.go +++ b/main.go @@ -138,6 +138,7 @@ type gRPCServerDeps struct { actions *agents.ActionsService agentsStateUpdater *agents.StateUpdater connectionCheck *agents.ConnectionChecker + defaultsFileParser *agents.DefaultsFileParser grafanaClient *grafana.Client checksService *checks.Service dbaasClient *dbaas.Client @@ -190,7 +191,7 @@ func runGRPCServer(ctx context.Context, deps *gRPCServerDeps) { nodeSvc := management.NewNodeService(deps.db) serviceSvc := management.NewServiceService(deps.db, deps.agentsStateUpdater, deps.vmdb) - mysqlSvc := management.NewMySQLService(deps.db, deps.agentsStateUpdater, deps.connectionCheck, deps.versionCache) + mysqlSvc := management.NewMySQLService(deps.db, deps.agentsStateUpdater, deps.connectionCheck, deps.versionCache, deps.defaultsFileParser) mongodbSvc := management.NewMongoDBService(deps.db, deps.agentsStateUpdater, deps.connectionCheck) postgresqlSvc := management.NewPostgreSQLService(deps.db, deps.agentsStateUpdater, deps.connectionCheck) proxysqlSvc := management.NewProxySQLService(deps.db, deps.agentsStateUpdater, deps.connectionCheck) @@ -737,6 +738,7 @@ func main() { schedulerService := scheduler.New(db, backupService) versionCache := versioncache.New(db, versioner) emailer := alertmanager.NewEmailer(logrus.WithField("component", "alertmanager-emailer").Logger) + defaultsFileParser := agents.NewDefaultsFileParser(agentsRegistry) serverParams := &server.Params{ DB: db, @@ -937,6 +939,7 @@ func main() { versionCache: versionCache, supervisord: supervisord, config: &cfg.Config, + defaultsFileParser: defaultsFileParser, }) }() diff --git a/models/defaults_file.go b/models/defaults_file.go new file mode 100644 index 0000000000..09f138bca0 --- /dev/null +++ b/models/defaults_file.go @@ -0,0 +1,26 @@ +// pmm-managed +// Copyright (C) 2017 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package models + +// ParseDefaultsFileResult contains result of parsing defaults file. +type ParseDefaultsFileResult struct { + Username string + Password string + Host string + Port uint32 + Socket string +} diff --git a/services/agents/channel/channel.go b/services/agents/channel/channel.go index 99ba8dfcb8..131bf66f54 100644 --- a/services/agents/channel/channel.go +++ b/services/agents/channel/channel.go @@ -275,6 +275,8 @@ func (c *Channel) runReceiver() { c.publish(msg.Id, msg.Status, p.GetVersions) case *agentpb.AgentMessage_PbmSwitchPitr: c.publish(msg.Id, msg.Status, p.PbmSwitchPitr) + case *agentpb.AgentMessage_ParseDefaultsFile: + c.publish(msg.Id, msg.Status, p.ParseDefaultsFile) case nil: c.cancel(msg.Id, errors.Errorf("unimplemented: failed to handle received message %s", msg)) diff --git a/services/agents/channel/channel_test.go b/services/agents/channel/channel_test.go index a777a641ad..57354dc93a 100644 --- a/services/agents/channel/channel_test.go +++ b/services/agents/channel/channel_test.go @@ -356,3 +356,50 @@ func TestUnexpectedResponsePayloadFromAgent(t *testing.T) { close(stopServer) <-stop } + +func TestChannelForDefaultsFileParser(t *testing.T) { + const count = 50 + require.True(t, count > agentRequestsCap) + + connect := func(ch *Channel) error { + testValue := "test" + testPort := uint32(123123) + for i := uint32(1); i <= count; i++ { + resp, err := ch.SendAndWaitResponse(&agentpb.ParseDefaultsFileRequest{}) + assert.NotNil(t, resp) + parserResponse := resp.(*agentpb.ParseDefaultsFileResponse) + assert.Equal(t, parserResponse.Username, testValue) + assert.Equal(t, parserResponse.Password, testValue) + assert.Equal(t, parserResponse.Socket, testValue) + assert.Equal(t, parserResponse.Port, testPort) + assert.NoError(t, err) + } + + assert.Nil(t, <-ch.Requests()) + return nil + } + + stream, _, teardown := setup(t, connect, io.EOF) // EOF = server exits from handler + defer teardown(t) + + for i := uint32(1); i <= count; i++ { + msg, err := stream.Recv() + assert.NoError(t, err) + assert.Equal(t, i, msg.Id) + assert.NotNil(t, msg.GetParseDefaultsFile()) + + err = stream.Send(&agentpb.AgentMessage{ + Id: i, + Payload: (&agentpb.ParseDefaultsFileResponse{ + Username: "test", + Password: "test", + Port: 123123, + Socket: "test", + }).AgentMessageResponsePayload(), + }) + assert.NoError(t, err) + } + + err := stream.CloseSend() + assert.NoError(t, err) +} diff --git a/services/agents/parse_defaults_file.go b/services/agents/parse_defaults_file.go new file mode 100644 index 0000000000..b42e4cef2a --- /dev/null +++ b/services/agents/parse_defaults_file.go @@ -0,0 +1,98 @@ +// pmm-managed +// Copyright (C) 2017 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package agents + +import ( + "context" + "time" + + "github.com/percona/pmm/api/agentpb" + "github.com/percona/pmm/api/inventorypb" + "github.com/pkg/errors" + + "github.com/percona/pmm-managed/models" + "github.com/percona/pmm-managed/utils/logger" +) + +// DefaultsFileParser requests from agent to parse defaultsFile. +type DefaultsFileParser struct { + r *Registry +} + +// NewDefaultsFileParser creates new ParseDefaultsFile request. +func NewDefaultsFileParser(r *Registry) *DefaultsFileParser { + return &DefaultsFileParser{ + r: r, + } +} + +// ParseDefaultsFile sends request (with file path) to pmm-agent to parse defaults file. +func (p *DefaultsFileParser) ParseDefaultsFile(ctx context.Context, pmmAgentID, filePath string, serviceType models.ServiceType) (*models.ParseDefaultsFileResult, error) { + l := logger.Get(ctx) + + pmmAgent, err := p.r.get(pmmAgentID) + if err != nil { + return nil, err + } + + start := time.Now() + defer func() { + if dur := time.Since(start); dur > 5*time.Second { + l.Warnf("ParseDefaultsFile took %s.", dur) + } + }() + + request, err := createRequest(filePath, serviceType) + if err != nil { + l.Debugf("can't create ParseDefaultsFileRequest %s", err) + return nil, err + } + + resp, err := pmmAgent.channel.SendAndWaitResponse(request) + if err != nil { + return nil, err + } + + l.Infof("ParseDefaultsFile response from agent: %+v.", resp) + parserResponse, ok := resp.(*agentpb.ParseDefaultsFileResponse) + if !ok { + return nil, errors.New("wrong response from agent (not ParseDefaultsFileResponse model)") + } + if parserResponse.Error != "" { + return nil, errors.New(parserResponse.Error) + } + + return &models.ParseDefaultsFileResult{ + Username: parserResponse.Username, + Password: parserResponse.Password, + Host: parserResponse.Host, + Port: parserResponse.Port, + Socket: parserResponse.Socket, + }, nil +} + +func createRequest(configPath string, serviceType models.ServiceType) (*agentpb.ParseDefaultsFileRequest, error) { + if serviceType == models.MySQLServiceType { + request := &agentpb.ParseDefaultsFileRequest{ + ServiceType: inventorypb.ServiceType_MYSQL_SERVICE, + ConfigPath: configPath, + } + return request, nil + } else { + return nil, errors.Errorf("unhandled service type %s", serviceType) + } +} diff --git a/services/agents/parse_defaults_file_test.go b/services/agents/parse_defaults_file_test.go new file mode 100644 index 0000000000..0f2e241479 --- /dev/null +++ b/services/agents/parse_defaults_file_test.go @@ -0,0 +1,41 @@ +// pmm-managed +// Copyright (C) 2017 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package agents + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/percona/pmm-managed/models" +) + +func TestCreateRequest(t *testing.T) { + t.Parallel() + response, err := createRequest("/path/to/file", models.MySQLServiceType) + + require.NoError(t, err) + require.NotNil(t, response, "ParseDefaultsFileRequest is nil") +} + +func TestCreateRequestNotSupported(t *testing.T) { + t.Parallel() + response, err := createRequest("/path/to/file", models.PostgreSQLServiceType) + + require.Error(t, err) + require.Nil(t, response, "ParseDefaultsFileRequest is not nil") +} diff --git a/services/management/deps.go b/services/management/deps.go index efa1991791..832341d41e 100644 --- a/services/management/deps.go +++ b/services/management/deps.go @@ -34,6 +34,8 @@ import ( //go:generate mockery -name=grafanaClient -case=snake -inpkg -testonly //go:generate mockery -name=jobsService -case=snake -inpkg -testonly //go:generate mockery -name=connectionChecker -case=snake -inpkg -testonly +//go:generate mockery -name=defaultsFileParser -case=snake -inpkg -testonly +//go:generate mockery -name=versionCache -case=snake -inpkg -testonly // agentsRegistry is a subset of methods of agents.Registry used by this package. // We use it instead of real type for testing and to avoid dependency cycle. @@ -93,3 +95,9 @@ type connectionChecker interface { type versionCache interface { RequestSoftwareVersionsUpdate() } + +// defaultsFileParser is a subset of methods of agents.ParseDefaultsFile. +// We use it instead of real type for testing and to avoid dependency cycle. +type defaultsFileParser interface { + ParseDefaultsFile(ctx context.Context, pmmAgentID, filePath string, serviceType models.ServiceType) (*models.ParseDefaultsFileResult, error) +} diff --git a/services/management/mock_defaults_file_parser_test.go b/services/management/mock_defaults_file_parser_test.go new file mode 100644 index 0000000000..04cf1b3e21 --- /dev/null +++ b/services/management/mock_defaults_file_parser_test.go @@ -0,0 +1,39 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package management + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + models "github.com/percona/pmm-managed/models" +) + +// mockDefaultsFileParser is an autogenerated mock type for the defaultsFileParser type +type mockDefaultsFileParser struct { + mock.Mock +} + +// ParseDefaultsFile provides a mock function with given fields: ctx, pmmAgentID, filePath, serviceType +func (_m *mockDefaultsFileParser) ParseDefaultsFile(ctx context.Context, pmmAgentID string, filePath string, serviceType models.ServiceType) (*models.ParseDefaultsFileResult, error) { + ret := _m.Called(ctx, pmmAgentID, filePath, serviceType) + + var r0 *models.ParseDefaultsFileResult + if rf, ok := ret.Get(0).(func(context.Context, string, string, models.ServiceType) *models.ParseDefaultsFileResult); ok { + r0 = rf(ctx, pmmAgentID, filePath, serviceType) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.ParseDefaultsFileResult) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, string, models.ServiceType) error); ok { + r1 = rf(ctx, pmmAgentID, filePath, serviceType) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/services/management/mock_version_cache_test.go b/services/management/mock_version_cache_test.go new file mode 100644 index 0000000000..063a059b57 --- /dev/null +++ b/services/management/mock_version_cache_test.go @@ -0,0 +1,15 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package management + +import mock "github.com/stretchr/testify/mock" + +// mockVersionCache is an autogenerated mock type for the versionCache type +type mockVersionCache struct { + mock.Mock +} + +// RequestSoftwareVersionsUpdate provides a mock function with given fields: +func (_m *mockVersionCache) RequestSoftwareVersionsUpdate() { + _m.Called() +} diff --git a/services/management/mysql.go b/services/management/mysql.go index ddf6723d96..efa8e2de9b 100644 --- a/services/management/mysql.go +++ b/services/management/mysql.go @@ -18,10 +18,13 @@ package management import ( "context" + "fmt" "github.com/AlekSi/pointer" "github.com/percona/pmm/api/inventorypb" "github.com/percona/pmm/api/managementpb" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "gopkg.in/reform.v1" "github.com/percona/pmm-managed/models" @@ -39,15 +42,17 @@ type MySQLService struct { state agentsStateUpdater cc connectionChecker vc versionCache + dfp defaultsFileParser } // NewMySQLService creates new MySQL Management Service. -func NewMySQLService(db *reform.DB, state agentsStateUpdater, cc connectionChecker, vc versionCache) *MySQLService { +func NewMySQLService(db *reform.DB, state agentsStateUpdater, cc connectionChecker, vc versionCache, dfp defaultsFileParser) *MySQLService { return &MySQLService{ db: db, state: state, cc: cc, vc: vc, + dfp: dfp, } } @@ -78,6 +83,35 @@ func (s *MySQLService) Add(ctx context.Context, req *managementpb.AddMySQLReques if err != nil { return err } + + if req.DefaultsFile != "" { + result, err := s.dfp.ParseDefaultsFile(ctx, req.PmmAgentId, req.DefaultsFile, models.MySQLServiceType) + if err != nil { + return status.Error(codes.FailedPrecondition, fmt.Sprintf("Defaults file error: %s.", err)) + } + + // set username and password from parsed defaults file by agent + if req.Username == "" && result.Username != "" { + req.Username = result.Username + } + + if req.Password == "" && result.Password != "" { + req.Password = result.Password + } + + if req.Address == "" && result.Host != "" { + req.Address = result.Host + } + + if req.Port == 0 && result.Port > 0 { + req.Port = result.Port + } + + if req.Socket == "" && result.Socket != "" { + req.Socket = result.Socket + } + } + service, err := models.AddNewService(tx.Querier, models.MySQLServiceType, &models.AddDBMSServiceParams{ ServiceName: req.ServiceName, NodeID: nodeID, diff --git a/services/management/mysql_test.go b/services/management/mysql_test.go new file mode 100644 index 0000000000..c27d145bd6 --- /dev/null +++ b/services/management/mysql_test.go @@ -0,0 +1,152 @@ +// pmm-managed +// Copyright (C) 2017 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package management + +import ( + "context" + "testing" + + "github.com/google/uuid" + "github.com/percona/pmm/api/managementpb" + "github.com/pkg/errors" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "gopkg.in/reform.v1" + "gopkg.in/reform.v1/dialects/postgresql" + + "github.com/percona/pmm-managed/models" + "github.com/percona/pmm-managed/utils/logger" + "github.com/percona/pmm-managed/utils/testdb" + "github.com/percona/pmm-managed/utils/tests" +) + +func setup(t *testing.T) (*MySQLService, func(t *testing.T), context.Context) { + t.Helper() + + uuid.SetRand(&tests.IDReader{}) + + sqlDB := testdb.Open(t, models.SetupFixtures, nil) + db := reform.NewDB(sqlDB, postgresql.Dialect, reform.NewPrintfLogger(t.Logf)) + + agentsRegistry := &mockAgentsRegistry{} + agentsRegistry.Test(t) + + agentsStateUpdater := &mockAgentsStateUpdater{} + agentsStateUpdater.Test(t) + + connectionChecker := &mockConnectionChecker{} + connectionChecker.Test(t) + + defaultsFileParser := &mockDefaultsFileParser{} + defaultsFileParser.Test(t) + + versionCache := &mockVersionCache{} + versionCache.Test(t) + + teardown := func(t *testing.T) { + uuid.SetRand(nil) + + require.NoError(t, sqlDB.Close()) + + agentsRegistry.AssertExpectations(t) + agentsStateUpdater.AssertExpectations(t) + connectionChecker.AssertExpectations(t) + defaultsFileParser.AssertExpectations(t) + versionCache.AssertExpectations(t) + } + + return NewMySQLService(db, agentsStateUpdater, connectionChecker, versionCache, defaultsFileParser), + teardown, + logger.Set(context.Background(), t.Name()) +} + +func TestDefaultsFileParserRequest(t *testing.T) { + t.Run("Add MySQLService defaults file test", func(t *testing.T) { + service, teardown, ctx := setup(t) + defer teardown(t) + + service.state.(*mockAgentsStateUpdater).On("RequestStateUpdate", ctx, "pmm-server").Once() + service.vc.(*mockVersionCache).On("RequestSoftwareVersionsUpdate").Once() + service.dfp.(*mockDefaultsFileParser).On("ParseDefaultsFile", ctx, mock.Anything, "/file/path", models.MySQLServiceType).Return(&models.ParseDefaultsFileResult{ + Username: "test", + Password: "test", + Host: "192.168.2.1", + Port: 6666, + }, nil).Once() + + req := &managementpb.AddMySQLRequest{ + NodeId: models.PMMServerNodeID, + PmmAgentId: models.PMMServerAgentID, + ServiceName: "test", + DefaultsFile: "/file/path", + SkipConnectionCheck: true, + } + res, err := service.Add(ctx, req) + require.NoError(t, err) + require.NotNil(t, res) + + require.Equal(t, res.Service.Address, "192.168.2.1") + require.Equal(t, res.Service.Port, uint32(6666)) + require.Equal(t, res.MysqldExporter.Username, "test") + }) + + t.Run("Add MySQLService defaults file with overriden params", func(t *testing.T) { + service, teardown, ctx := setup(t) + defer teardown(t) + + service.state.(*mockAgentsStateUpdater).On("RequestStateUpdate", ctx, "pmm-server").Once() + service.vc.(*mockVersionCache).On("RequestSoftwareVersionsUpdate").Once() + service.dfp.(*mockDefaultsFileParser).On("ParseDefaultsFile", ctx, mock.Anything, "/file/path", models.MySQLServiceType).Return(&models.ParseDefaultsFileResult{ + Username: "test", + Socket: "socks4://localhost", + }, nil).Once() + + req := &managementpb.AddMySQLRequest{ + NodeId: models.PMMServerNodeID, + PmmAgentId: models.PMMServerAgentID, + ServiceName: "test overriden", + DefaultsFile: "/file/path", + Username: "overriden", + SkipConnectionCheck: true, + } + res, err := service.Add(ctx, req) + require.NoError(t, err) + require.NotNil(t, res) + + require.Equal(t, res.Service.Socket, "socks4://localhost") + require.Equal(t, res.MysqldExporter.Username, "overriden") + }) + + t.Run("Add MySQLService defaults file parse error", func(t *testing.T) { + service, teardown, ctx := setup(t) + defer teardown(t) + + service.dfp.(*mockDefaultsFileParser).On("ParseDefaultsFile", ctx, mock.Anything, "/file/path", models.MySQLServiceType).Return(nil, errors.New("dfp error")).Once() + + req := &managementpb.AddMySQLRequest{ + NodeId: models.PMMServerNodeID, + PmmAgentId: models.PMMServerAgentID, + ServiceName: "not used", + DefaultsFile: "/file/path", + Username: "overriden", + SkipConnectionCheck: true, + } + res, err := service.Add(ctx, req) + require.Error(t, err, "dfp error") + require.Nil(t, res) + }) +}