Warn if user is not using a supported platform ()

* Add prereqs package

* Incorporate prereq check into the clients

* gazelle

* gazelle fix

* linter

* Add tests

* minor change in test

* finish up tests

* gazelle

* error during platform detection does not cause client to fail fast

Co-authored-by: dv8silencer <15720668+dv8silencer@users.noreply.github.com>
This commit is contained in:
dv8silencer 2020-10-02 01:45:34 -05:00 committed by GitHub
parent 437bab7df0
commit 7d9a706cfa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 275 additions and 0 deletions

View File

@ -34,6 +34,7 @@ go_library(
"//shared/event:go_default_library",
"//shared/featureconfig:go_default_library",
"//shared/params:go_default_library",
"//shared/prereq:go_default_library",
"//shared/prometheus:go_default_library",
"//shared/sliceutil:go_default_library",
"//shared/tracing:go_default_library",

View File

@ -41,6 +41,7 @@ import (
"github.com/prysmaticlabs/prysm/shared/event"
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/prereq"
"github.com/prysmaticlabs/prysm/shared/prometheus"
"github.com/prysmaticlabs/prysm/shared/sliceutil"
"github.com/prysmaticlabs/prysm/shared/tracing"
@ -91,6 +92,9 @@ func NewBeaconNode(cliCtx *cli.Context) (*BeaconNode, error) {
return nil, err
}
// Warn if user's platform is not supported
prereq.WarnIfNotSupported(cliCtx.Context)
featureconfig.ConfigureBeaconChain(cliCtx)
cmd.ConfigureBeaconChain(cliCtx)
flags.ConfigureGlobalFlags(cliCtx)

24
shared/prereq/BUILD.bazel Normal file
View File

@ -0,0 +1,24 @@
load("@io_bazel_rules_go//go:def.bzl", "go_test")
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["prereq.go"],
importpath = "github.com/prysmaticlabs/prysm/shared/prereq",
visibility = ["//visibility:public"],
deps = [
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["prereq_test.go"],
embed = [":go_default_library"],
deps = [
"//shared/testutil/require:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
],
)

108
shared/prereq/prereq.go Normal file
View File

@ -0,0 +1,108 @@
package prereq
import (
"context"
"os/exec"
"runtime"
"strconv"
"strings"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
type platform struct {
os string
arch string
majorVersion int
minorVersion int
}
var (
// execShellOutput has execShellOutputFunc as the default but can be changed for testing purposes.
execShellOutput func(ctx context.Context, command string, args ...string) (string, error) = execShellOutputFunc
runtimeOS = runtime.GOOS
runtimeArch = runtime.GOARCH
)
// execShellOutputFunc passes a command and args to exec.CommandContext and returns the result as a string
func execShellOutputFunc(ctx context.Context, command string, args ...string) (string, error) {
result, err := exec.CommandContext(ctx, command, args...).Output()
if err != nil {
return "", errors.Wrap(err, "error in command execution")
}
return string(result), nil
}
func getSupportedPlatforms() []platform {
return []platform{
{os: "linux", arch: "amd64"},
{os: "linux", arch: "arm64"},
{os: "darwin", arch: "amd64", majorVersion: 10, minorVersion: 14},
{os: "windows", arch: "amd64"},
}
}
// parseVersion takes a string and splits it using sep separator, and outputs a slice of integers
// corresponding to version numbers. If it cannot find num level of versions, it returns an error
func parseVersion(input string, num int, sep string) ([]int, error) {
var version = make([]int, num)
components := strings.Split(input, sep)
for i, component := range components {
components[i] = strings.TrimSpace(component)
}
if len(components) < num {
return nil, errors.New("insufficient information about version")
}
for i := range version {
var err error
version[i], err = strconv.Atoi(components[i])
if err != nil {
return nil, errors.Wrap(err, "error during conversion")
}
}
return version, nil
}
// meetsMinPlatformReqs returns true if the runtime matches any on the list of supported platforms
func meetsMinPlatformReqs(ctx context.Context) (bool, error) {
okPlatforms := getSupportedPlatforms()
for _, platform := range okPlatforms {
if runtimeOS == platform.os && runtimeArch == platform.arch {
// If MacOS we make sure it meets the minimum version cutoff
if runtimeOS == "darwin" {
versionStr, err := execShellOutput(ctx, "uname", "-r")
if err != nil {
return false, errors.Wrap(err, "error obtaining MacOS version")
}
version, err := parseVersion(versionStr, 2, ".")
if err != nil {
return false, errors.Wrap(err, "error parsing version")
}
if version[0] != platform.majorVersion {
return version[0] > platform.majorVersion, nil
}
if version[1] < platform.minorVersion {
return false, nil
}
return true, nil
}
// Otherwise we have a match between runtime and our list of accepted platforms
return true, nil
}
}
return false, nil
}
// WarnIfNotSupported warns if the user's platform is not supported or if it fails to detect user's platform
func WarnIfNotSupported(ctx context.Context) {
supported, err := meetsMinPlatformReqs(ctx)
if err != nil {
log.Warnf("Failed to detect host platform: %v", err)
return
}
if !supported {
log.Warn("This platform is not supported. The following platforms are supported: Linux/AMD64," +
" Linux/ARM64, Mac OS X/AMD64 (10.14+ only), and Windows/AMD64")
}
}

View File

@ -0,0 +1,128 @@
package prereq
import (
"context"
"testing"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
logTest "github.com/sirupsen/logrus/hooks/test"
)
func TestMeetsMinPlatformReqs(t *testing.T) {
// Linux
runtimeOS = "linux"
runtimeArch = "amd64"
meetsReqs, err := meetsMinPlatformReqs(context.Background())
require.Equal(t, true, meetsReqs)
require.NoError(t, err)
runtimeArch = "arm64"
meetsReqs, err = meetsMinPlatformReqs(context.Background())
require.Equal(t, true, meetsReqs)
require.NoError(t, err)
// mips64 is not supported
runtimeArch = "mips64"
meetsReqs, err = meetsMinPlatformReqs(context.Background())
require.Equal(t, false, meetsReqs)
require.NoError(t, err)
// Mac OS X
// In this function we'll set the execShellOutput package variable to another function that will 'mock' the shell
execShellOutput = func(ctx context.Context, command string, args ...string) (string, error) {
return "", errors.New("error while running command")
}
runtimeOS = "darwin"
runtimeArch = "amd64"
meetsReqs, err = meetsMinPlatformReqs(context.Background())
require.Equal(t, false, meetsReqs)
require.ErrorContains(t, "error obtaining MacOS version", err)
// Insufficient version
execShellOutput = func(ctx context.Context, command string, args ...string) (string, error) {
return "10.4", nil
}
meetsReqs, err = meetsMinPlatformReqs(context.Background())
require.Equal(t, false, meetsReqs)
require.NoError(t, err)
// Just-sufficient older version
execShellOutput = func(ctx context.Context, command string, args ...string) (string, error) {
return "10.14", nil
}
meetsReqs, err = meetsMinPlatformReqs(context.Background())
require.Equal(t, true, meetsReqs)
require.NoError(t, err)
// Sufficient newer version
execShellOutput = func(ctx context.Context, command string, args ...string) (string, error) {
return "10.15.7", nil
}
meetsReqs, err = meetsMinPlatformReqs(context.Background())
require.Equal(t, true, meetsReqs)
require.NoError(t, err)
// Handling abnormal response
execShellOutput = func(ctx context.Context, command string, args ...string) (string, error) {
return "tiger.lion", nil
}
meetsReqs, err = meetsMinPlatformReqs(context.Background())
require.Equal(t, false, meetsReqs)
require.ErrorContains(t, "error parsing version", err)
// Windows
runtimeOS = "windows"
runtimeArch = "amd64"
meetsReqs, err = meetsMinPlatformReqs(context.Background())
require.Equal(t, true, meetsReqs)
require.NoError(t, err)
runtimeArch = "arm64"
meetsReqs, err = meetsMinPlatformReqs(context.Background())
require.Equal(t, false, meetsReqs)
require.NoError(t, err)
}
func TestParseVersion(t *testing.T) {
version, err := parseVersion("1.2.3", 3, ".")
require.DeepEqual(t, version, []int{1, 2, 3})
require.NoError(t, err)
version, err = parseVersion("6 .7 . 8 ", 3, ".")
require.DeepEqual(t, version, []int{6, 7, 8})
require.NoError(t, err)
version, err = parseVersion("10,3,5,6", 4, ",")
require.DeepEqual(t, version, []int{10, 3, 5, 6})
require.NoError(t, err)
version, err = parseVersion("4;6;8;10;11", 3, ";")
require.DeepEqual(t, version, []int{4, 6, 8})
require.NoError(t, err)
_, err = parseVersion("10.11", 3, ".")
require.ErrorContains(t, "insufficient information about version", err)
}
func TestWarnIfNotSupported(t *testing.T) {
runtimeOS = "linux"
runtimeArch = "amd64"
hook := logTest.NewGlobal()
WarnIfNotSupported(context.Background())
require.LogsDoNotContain(t, hook, "Failed to detect host platform")
require.LogsDoNotContain(t, hook, "platform is not supported")
execShellOutput = func(ctx context.Context, command string, args ...string) (string, error) {
return "tiger.lion", nil
}
runtimeOS = "darwin"
runtimeArch = "amd64"
hook = logTest.NewGlobal()
WarnIfNotSupported(context.Background())
require.LogsContain(t, hook, "Failed to detect host platform")
require.LogsContain(t, hook, "error parsing version")
runtimeOS = "falseOs"
runtimeArch = "falseArch"
hook = logTest.NewGlobal()
WarnIfNotSupported(context.Background())
require.LogsContain(t, hook, "platform is not supported")
}

View File

@ -13,6 +13,7 @@ go_library(
"//shared/event:go_default_library",
"//shared/featureconfig:go_default_library",
"//shared/params:go_default_library",
"//shared/prereq:go_default_library",
"//shared/prometheus:go_default_library",
"//shared/tracing:go_default_library",
"//shared/version:go_default_library",

View File

@ -19,6 +19,7 @@ import (
"github.com/prysmaticlabs/prysm/shared/event"
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/prereq"
"github.com/prysmaticlabs/prysm/shared/prometheus"
"github.com/prysmaticlabs/prysm/shared/tracing"
"github.com/prysmaticlabs/prysm/shared/version"
@ -64,6 +65,9 @@ func NewSlasherNode(cliCtx *cli.Context) (*SlasherNode, error) {
return nil, err
}
// Warn if user's platform is not supported
prereq.WarnIfNotSupported(cliCtx.Context)
if cliCtx.Bool(flags.EnableHistoricalDetectionFlag.Name) {
// Set the max RPC size to 4096 as configured by --historical-slasher-node for optimal historical detection.
cmdConfig := cmd.Get()

View File

@ -28,6 +28,7 @@ go_library(
"//shared/featureconfig:go_default_library",
"//shared/fileutil:go_default_library",
"//shared/params:go_default_library",
"//shared/prereq:go_default_library",
"//shared/prometheus:go_default_library",
"//shared/tracing:go_default_library",
"//shared/version:go_default_library",

View File

@ -21,6 +21,7 @@ import (
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/fileutil"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/prereq"
"github.com/prysmaticlabs/prysm/shared/prometheus"
"github.com/prysmaticlabs/prysm/shared/tracing"
"github.com/prysmaticlabs/prysm/shared/version"
@ -71,6 +72,9 @@ func NewValidatorClient(cliCtx *cli.Context) (*ValidatorClient, error) {
}
logrus.SetLevel(level)
// Warn if user's platform is not supported
prereq.WarnIfNotSupported(cliCtx.Context)
registry := shared.NewServiceRegistry()
ValidatorClient := &ValidatorClient{
cliCtx: cliCtx,