mirror of
https://github.com/percona/percona-toolkit.git
synced 2026-05-08 03:06:21 +08:00
Merge branch '3.x' into PT-2448-pt-k8s-debug-collector-refactoring
This commit is contained in:
@@ -27,7 +27,7 @@ jobs:
|
||||
- name: Build the Docker image
|
||||
run: echo "FROM oraclelinux:9-slim" > Dockerfile; echo "RUN microdnf -y update" >> Dockerfile; echo "COPY bin/* /usr/bin/" >> Dockerfile; docker build . --file Dockerfile --tag percona-toolkit:${{ github.sha }}
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@0.33.1
|
||||
uses: aquasecurity/trivy-action@0.34.0
|
||||
with:
|
||||
image-ref: 'percona-toolkit:${{ github.sha }}'
|
||||
format: 'table'
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
vuln-type: 'os,library'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v6.0.0
|
||||
uses: actions/upload-artifact@v7.0.0
|
||||
with:
|
||||
name: binaries
|
||||
path: bin/*
|
||||
|
||||
@@ -62,6 +62,7 @@ extend-ignore-re = [
|
||||
"RegexISTReceiver" = "RegexISTReceiver"
|
||||
"RegexISTSender" = "RegexISTSender"
|
||||
"RegexXtrabackupISTReceived" = "RegexXtrabackupISTReceived"
|
||||
"SELECTs" = "SELECTs"
|
||||
"START_ND_SUMMARY" = "START_ND_SUMMARY"
|
||||
"START_ND_TOOLTIPS" = "START_ND_TOOLTIPS"
|
||||
"thr" = "thr" # common abbreviation
|
||||
|
||||
@@ -105,7 +105,8 @@ func main() {
|
||||
resp.Unused = findUnused(ctx, client, args.Databases, args.Collections)
|
||||
resp.Duplicated = findDuplicated(ctx, client, args.Databases, args.Collections)
|
||||
default:
|
||||
kong.DefaultHelpPrinter(kong.HelpOptions{}, kongctx)
|
||||
kongctx.PrintUsage(false)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(output(resp, args.JSON))
|
||||
|
||||
@@ -16,6 +16,7 @@ package main
|
||||
import (
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -33,3 +34,16 @@ func TestVersionOption(t *testing.T) {
|
||||
t.Errorf("%s --version returns wrong result:\n%s", toolname, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoCommand(t *testing.T) {
|
||||
mockMongo := "mongodb://127.0.0.1:27017"
|
||||
out, err := exec.Command("../../../bin/"+toolname, "--mongodb.uri", mockMongo).Output()
|
||||
if err != nil {
|
||||
t.Errorf("error executing %s with no command: %s", toolname, err.Error())
|
||||
}
|
||||
|
||||
want := "Usage: pt-mongodb-index-check show-help"
|
||||
if !strings.Contains(string(out), want) {
|
||||
t.Errorf("Output missmatch. Output %q should contain %q", string(out), want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,12 @@ Output example
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
# Mongos #################################################################################################
|
||||
Host LastPing Version Uptime (sec)
|
||||
my-cluster-name-mongos-0:27017 2026-02-16T13:01:22Z 8.0.17-6 3553
|
||||
my-cluster-name-mongos-1:27017 2026-02-16T13:01:26Z 8.0.17-6 3543
|
||||
my-cluster-name-mongos-2:27017 2026-02-16T13:01:28Z 8.0.17-6 3533
|
||||
|
||||
# Instances ####################################################################################
|
||||
ID Host Type ReplSet
|
||||
0 localhost:17001 PRIMARY r1
|
||||
|
||||
@@ -15,6 +15,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
@@ -26,6 +27,7 @@ import (
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -35,6 +37,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shirou/gopsutil/process"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
@@ -159,6 +162,29 @@ type clusterwideInfo struct {
|
||||
Chunks []proto.ChunksByCollection
|
||||
}
|
||||
|
||||
type mongosInstance struct {
|
||||
Name string `bson:"_id"`
|
||||
LastPing time.Time `bson:"ping"`
|
||||
UpTime int `bson:"up"`
|
||||
Version string `bson:"mongoVersion"`
|
||||
}
|
||||
|
||||
type mongosInfo struct {
|
||||
Instances []mongosInstance `bson:"instances"`
|
||||
}
|
||||
|
||||
func (t mongosInfo) MaxNameLen() int {
|
||||
if len(t.Instances) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
maxInst := slices.MaxFunc(t.Instances, func(a, b mongosInstance) int {
|
||||
return cmp.Compare(len(a.Name), len(b.Name))
|
||||
})
|
||||
|
||||
return len(maxInst.Name)
|
||||
}
|
||||
|
||||
type cliOptions struct {
|
||||
Host string
|
||||
User string
|
||||
@@ -184,6 +210,7 @@ type collectedInfo struct {
|
||||
RunningOps *opCounters
|
||||
SecuritySettings *security
|
||||
HostInfo *hostInfo
|
||||
MongosInfo *mongosInfo
|
||||
Errors []string
|
||||
}
|
||||
|
||||
@@ -256,6 +283,11 @@ func main() {
|
||||
|
||||
ci := &collectedInfo{}
|
||||
|
||||
ci.MongosInfo, err = getMongosInfo(ctx, client)
|
||||
if err != nil {
|
||||
log.Warnf("[Warning] cannot get mongos info: %v\n", err)
|
||||
}
|
||||
|
||||
ci.HostInfo, err = getHostInfo(ctx, client)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot get host info for %q: %s", opts.Host, err)
|
||||
@@ -263,7 +295,7 @@ func main() {
|
||||
}
|
||||
|
||||
if ci.ReplicaMembers, err = util.GetReplicasetMembers(ctx, clientOptions); err != nil {
|
||||
log.Warnf("[Error] cannot get replicaset members: %v\n", err)
|
||||
log.Warnf("[Warning] cannot get replicaset members: %v\n", err)
|
||||
}
|
||||
|
||||
log.Debugf("replicaMembers:\n%+v\n", ci.ReplicaMembers)
|
||||
@@ -332,7 +364,12 @@ func formatResults(ci *collectedInfo, format string) ([]byte, error) {
|
||||
default:
|
||||
buf = new(bytes.Buffer)
|
||||
|
||||
t := template.Must(template.New("replicas").Parse(templates.Replicas))
|
||||
t := template.Must(template.New("mongos").Parse(templates.MongosInfo))
|
||||
if err := t.Execute(buf, ci.MongosInfo); err != nil {
|
||||
return nil, errors.Wrap(err, "cannot parse mongos section of the output template")
|
||||
}
|
||||
|
||||
t = template.Must(template.New("replicas").Parse(templates.Replicas))
|
||||
if err := t.Execute(buf, ci.ReplicaMembers); err != nil {
|
||||
return nil, errors.Wrap(err, "cannot parse replicas section of the output template")
|
||||
}
|
||||
@@ -454,6 +491,29 @@ func countMongodProcesses() (int, error) {
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func getMongosInfo(ctx context.Context, client *mongo.Client) (*mongosInfo, error) {
|
||||
threshold := time.Now().Add(-300 * time.Second)
|
||||
|
||||
filter := bson.M{
|
||||
"ping": bson.M{
|
||||
"$gt": threshold,
|
||||
},
|
||||
}
|
||||
|
||||
cursor, err := client.Database("config").Collection("mongos").Find(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find mongos: %w", err)
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
var instances []mongosInstance
|
||||
if err := cursor.All(ctx, &instances); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode mongos: %w", err)
|
||||
}
|
||||
|
||||
return &mongosInfo{Instances: instances}, nil
|
||||
}
|
||||
|
||||
func getClusterwideInfo(ctx context.Context, client *mongo.Client) (*clusterwideInfo, error) {
|
||||
var databases databases
|
||||
|
||||
|
||||
@@ -151,3 +151,23 @@ func TestParseArgs(t *testing.T) {
|
||||
|
||||
os.Stdout = old
|
||||
}
|
||||
|
||||
func TestGetMongosInfo(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
client, err := tu.TestClient(ctx, tu.MongoDBMongosPort)
|
||||
require.NoError(t, err)
|
||||
|
||||
info, err := getMongosInfo(ctx, client)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, info)
|
||||
require.NotEmpty(t, info.Instances)
|
||||
|
||||
for _, m := range info.Instances {
|
||||
require.NotEmpty(t, m.Name)
|
||||
require.NotEmpty(t, m.Version)
|
||||
require.NotEqual(t, 0, m.UpTime)
|
||||
require.False(t, m.LastPing.IsZero())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// This program is copyright 2016-2026 Percona LLC and/or its affiliates.
|
||||
//
|
||||
// THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
|
||||
// WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify it under
|
||||
// the terms of the GNU General Public License as published by the Free Software
|
||||
// Foundation, version 2.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License, version 2
|
||||
// along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package templates
|
||||
|
||||
const MongosInfo = `
|
||||
# Mongos #################################################################################################
|
||||
{{ "" }}
|
||||
{{- $padding := " " -}}
|
||||
{{- $timeWidth := 20 -}}
|
||||
{{- $hostWidth := .MaxNameLen -}}
|
||||
{{- $versionWidth := 15 -}}
|
||||
|
||||
{{ printf "%-*s" $hostWidth "Host" }}{{ $padding }}
|
||||
{{- printf "%-*s" $timeWidth "LastPing" }}{{ $padding }}
|
||||
{{- printf "%-*s" $versionWidth "Version" }}{{ $padding }}Uptime (sec)
|
||||
{{ if .Instances -}}
|
||||
{{- range .Instances -}}
|
||||
{{ printf "%-*s" $hostWidth .Name }}{{ $padding }}
|
||||
{{- printf "%-*s" $timeWidth (.LastPing.Format "2006-01-02T15:04:05Z07:00") }}{{ $padding }}
|
||||
{{- printf "%-*s" $versionWidth .Version }}{{ $padding }}
|
||||
{{- printf "%-15d" .UpTime }}
|
||||
{{ end }}
|
||||
{{- else -}}
|
||||
no mongos instances found
|
||||
{{- end }}
|
||||
`
|
||||
Reference in New Issue
Block a user