Skip to content

Commit

Permalink
PMM-12375 refactore to use a separate method for version and tableCount
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex Tymchuk committed Sep 12, 2023
1 parent 81aa208 commit cb1b200
Show file tree
Hide file tree
Showing 16 changed files with 1,817 additions and 902 deletions.
82 changes: 52 additions & 30 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,43 +1,65 @@
# Host Makefile.
# Devcontainer Makefile.

include Makefile.include

ifeq ($(PROFILES),)
PROFILES := 'pmm'
endif
release-dev-managed: ## Build pmm-managed
make -C managed release-dev

env-up: ## Start devcontainer
COMPOSE_PROFILES=$(PROFILES) \
docker compose up -d
release-dev-agent: ## Build pmm-agent
make -C agent release-dev

env-up-rebuild: env-update-image ## Rebuild and start devcontainer. Useful for custom $PMM_SERVER_IMAGE
COMPOSE_PROFILES=$(PROFILES) \
docker compose up --build -d
release-vmproxy: ## Build vmproxy
make -C vmproxy release

env-update-image: ## Pull latest dev image
COMPOSE_PROFILES=$(PROFILES) \
docker compose pull
# used by host Makefile
_bash:
/bin/bash

env-compose-up: env-update-image
COMPOSE_PROFILES=$(PROFILES) \
docker compose up --detach --renew-anon-volumes --remove-orphans
PMM_RELEASE_PATH ?= ./bin

env-devcontainer:
docker exec -it --workdir=/root/go/src/github.com/percona/pmm pmm-server .devcontainer/setup.py
run-managed: release-dev-managed ## Replace pmm-managed from build, restart and tail logs
supervisorctl stop pmm-managed
cp $(PMM_RELEASE_PATH)/pmm-managed /usr/sbin/pmm-managed
supervisorctl start pmm-managed &
supervisorctl tail -f pmm-managed

env-down: ## Stop devcontainer
COMPOSE_PROFILES=$(PROFILES) \
docker compose down --remove-orphans
run-managed-ci: release-dev-managed ## Replace pmm-managed from build, restart (used in CI)
supervisorctl stop pmm-managed
cp $(PMM_RELEASE_PATH)/pmm-managed /usr/sbin/pmm-managed
supervisorctl start pmm-managed

env-remove:
COMPOSE_PROFILES=$(PROFILES) \
docker compose down --volumes --remove-orphans
run-agent: release-dev-agent ## Replace pmm-agent from build and restart
supervisorctl stop pmm-agent
cp $(PMM_RELEASE_PATH)/pmm-agent /usr/sbin/pmm-agent
supervisorctl start pmm-agent

TARGET ?= _bash
run-vmproxy: release-vmproxy
supervisorctl stop vmproxy
cp $(PMM_RELEASE_PATH)/vmproxy /usr/sbin/vmproxy
supervisorctl start vmproxy

env: ## Run `make TARGET` in devcontainer (`make env TARGET=help`); TARGET defaults to bash
COMPOSE_PROFILES=$(PROFILES) \
docker exec -it --workdir=/root/go/src/github.com/percona/pmm pmm-server make $(TARGET)
run-all: run-agent run-managed ## Run pmm-managed and pmm-agent

update-dbaas-catalog: ## Update the DBaaS catalog from the latest production branch (percona-platform).
wget https://raw.githubusercontent.com/percona/dbaas-catalog/percona-platform/percona-dbaas-catalog.yaml -O managed/data/crds/olm/percona-dbaas-catalog.yaml
run: ## Deprecated
echo "Deprecated: please use run-all"

# TODO https://jira.percona.com/browse/PMM-3484, see maincover_test.go
# run-race-cover: install-race ## Run pmm-managed with race detector and collect coverage information.
# go test -coverpkg="github.com/percona/pmm/managed/..." \
# -tags maincover \
# $(PMM_LD_FLAGS) \
# -race -c -o bin/pmm-managed.test
# bin/pmm-managed.test -test.coverprofile=cover.out -test.run=TestMainCover $(RUN_FLAGS)

psql: ## Open database for the pmm-managed instance in psql shell
env PGPASSWORD=pmm-managed psql -U pmm-managed pmm-managed

psql-test: ## Open database used in unit tests in psql shell
env psql -U postgres pmm-managed-dev

dlv/attach: ## Attach Delve to `pmm-managed`
dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient attach $(shell pgrep pmm-managed)

