Merge pull request #4386 from dolthub/aaron/sql-server-yaml-parse-and-validate-cluster-config

go/cmd/dolt/commands/sqlserver: yaml_config.go: Parse and validate cluster: config.
This commit is contained in:
Aaron Son
2022-09-21 16:37:07 -07:00
committed by GitHub
3 changed files with 244 additions and 2 deletions
@@ -16,9 +16,11 @@ package sqlserver
import (
"crypto/tls"
"errors"
"fmt"
"net"
"path/filepath"
"strings"
"github.com/dolthub/dolt/go/cmd/dolt/commands/engine"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
@@ -146,6 +148,24 @@ type ServerConfig interface {
// as a dolt remote for things like `clone`, `fetch` and read
// replication.
RemotesapiPort() *int
// ClusterConfig is the configuration for clustering in this sql-server.
ClusterConfig() ClusterConfig
}
type ClusterConfig interface {
StandbyRemotes() []StandbyRemoteConfig
BootstrapRole() string
BootstrapEpoch() int
RemotesAPIConfig() RemotesAPIConfig
}
type RemotesAPIConfig interface {
Port() int
}
type StandbyRemoteConfig interface {
Name() string
RemoteURLTemplate() string
}
type commandLineServerConfig struct {
@@ -273,6 +293,10 @@ func (cfg *commandLineServerConfig) RemotesapiPort() *int {
return cfg.remotesapiPort
}
func (cfg *commandLineServerConfig) ClusterConfig() ClusterConfig {
return nil
}
// PrivilegeFilePath returns the path to the file which contains all needed privilege information in the form of a
// JSON string.
func (cfg *commandLineServerConfig) PrivilegeFilePath() string {
@@ -453,6 +477,34 @@ func ValidateConfig(config ServerConfig) error {
if config.RequireSecureTransport() && config.TLSCert() == "" && config.TLSKey() == "" {
return fmt.Errorf("require_secure_transport can only be `true` when a tls_key and tls_cert are provided.")
}
return ValidateClusterConfig(config.ClusterConfig())
}
func ValidateClusterConfig(config ClusterConfig) error {
if config == nil {
return nil
}
remotes := config.StandbyRemotes()
if len(remotes) == 0 {
return errors.New("cluster config: must supply standby_remotes when supplying cluster configuration.")
}
for i := range remotes {
if remotes[i].Name() == "" {
return fmt.Errorf("cluster: standby_remotes[%d]: name: Cannot be empty", i)
}
if strings.Index(remotes[i].RemoteURLTemplate(), "{database}") == -1 {
return fmt.Errorf("cluster: standby_remotes[%d]: remote_url_template: is \"%s\" but must include the {database} template parameter", i, remotes[i].RemoteURLTemplate())
}
}
if config.BootstrapRole() != "" && config.BootstrapRole() != "primary" && config.BootstrapRole() != "standby" {
return fmt.Errorf("cluster: boostrap_role: is \"%s\" but must be \"primary\" or \"standby\"", config.BootstrapRole())
}
if config.BootstrapEpoch() < 0 {
return fmt.Errorf("cluster: boostrap_epoch: is %d but must be >= 0", config.BootstrapEpoch())
}
if config.RemotesAPIConfig().Port() < 0 || config.RemotesAPIConfig().Port() > 65535 {
return fmt.Errorf("cluster: remotesapi: port: is not in range 0-65535: %d", config.RemotesAPIConfig().Port())
}
return nil
}
+62 -2
View File
@@ -114,7 +114,11 @@ type MetricsYAMLConfig struct {
}
type RemotesapiYAMLConfig struct {
Port *int `yaml:"port"`
Port_field *int `yaml:"port"`
}
func (r RemotesapiYAMLConfig) Port() int {
return *r.Port_field
}
type UserSessionVars struct {
@@ -134,6 +138,7 @@ type YAMLConfig struct {
CfgDirStr *string `yaml:"cfg_dir"`
MetricsConfig MetricsYAMLConfig `yaml:"metrics"`
RemotesapiConfig RemotesapiYAMLConfig `yaml:"remotesapi"`
ClusterCfg *ClusterYAMLConfig `yaml:"cluster"`
PrivilegeFile *string `yaml:"privilege_file"`
Vars []UserSessionVars `yaml:"user_session_vars"`
Jwks []engine.JwksConfig `yaml:"jwks"`
@@ -340,7 +345,7 @@ func (cfg YAMLConfig) MetricsPort() int {
}
func (cfg YAMLConfig) RemotesapiPort() *int {
return cfg.RemotesapiConfig.Port
return cfg.RemotesapiConfig.Port_field
}
// PrivilegeFilePath returns the path to the file which contains all needed privilege information in the form of a
@@ -444,3 +449,58 @@ func (cfg YAMLConfig) Socket() string {
}
return *cfg.ListenerConfig.Socket
}
func (cfg YAMLConfig) ClusterConfig() ClusterConfig {
if cfg.ClusterCfg == nil {
return nil
}
return cfg.ClusterCfg
}
type ClusterYAMLConfig struct {
StandbyRemotes_field []standbyRemoteYAMLConfig `yaml:"standby_remotes"`
BootstrapRole_field string `yaml:"bootstrap_role"`
BootstrapEpoch_field int `yaml:"bootstrap_epoch"`
Remotesapi clusterRemotesAPIYAMLConfig `yaml:"remotesapi"`
}
type standbyRemoteYAMLConfig struct {
Name_field string `yaml:"name"`
RemoteURLTemplate_field string `yaml:"remote_url_template"`
}
func (c standbyRemoteYAMLConfig) Name() string {
return c.Name_field
}
func (c standbyRemoteYAMLConfig) RemoteURLTemplate() string {
return c.RemoteURLTemplate_field
}
func (c *ClusterYAMLConfig) StandbyRemotes() []StandbyRemoteConfig {
ret := make([]StandbyRemoteConfig, len(c.StandbyRemotes_field))
for i := range c.StandbyRemotes_field {
ret[i] = c.StandbyRemotes_field[i]
}
return ret
}
func (c *ClusterYAMLConfig) BootstrapRole() string {
return c.BootstrapRole_field
}
func (c *ClusterYAMLConfig) BootstrapEpoch() int {
return c.BootstrapEpoch_field
}
func (c *ClusterYAMLConfig) RemotesAPIConfig() RemotesAPIConfig {
return c.Remotesapi
}
type clusterRemotesAPIYAMLConfig struct {
P int `yaml:"port"`
}
func (c clusterRemotesAPIYAMLConfig) Port() int {
return c.P
}
@@ -161,6 +161,136 @@ remotesapi:
require.Equal(t, 8000, *config.RemotesapiPort())
}
func TestUnmarshallCluster(t *testing.T) {
testStr := `
cluster:
standby_remotes:
- name: standby
remote_url_template: http://doltdb-1.doltdb:50051/{database}
bootstrap_role: primary
bootstrap_epoch: 0
remotesapi:
port: 50051
`
config, err := NewYamlConfig([]byte(testStr))
require.NoError(t, err)
require.NotNil(t, config.ClusterConfig())
require.NotNil(t, config.ClusterConfig().RemotesAPIConfig())
require.Equal(t, 50051, config.ClusterConfig().RemotesAPIConfig().Port())
require.Len(t, config.ClusterConfig().StandbyRemotes(), 1)
require.Equal(t, "primary", config.ClusterConfig().BootstrapRole())
require.Equal(t, 0, config.ClusterConfig().BootstrapEpoch())
require.Equal(t, "standby", config.ClusterConfig().StandbyRemotes()[0].Name())
require.Equal(t, "http://doltdb-1.doltdb:50051/{database}", config.ClusterConfig().StandbyRemotes()[0].RemoteURLTemplate())
}
func TestValidateClusterConfig(t *testing.T) {
cases := []struct {
Name string
Config string
Error bool
}{
{
Name: "no cluster: config",
Config: "",
Error: false,
},
{
Name: "all fields valid",
Config: `
cluster:
standby_remotes:
- name: standby
remote_url_template: http://localhost:50051/{database}
bootstrap_role: primary
bootstrap_epoch: 0
remotesapi:
port: 50051
`,
Error: false,
},
{
Name: "bad bootstrap_role",
Config: `
cluster:
standby_remotes:
- name: standby
remote_url_template: http://localhost:50051/{database}
bootstrap_role: backup
bootstrap_epoch: 0
remotesapi:
port: 50051
`,
Error: true,
},
{
Name: "negative bootstrap_epoch",
Config: `
cluster:
standby_remotes:
- name: standby
remote_url_template: http://localhost:50051/{database}
bootstrap_role: primary
bootstrap_epoch: -1
remotesapi:
port: 50051
`,
Error: true,
},
{
Name: "negative remotesapi port",
Config: `
cluster:
standby_remotes:
- name: standby
remote_url_template: http://localhost:50051/{database}
bootstrap_role: primary
bootstrap_epoch: 0
remotesapi:
port: -5
`,
Error: true,
},
{
Name: "bad remote_url_template",
Config: `
cluster:
standby_remotes:
- name: standby
remote_url_template: http://localhost:50051/{database
bootstrap_role: primary
bootstrap_epoch: 0
remotesapi:
port: 50051
`,
Error: true,
},
{
Name: "no standby remotes",
Config: `
cluster:
standby_remotes:
bootstrap_role: primary
bootstrap_epoch: 0
remotesapi:
port: 50051
`,
Error: true,
},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
cfg, err := NewYamlConfig([]byte(c.Config))
require.NoError(t, err)
if c.Error {
require.Error(t, ValidateClusterConfig(cfg.ClusterConfig()))
} else {
require.NoError(t, ValidateClusterConfig(cfg.ClusterConfig()))
}
})
}
}
// Tests that a common YAML error (incorrect indentation) throws an error
func TestUnmarshallError(t *testing.T) {
testStr := `