diff --git a/cmd/deploy_proxysql.go b/cmd/deploy_proxysql.go new file mode 100644 index 0000000..5e6bfd0 --- /dev/null +++ b/cmd/deploy_proxysql.go @@ -0,0 +1,97 @@ +// DBDeployer - The MySQL Sandbox +// Copyright © 2006-2021 Giuseppe Maxia +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + "path" + + "github.com/ProxySQL/dbdeployer/common" + "github.com/ProxySQL/dbdeployer/defaults" + "github.com/ProxySQL/dbdeployer/providers" + "github.com/spf13/cobra" +) + +func deploySandboxProxySQL(cmd *cobra.Command, args []string) { + flags := cmd.Flags() + port, _ := flags.GetInt("port") + adminUser, _ := flags.GetString("admin-user") + adminPassword, _ := flags.GetString("admin-password") + skipStart, _ := flags.GetBool("skip-start") + + p, err := providers.DefaultRegistry.Get("proxysql") + if err != nil { + common.Exitf(1, "ProxySQL provider not available: %s", err) + } + + if _, err := p.FindBinary(""); err != nil { + common.Exitf(1, "proxysql binary not found in PATH: %s", err) + } + + sandboxHome := defaults.Defaults().SandboxHome + sandboxDir := path.Join(sandboxHome, fmt.Sprintf("proxysql_%d", port)) + + if common.DirExists(sandboxDir) { + common.Exitf(1, "sandbox directory %s already exists", sandboxDir) + } + + config := providers.SandboxConfig{ + Version: "system", + Dir: sandboxDir, + Port: port, + AdminPort: port, + Host: "127.0.0.1", + DbUser: adminUser, + DbPassword: adminPassword, + Options: map[string]string{}, + } + + _, err = p.CreateSandbox(config) + if err != nil { + common.Exitf(1, "error creating ProxySQL sandbox: %s", err) + } + + if !skipStart { + if err := p.StartSandbox(sandboxDir); err != nil { + common.Exitf(1, "error starting ProxySQL: %s", err) + } + } + + fmt.Printf("ProxySQL sandbox deployed in %s (admin port: %d, mysql port: %d)\n", sandboxDir, port, port+1) +} + +var deployProxySQLCmd = &cobra.Command{ + Use: "proxysql", + Short: "deploys a ProxySQL sandbox", + Long: `proxysql deploys a standalone ProxySQL instance as a sandbox. +It creates a sandbox directory with configuration, start/stop scripts, and a +client script for administration. + +Example: + dbdeployer deploy proxysql + dbdeployer deploy proxysql --port=16032 + dbdeployer deploy proxysql --admin-user=myadmin --admin-password=secret +`, + Run: deploySandboxProxySQL, +} + +func init() { + deployCmd.AddCommand(deployProxySQLCmd) + deployProxySQLCmd.Flags().Int("port", 6032, "ProxySQL admin port") + deployProxySQLCmd.Flags().String("admin-user", "admin", "ProxySQL admin user") + deployProxySQLCmd.Flags().String("admin-password", "admin", "ProxySQL admin password") + deployProxySQLCmd.Flags().Bool("skip-start", false, "Do not start ProxySQL after deployment") +} diff --git a/cmd/replication.go b/cmd/replication.go index 2043231..696f41d 100644 --- a/cmd/replication.go +++ b/cmd/replication.go @@ -16,7 +16,11 @@ package cmd import ( + "fmt" + "path" + "github.com/ProxySQL/dbdeployer/common" + "github.com/ProxySQL/dbdeployer/defaults" "github.com/ProxySQL/dbdeployer/globals" "github.com/ProxySQL/dbdeployer/providers" "github.com/ProxySQL/dbdeployer/sandbox" @@ -102,6 +106,37 @@ func replicationSandbox(cmd *cobra.Command, args []string) { if err != nil { common.Exitf(1, globals.ErrCreatingSandbox, err) } + + withProxySQL, _ := flags.GetBool("with-proxysql") + if withProxySQL { + // Determine the sandbox directory that was created + sandboxDir := path.Join(sd.SandboxDir, defaults.Defaults().MasterSlavePrefix+common.VersionToName(origin)) + if sd.DirName != "" { + sandboxDir = path.Join(sd.SandboxDir, sd.DirName) + } + + // Read port info from child sandbox descriptions + masterDesc, err := common.ReadSandboxDescription(path.Join(sandboxDir, defaults.Defaults().MasterName)) + if err != nil { + common.Exitf(1, "could not read master sandbox description: %s", err) + } + masterPort := masterDesc.Port[0] + + var slavePorts []int + for i := 1; i < nodes; i++ { + nodeDir := path.Join(sandboxDir, fmt.Sprintf("%s%d", defaults.Defaults().NodePrefix, i)) + nodeDesc, err := common.ReadSandboxDescription(nodeDir) + if err != nil { + common.Exitf(1, "could not read node%d sandbox description: %s", i, err) + } + slavePorts = append(slavePorts, nodeDesc.Port[0]) + } + + err = sandbox.DeployProxySQLForTopology(sandboxDir, masterPort, slavePorts, 0, "127.0.0.1") + if err != nil { + common.Exitf(1, "ProxySQL deployment failed: %s", err) + } + } } var replicationCmd = &cobra.Command{ @@ -154,4 +189,5 @@ func init() { replicationCmd.PersistentFlags().BoolP(globals.SuperReadOnlyLabel, "", false, "Set super-read-only for slaves") replicationCmd.PersistentFlags().Bool(globals.ReplHistoryDirLabel, false, "uses the replication directory to store mysql client history") setPflag(replicationCmd, globals.ChangeMasterOptions, "", "CHANGE_MASTER_OPTIONS", "", "options to add to CHANGE MASTER TO", true) + replicationCmd.PersistentFlags().Bool("with-proxysql", false, "Deploy ProxySQL alongside the replication sandbox") } diff --git a/sandbox/proxysql_topology.go b/sandbox/proxysql_topology.go new file mode 100644 index 0000000..4f78990 --- /dev/null +++ b/sandbox/proxysql_topology.go @@ -0,0 +1,76 @@ +package sandbox + +import ( + "fmt" + "path" + "strings" + + "github.com/ProxySQL/dbdeployer/common" + "github.com/ProxySQL/dbdeployer/providers" +) + +// DeployProxySQLForTopology creates a ProxySQL sandbox configured for a MySQL topology. +// +// Parameters: +// - sandboxDir: parent sandbox directory (e.g. ~/sandboxes/rsandbox_8_4_4) +// - masterPort: MySQL master port +// - slavePorts: MySQL slave ports (empty for single topology) +// - proxysqlPort: port for ProxySQL admin interface (0 = auto-assign) +// - host: bind address (typically "127.0.0.1") +func DeployProxySQLForTopology(sandboxDir string, masterPort int, slavePorts []int, proxysqlPort int, host string) error { + reg := providers.DefaultRegistry + p, err := reg.Get("proxysql") + if err != nil { + return fmt.Errorf("ProxySQL provider not available: %w", err) + } + + if _, err := p.FindBinary(""); err != nil { + return fmt.Errorf("proxysql binary not found: %w", err) + } + + proxysqlDir := path.Join(sandboxDir, "proxysql") + + if proxysqlPort == 0 { + proxysqlPort = 6032 + // Try to find a free port + freePort, err := common.FindFreePort(proxysqlPort, []int{}, 1) + if err == nil { + proxysqlPort = freePort + } + } + + // Build backends: master = HG 0, slaves = HG 1 + var backendParts []string + backendParts = append(backendParts, fmt.Sprintf("%s:%d:0", host, masterPort)) + for _, slavePort := range slavePorts { + backendParts = append(backendParts, fmt.Sprintf("%s:%d:1", host, slavePort)) + } + + config := providers.SandboxConfig{ + Version: "system", + Dir: proxysqlDir, + Port: proxysqlPort, + AdminPort: proxysqlPort, + Host: host, + DbUser: "admin", + DbPassword: "admin", + Options: map[string]string{ + "monitor_user": "msandbox", + "monitor_password": "msandbox", + "backends": strings.Join(backendParts, ","), + }, + } + + _, err = p.CreateSandbox(config) + if err != nil { + return fmt.Errorf("creating ProxySQL sandbox: %w", err) + } + + // Start ProxySQL + if err := p.StartSandbox(proxysqlDir); err != nil { + return fmt.Errorf("starting ProxySQL: %w", err) + } + + fmt.Printf("ProxySQL deployed in %s (admin port: %d, mysql port: %d)\n", proxysqlDir, proxysqlPort, proxysqlPort+1) + return nil +}