refresh-swagger: ## Refresh swagger files
cp /root/go/src/github.com/percona/pmm/api/swagger/swagger.json /usr/share/pmm-managed/swagger/swagger.json
cp /root/go/src/github.com/percona/pmm/api/swagger/swagger-dev.json /usr/share/pmm-managed/swagger/swagger-dev.json
3 changes: 3 additions & 0 deletions agent/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,9 @@ loop:
case *agentpb.CheckConnectionRequest:
responsePayload = c.connectionChecker.Check(ctx, p, req.ID)

case *agentpb.ServiceInfoRequest:
responsePayload = c.connectionChecker.GetServiceInfo(ctx, p, req.ID)

case *agentpb.StartJobRequest:
var resp agentpb.StartJobResponse
if err := c.handleStartJobRequest(p); err != nil {
Expand Down
1 change: 1 addition & 0 deletions agent/client/deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
// We use it instead of real type for testing and to avoid dependency cycle.
type connectionChecker interface {
Check(ctx context.Context, req *agentpb.CheckConnectionRequest, id uint32) *agentpb.CheckConnectionResponse
GetServiceInfo(ctx context.Context, req *agentpb.ServiceInfoRequest, id uint32) *agentpb.ServiceInfoResponse
}

// softwareVersioner is a subset of methods of version.Versioner used by this package.
Expand Down
16 changes: 16 additions & 0 deletions agent/client/mock_connection_checker_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

223 changes: 223 additions & 0 deletions agent/connectionchecker/connection_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,3 +339,226 @@ func (cc *ConnectionChecker) checkExternalConnection(ctx context.Context, uri st

return &res
}

// GetServiceInfo gathers information from a service. It returns context cancelation/timeout or driver errors as is.
func (cc *ConnectionChecker) GetServiceInfo(ctx context.Context, msg *agentpb.ServiceInfoRequest, id uint32) *agentpb.ServiceInfoResponse {
timeout := msg.Timeout.AsDuration()
if timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, timeout)
defer cancel()
}

switch msg.Type {
case inventorypb.ServiceType_MYSQL_SERVICE:
return cc.getMySQLInfo(ctx, msg.Dsn, msg.TextFiles, msg.TlsSkipVerify, id)
case inventorypb.ServiceType_MONGODB_SERVICE:
return cc.getMongoDBInfo(ctx, msg.Dsn, msg.TextFiles, id)
case inventorypb.ServiceType_POSTGRESQL_SERVICE:
return cc.getPostgreSQLInfo(ctx, msg.Dsn, msg.TextFiles, id)
case inventorypb.ServiceType_PROXYSQL_SERVICE:
return cc.getProxySQLInfo(ctx, msg.Dsn)
// NOTE: inventorypb.ServiceType_EXTERNAL_SERVICE, inventorypb.ServiceType_HAPROXY_SERVICE can't be implemented for now
default:
panic(fmt.Sprintf("unknown service type: %v", msg.Type))
}
}

func (cc *ConnectionChecker) getMySQLInfo(ctx context.Context, dsn string, files *agentpb.TextFiles, tlsSkipVerify bool, id uint32) *agentpb.ServiceInfoResponse { //nolint:lll,unparam
var res agentpb.ServiceInfoResponse
var err error

if files != nil {
err = tlshelpers.RegisterMySQLCerts(files.Files)
if err != nil {
cc.l.Debugf("getMySQLInfo: failed to register cert: %s", err)
res.Error = err.Error()
return &res
}
}

cfg, err := mysql.ParseDSN(dsn)
if err != nil {
cc.l.Debugf("getMySQLInfo: failed to parse DSN: %s", err)
res.Error = err.Error()
return &res
}

tempdir := filepath.Join(cc.cfg.Get().Paths.TempDir, strings.ToLower("get-mysql-info"), strconv.Itoa(int(id)))
_, err = templates.RenderDSN(dsn, files, tempdir)
if err != nil {
cc.l.Debugf("getMySQLInfo: failed to Render DSN: %s", err)
res.Error = err.Error()
return &res
}

connector, err := mysql.NewConnector(cfg)
if err != nil {
cc.l.Debugf("getMySQLInfo: failed to create connector: %s", err)
res.Error = err.Error()
return &res
}

db := sql.OpenDB(connector)
defer db.Close() //nolint:errcheck

if err = cc.sqlPing(ctx, db); err != nil {
if errors.As(err, &x509.HostnameError{}) {
res.Error = errors.Wrap(err,
"mysql ssl certificate is misconfigured, make sure the certificate includes the requested hostname/IP in CN or subjectAltName fields").Error()
} else {
res.Error = err.Error()
}
return &res
}

var count uint64
if err = db.QueryRowContext(ctx, "SELECT /* agent='connectionchecker' */ COUNT(*) FROM information_schema.tables").Scan(&count); err != nil {
res.Error = err.Error()
return &res
}

tableCount := int32(count)
if count > math.MaxInt32 {
tableCount = math.MaxInt32
}

var version string
if err = db.QueryRowContext(ctx, "SELECT /* agent='connectionchecker' */ VERSION()").Scan(&version); err != nil {
res.Error = err.Error()
return &res
}

res.TableCount = tableCount
res.Version = version

return &res
}

