Skip to content

Commit

Permalink
Refactor sidecar command, add configs
Browse files Browse the repository at this point in the history
  • Loading branch information
AMecea committed Feb 25, 2019
1 parent 924171e commit 2de1e4d
Show file tree
Hide file tree
Showing 10 changed files with 579 additions and 498 deletions.
17 changes: 11 additions & 6 deletions cmd/mysql-operator-sidecar/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/spf13/cobra"
"sigs.k8s.io/controller-runtime/pkg/runtime/signals"

"github.com/presslabs/mysql-operator/pkg/sidecar/app"
"github.com/presslabs/mysql-operator/pkg/sidecar/appclone"
"github.com/presslabs/mysql-operator/pkg/sidecar/appconf"
"github.com/presslabs/mysql-operator/pkg/sidecar/apphelper"
Expand Down Expand Up @@ -59,11 +60,14 @@ func main() {
// setup logging
logf.SetLogger(logf.ZapLogger(debug))

// init configs
cfg := app.NewBasicConfig(stopCh)

confCmd := &cobra.Command{
Use: "init-configs",
Short: "Init subcommand, for init files.",
Run: func(cmd *cobra.Command, args []string) {
err := appconf.RunConfigCommand(stopCh)
err := appconf.RunConfigCommand(cfg)
if err != nil {
log.Error(err, "init command failed")
os.Exit(1)
Expand All @@ -76,7 +80,7 @@ func main() {
Use: "clone",
Short: "Clone data from a bucket or prior node.",
Run: func(cmd *cobra.Command, args []string) {
err := appclone.RunCloneCommand(stopCh)
err := appclone.RunCloneCommand(cfg)
if err != nil {
log.Error(err, "clone command failed")
os.Exit(1)
Expand All @@ -85,18 +89,19 @@ func main() {
}
cmd.AddCommand(cloneCmd)

helperCmd := &cobra.Command{
sidecarCmd := &cobra.Command{
Use: "run",
Short: "Configs mysql users, replication, and serve backups.",
Run: func(cmd *cobra.Command, args []string) {
err := apphelper.RunRunCommand(stopCh)
mysqlCFG := app.NewMysqlConfig(cfg)
err := apphelper.RunRunCommand(mysqlCFG)
if err != nil {
log.Error(err, "run command failed")
os.Exit(1)
}
},
}
cmd.AddCommand(helperCmd)
cmd.AddCommand(sidecarCmd)

takeBackupCmd := &cobra.Command{
Use: "take-backup-to",
Expand All @@ -108,7 +113,7 @@ func main() {
return nil
},
Run: func(cmd *cobra.Command, args []string) {
err := apptakebackup.RunTakeBackupCommand(stopCh, args[0], args[1])
err := apptakebackup.RunTakeBackupCommand(cfg, args[0], args[1])
if err != nil {
log.Error(err, "take backup command failed")
os.Exit(1)
Expand Down
234 changes: 234 additions & 0 deletions pkg/sidecar/app/configs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
/*
Copyright 2019 Pressinfra SRL
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 app

import (
"fmt"
"os"
"path"
"strconv"
"strings"

"github.com/go-ini/ini"
// add mysql driver
_ "github.com/go-sql-driver/mysql"

"github.com/presslabs/mysql-operator/pkg/internal/mysqlcluster"
orc "github.com/presslabs/mysql-operator/pkg/orchestrator"
)

// NodeRole represents the kind of the MySQL server
type NodeRole string

const (
// MasterNode represents the master role for MySQL server
MasterNode NodeRole = "master"
// SlaveNode represents the slave role for MySQL server
SlaveNode NodeRole = "slave"
)

// BaseConfig contains information related with the pod.
type BaseConfig struct {
StopCh <-chan struct{}

// Hostname represents the pod hostname
Hostname string
// ClusterName is the MySQL cluster name
ClusterName string
// Namespace represents the namespace where the pod is in
Namespace string
// ServiceName is the name of the headless service
ServiceName string

// NodeRole represents the MySQL role of the node, can be on of: msater, slave
NodeRole NodeRole
// ServerID represents the MySQL server id
ServerID int

// InitBucketURL represents the init bucket to initialize mysql
InitBucketURL *string

// OrchestratorURL is the URL to connect to orchestrator
OrchestratorURL *string

// MasterHost represents the cluster master hostname
MasterHost string

// backup user and password for http endpoint
BackupUser string
BackupPassword string
}

// GetHostFor returns the pod hostname for given MySQL server id
func (cfg *BaseConfig) GetHostFor(id int) string {
base := mysqlcluster.GetNameForResource(mysqlcluster.StatefulSet, cfg.ClusterName)
return fmt.Sprintf("%s-%d.%s.%s", base, id-100, cfg.ServiceName, cfg.Namespace)
}

func (cfg *BaseConfig) getOrcClient() orc.Interface {
if cfg.OrchestratorURL == nil {
return nil
}

return orc.NewFromURI(*cfg.OrchestratorURL)
}

func (cfg *BaseConfig) getFQClusterName() string {
return fmt.Sprintf("%s.%s", cfg.ClusterName, cfg.Namespace)
}

func (cfg *BaseConfig) getMasterHost() string {
if client := cfg.getOrcClient(); client != nil {
if master, err := client.Master(cfg.getFQClusterName()); err == nil {
return master.Key.Hostname
}
}

log.V(-1).Info("failed to obtain master from orchestrator, go for default master",
"master", cfg.GetHostFor(100))
return cfg.GetHostFor(100)

}

// NewBasicConfig returns a pointer to BaseConfig configured from environment variables
func NewBasicConfig(stop <-chan struct{}) *BaseConfig {
cfg := &BaseConfig{
StopCh: stop,
Hostname: getEnvValue("HOSTNAME"),
ClusterName: getEnvValue("MY_CLUSTER_NAME"),
Namespace: getEnvValue("MY_NAMESPACE"),
ServiceName: getEnvValue("MY_SERVICE_NAME"),

InitBucketURL: getEnvP("INIT_BUCKET_URI"),
OrchestratorURL: getEnvP("ORCHESTRATOR_URI"),

BackupUser: getEnvValue("MYSQL_BACKUP_USER"),
BackupPassword: getEnvValue("MYSQL_BACKUP_PASSWORD"),
}

// get master host
cfg.MasterHost = cfg.getMasterHost()

// set node role
cfg.NodeRole = SlaveNode
if cfg.Hostname == cfg.MasterHost {
cfg.NodeRole = MasterNode
}

// get server id
ordinal := getOrdinalFromHostname(cfg.Hostname)
cfg.ServerID = ordinal + 100

return cfg
}

func getEnvValue(key string) string {
value := os.Getenv(key)
if len(value) == 0 {
log.Info("envirorment is not set", "key", key)
}

return value
}

func getEnvP(key string) *string {
if value := getEnvValue(key); len(value) != 0 {
return &value
}
return nil
}

func getOrdinalFromHostname(hn string) int {
// mysql-master-1
// or
// stateful-ceva-3
l := strings.Split(hn, "-")
for i := len(l) - 1; i >= 0; i-- {
if o, err := strconv.ParseInt(l[i], 10, 8); err == nil {
return int(o)
}
}

return 0
}

// MysqlConfig contains extra information to connect or configure MySQL server
type MysqlConfig struct {
// inherit from base config
BaseConfig

MysqlDSN *string

// replication user and password
ReplicationUser string
ReplicationPassword string

// metrcis exporter user and password
MetricsUser string
MetricsPassword string

// orchestrator credentials
OrchestratorUser string
OrchestratorPassword string
}

// NewMysqlConfig returns a pointer to MysqlConfig
func NewMysqlConfig(cfg *BaseConfig) *MysqlConfig {
mycfg := &MysqlConfig{
BaseConfig: *cfg,

ReplicationUser: getEnvValue("MYSQL_REPLICATION_USER"),
ReplicationPassword: getEnvValue("MYSQL_REPLICATION_PASSWORD"),

MetricsUser: getEnvValue("MYSQL_METRICS_EXPORTER_USER"),
MetricsPassword: getEnvValue("MYSQL_METRICS_EXPORTER_PASSWORD"),

OrchestratorUser: getEnvValue("MYSQL_ORC_TOPOLOGY_USER"),
OrchestratorPassword: getEnvValue("MYSQL_ORC_TOPOLOGY_PASSWORD"),
}

// set connection DSN to MySQL
var err error
if mycfg.MysqlDSN, err = getMySQLConnectionString(); err != nil {
log.Error(err, "get MySQL DSN")
}

return mycfg
}

// getMySQLConnectionString returns the mysql DSN
func getMySQLConnectionString() (*string, error) {
cnfPath := path.Join(ConfigDir, "client.cnf")
cfg, err := ini.Load(cnfPath)
if err != nil {
return nil, fmt.Errorf("Could not open %s: %s", cnfPath, err)
}

client := cfg.Section("client")
host := client.Key("host").String()
user := client.Key("user").String()
password := client.Key("password").String()
port, err := client.Key("port").Int()

if err != nil {
return nil, fmt.Errorf("Invalid port in %s: %s", cnfPath, err)
}
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/?timeout=5s&multiStatements=true&interpolateParams=true",
user, password, host, port,
)
return &dsn, nil
}
69 changes: 69 additions & 0 deletions pkg/sidecar/app/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
Copyright 2018 Pressinfra SRL
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 app

import (
"strconv"

// add mysql driver
_ "github.com/go-sql-driver/mysql"

"github.com/presslabs/mysql-operator/pkg/util/constants"
)

var (
// MysqlPort represents port on which mysql works
MysqlPort = strconv.Itoa(constants.MysqlPort)

// ConfigDir is the mysql configs path, /etc/mysql
ConfigDir = constants.ConfVolumeMountPath

// ConfDPath is /etc/mysql/conf.d
ConfDPath = constants.ConfDPath

// MountConfigDir is the mounted configs that needs processing
MountConfigDir = constants.ConfMapVolumeMountPath

// DataDir is the mysql data. /var/lib/mysql
DataDir = constants.DataVolumeMountPath

// ToolsDbName is the name of the tools table
ToolsDbName = constants.HelperDbName
// ToolsInitTableName is the name of the init table
ToolsInitTableName = "init"

// UtilityUser is the name of the percona utility user.
UtilityUser = "sys_utility_sidecar"

// OrcTopologyDir contains the path where the secret with orc credentials is
// mounted.
OrcTopologyDir = constants.OrcTopologyDir

// ServerPort http server port
ServerPort = constants.SidecarServerPort
// ServerProbeEndpoint is the http server endpoint for probe
ServerProbeEndpoint = constants.SidecarServerProbePath
// ServerBackupEndpoint is the http server endpoint for backups
ServerBackupEndpoint = "/xbackup"
)

const (
// RcloneConfigFile represents the path to the file that contains rclon
// configs. This path should be the same as defined in docker entrypoint
// script from mysql-operator-sidecar/docker-entrypoint.sh. /etc/rclone.conf
RcloneConfigFile = "/etc/rclone.conf"
)
Loading

0 comments on commit 2de1e4d

Please sign in to comment.