Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve efficiency and accuracy of mysqld.GetVersionString #15096

Merged
merged 13 commits into from
Feb 1, 2024
18 changes: 13 additions & 5 deletions go/vt/env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"path"
"path/filepath"
"strings"
"sync"
)

const (
Expand All @@ -33,6 +34,9 @@ const (
DefaultVtRoot = "/usr/local/vitess"
)

// Used to ensure that we only add /usr/sbin to the PATH once.
var once sync.Once

// VtRoot returns $VTROOT or tries to guess its value if it's not set.
// This is the root for the 'vt' distribution, which contains bin/vttablet
// for instance.
Expand Down Expand Up @@ -72,11 +76,15 @@ func VtMysqlRoot() (string, error) {
return root, nil
}

// otherwise let's look for mysqld in the PATH.
// ensure that /usr/sbin is included, as it might not be by default
// This is the default location for mysqld from packages.
newPath := fmt.Sprintf("/usr/sbin:%s", os.Getenv("PATH"))
os.Setenv("PATH", newPath)
// Otherwise let's look for mysqld in the PATH env var.
// Ensure that /usr/sbin is included, as it might not be by default
// and this is often the default location used by mysqld system
// packages (apt, dnf, etc).
once.Do(func() {
newPath := fmt.Sprintf("/usr/sbin:%s", os.Getenv("PATH"))
mattlord marked this conversation as resolved.
Show resolved Hide resolved
os.Setenv("PATH", newPath)
})

path, err := exec.LookPath("mysqld")
if err != nil {
return "", errors.New("VT_MYSQL_ROOT is not set and no mysqld could be found in your PATH")
Expand Down
43 changes: 43 additions & 0 deletions go/vt/env/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package env
import (
"os"
"testing"

"github.com/stretchr/testify/require"
)

func TestVtDataRoot(t *testing.T) {
Expand All @@ -43,3 +45,44 @@ func TestVtDataRoot(t *testing.T) {
t.Errorf("The value of VtDataRoot should be %v, not %v.", passed, root)
}
}

func TestVtMysqlRoot(t *testing.T) {
envVar := "VT_MYSQL_ROOT"
originalMySQLRoot := os.Getenv(envVar)
defer os.Setenv(envVar, originalMySQLRoot)
originalPATH := os.Getenv("PATH")
defer os.Setenv("PATH", originalPATH)

testcases := []struct {
name string
envVal string
}{
{
name: "env var set",
envVal: "/home/mysql/binaries",
},
{
name: "env var unset",
},
{ // Second call allows us to verify that we're not adding to the PATH multiple times.
name: "env var unset again",
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
os.Setenv(envVar, tc.envVal)
defer os.Setenv(envVar, "")

path, err := VtMysqlRoot()
if tc.envVal != "" {
require.Equal(t, tc.envVal, path)
require.NoError(t, err)
}
// We don't require a non-error as the test env may not have mysql installed.
})
}

// After all of the test runs, the PATH should only have had /usr/sbin added once.
require.Equal(t, "/usr/sbin:"+originalPATH, os.Getenv("PATH"))
}
28 changes: 22 additions & 6 deletions go/vt/mysqlctl/mysqld.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,14 @@
"vitess.io/vitess/go/vt/proto/vtrpc"
)

var (
// The string we expect before the MySQL version number
// in strings containing MySQL version information.
const versionStringPrefix = "Ver "

// How many bytes from MySQL error log to sample for error messages
const maxLogFileSampleSize = 4096

var (
// DisableActiveReparents is a flag to disable active
// reparents for safety reasons. It is used in three places:
// 1. in this file to skip registering the commands.
Expand All @@ -87,15 +93,15 @@

replicationConnectRetry = 10 * time.Second

versionRegex = regexp.MustCompile(`Ver ([0-9]+)\.([0-9]+)\.([0-9]+)`)
versionRegex = regexp.MustCompile(fmt.Sprintf(`%s([0-9]+)\.([0-9]+)\.([0-9]+)`, versionStringPrefix))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure there's a good reason you changed this, but it doesn't feel necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just so that we're using the same const in the regex and where we build the string to match it when querying mysqld.

// The SQL query that will return a version string directly from
// a MySQL server that will comply with the versionRegex.
versionSQLQuery = fmt.Sprintf("select concat('%s', @@global.version, ' ', @@global.version_comment) as version", versionStringPrefix)

binlogEntryCommittedTimestampRegex = regexp.MustCompile("original_committed_timestamp=([0-9]+)")
binlogEntryTimestampGTIDRegexp = regexp.MustCompile(`^#(.+) server id.*\bGTID\b`)
)

// How many bytes from MySQL error log to sample for error messages
const maxLogFileSampleSize = 4096

// Mysqld is the object that represents a mysqld daemon running on this server.
type Mysqld struct {
dbcfgs *dbconfigs.DBConfigs
Expand Down Expand Up @@ -1186,7 +1192,16 @@

// GetVersionString is part of the MysqlExecutor interface.
func (mysqld *Mysqld) GetVersionString(ctx context.Context) (string, error) {
// Execute as remote action on mysqlctld to ensure we get the actual running MySQL version.
// Try to query the mysqld instance directly.
// This query produces output that is compatible with what we expect from
// mysqld --version. For example: Ver 8.0.35 MySQL Community Server - GPL
qr, err := mysqld.FetchSuperQuery(ctx, versionSQLQuery)
if err == nil && len(qr.Rows) == 1 {
return qr.Rows[0][0].ToString(), nil

mattlord marked this conversation as resolved.
Show resolved Hide resolved
}

Check warning on line 1202 in go/vt/mysqlctl/mysqld.go

View check run for this annotation

Codecov / codecov/patch

go/vt/mysqlctl/mysqld.go#L1195-L1202

Added lines #L1195 - L1202 were not covered by tests
// Execute as remote action on mysqlctld to use the actual running MySQL
// version.
if socketFile != "" {
client, err := mysqlctlclient.New("unix", socketFile)
if err != nil {
Expand All @@ -1195,6 +1210,7 @@
defer client.Close()
return client.VersionString(ctx)
}
// Fall back to the sys exec method using mysqld --version.
return GetVersionString()
}

Expand Down
Loading