func (cc *ConnectionChecker) getMongoDBInfo(ctx context.Context, dsn string, files *agentpb.TextFiles, id uint32) *agentpb.ServiceInfoResponse {
var res agentpb.ServiceInfoResponse
var err error

tempdir := filepath.Join(cc.cfg.Get().Paths.TempDir, strings.ToLower("get-mongodb-info"), strconv.Itoa(int(id)))
dsn, err = templates.RenderDSN(dsn, files, tempdir)
if err != nil {
cc.l.Debugf("getMongoDBInfo: failed to Render DSN: %s", err)
res.Error = err.Error()
return &res
}

opts, err := mongo_fix.ClientOptionsForDSN(dsn)
if err != nil {
cc.l.Debugf("failed to parse DSN: %s", err)
res.Error = err.Error()
return &res
}

client, err := mongo.Connect(ctx, opts)
if err != nil {
cc.l.Debugf("getMongoDBInfo: failed to Connect: %s", err)
res.Error = err.Error()
return &res
}
defer client.Disconnect(ctx) //nolint:errcheck

if err = client.Ping(ctx, nil); err != nil {
cc.l.Debugf("getMongoDBInfo: failed to Ping: %s", err)
res.Error = err.Error()
return &res
}

resp := client.Database("admin").RunCommand(ctx, bson.D{{Key: "getDiagnosticData", Value: 1}})
if err = resp.Err(); err != nil {
cc.l.Debugf("getMongoDBInfo: failed to runCommand getDiagnosticData: %s", err)
res.Error = err.Error()
return &res
}

resp = client.Database("admin").RunCommand(ctx, bson.D{{Key: "buildInfo", Value: 1}})
if err = resp.Err(); err != nil {
res.Error = err.Error()
return &res
}

buildInfo := struct {
Version string `bson:"version"`
}{}

if err = resp.Decode(&buildInfo); err != nil {
cc.l.Debugf("getMongoDBInfo: failed to decode buildInfo: %s", err)
return &res
}

res.Version = buildInfo.Version

return &res
}

func (cc *ConnectionChecker) getPostgreSQLInfo(ctx context.Context, dsn string, files *agentpb.TextFiles, id uint32) *agentpb.ServiceInfoResponse {
var res agentpb.ServiceInfoResponse
var err error

tempdir := filepath.Join(cc.cfg.Get().Paths.TempDir, strings.ToLower("get-postgresql-info"), strconv.Itoa(int(id)))
dsn, err = templates.RenderDSN(dsn, files, tempdir)
if err != nil {
cc.l.Debugf("getPostgreSQLInfo: failed to Render DSN: %s", err)
res.Error = err.Error()
return &res
}

c, err := pq.NewConnector(dsn)
if err != nil {
res.Error = err.Error()
return &res
}
db := sql.OpenDB(c)
defer db.Close() //nolint:errcheck

if err = cc.sqlPing(ctx, db); err != nil {
res.Error = err.Error()
}

var version string
if err = db.QueryRowContext(ctx, "SHOW /* agent='connectionchecker' */ SERVER_VERSION").Scan(&version); err != nil {
res.Error = err.Error()
return &res
}

res.Version = version

return &res
}

func (cc *ConnectionChecker) getProxySQLInfo(ctx context.Context, dsn string) *agentpb.ServiceInfoResponse {
var res agentpb.ServiceInfoResponse

cfg, err := mysql.ParseDSN(dsn)
if err != nil {
res.Error = err.Error()
return &res
}

connector, err := mysql.NewConnector(cfg)
if err != nil {
res.Error = err.Error()
return &res
}

db := sql.OpenDB(connector)
defer db.Close() //nolint:errcheck

if err = cc.sqlPing(ctx, db); err != nil {
res.Error = err.Error()
}

var version string
if err := db.QueryRowContext(ctx, "SELECT /* agent='connectionchecker' */ @@GLOBAL.'admin-version'").Scan(&version); err != nil {
res.Error = err.Error()
return &res
}

res.Version = version

return &res
}
Loading

0 comments on commit cb1b200

Please sign in to comment.