1
0
mirror of https://github.com/gluster/glusterd2.git synced 2026-02-05 12:45:38 +01:00
Files
glusterd2/glustercli/cmd/georep.go
Aravinda VK 66a9b6e308 geo-rep: Fixes for remote authentication and gsyncd path
- gsyncd path defaulted to /usr/libexec/glusterfs/gsyncd
- Fixes remote REST API auth issues
- Workaround to make it work with marker xlator

Signed-off-by: Aravinda VK <avishwan@redhat.com>
2018-11-29 11:40:33 +05:30

634 lines
18 KiB
Go

package cmd
import (
"errors"
"fmt"
"io/ioutil"
"net/url"
"os"
"strings"
"time"
"github.com/gluster/glusterd2/pkg/restclient"
georepapi "github.com/gluster/glusterd2/plugins/georeplication/api"
"github.com/olekukonko/tablewriter"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
const (
helpGeorepCmd = "Gluster Geo-replication"
helpGeorepCreateCmd = "Create a Geo-replication Session"
helpGeorepStartCmd = "Start a Geo-replication Session"
helpGeorepStopCmd = "Stop a Geo-replication Session"
helpGeorepDeleteCmd = "Delete a Geo-replication Session"
helpGeorepPauseCmd = "Pause a Geo-replication Session"
helpGeorepResumeCmd = "Resume a Geo-replication Session"
helpGeorepStatusCmd = "Status of Geo-replication Sessions"
helpGeorepConfigGetCmd = "Geo-replication Session Configurations"
helpGeorepConfigSetCmd = "Geo-replication Session Config management"
helpGeorepConfigResetCmd = "Reset Geo-replication Session Configurations"
errGeorepSessionCreationFailed = "Georep session creation failed.\n"
errGeorepSSHKeysGenerate = `Failed to create SSH Keys in one or more Master Volume nodes.
Please check the log file for more details`
errGeorepMasterInfoNotAvailable = `Failed to get Master Volume details, Please check
Glusterd is running and Master Volume name is Valid`
errGeorepRemoteInfoNotAvailable = `Failed to get Remote Volume details, Please check Glusterd is running
in the remote Cluster and reachable from this node`
errGeorepSessionAlreadyExists = `Geo-replication session already exists, Use --force to
update the existing session and redistribute the SSH Keys to Remote Cluster`
errGeorepSSHKeysPush = `Geo-replication session created successfully. But failed to push
SSH Keys to Remote Cluster. Please check the following
- Glusterd is running in the remote node and reachable from this node.
- If any nodes belonging to the remote Volume are down
Rerun the Session Create command with --force once the issues related to Remote Cluster is resolved`
errGeorepStatusCommandFailed = "Geo-replication Status command failed.\n"
)
const (
geoRepHTTPScheme = "http"
geoRepGlusterdPort = 24007
)
var (
flagGeorepCmdForce bool
flagGeorepShowAllConfig bool
flagGeorepRemoteEndpoints string
flagRemoteUser string
flagRemoteSecret string
flagRemoteSecretFile string
flagRemoteCacert string
flagRemoteInsecure bool
)
func init() {
// Geo-rep Create
georepCreateCmd.Flags().StringVar(&flagGeorepRemoteEndpoints, "remote-endpoints", "", "remote glusterd2 endpoints")
georepCreateCmd.Flags().BoolVarP(&flagGeorepCmdForce, "force", "f", false, "Force")
georepCreateCmd.Flags().StringVar(&flagRemoteUser, "remote-user", "glustercli", "Username for authentication")
georepCreateCmd.Flags().StringVar(&flagRemoteSecret, "remote-secret", "", "Password for authentication")
georepCreateCmd.Flags().StringVar(&flagRemoteSecretFile, "remote-secret-file", "", "Path to file which contains the secret for authentication")
georepCreateCmd.Flags().StringVar(&flagRemoteCacert, "remote-cacert", "", "Path to CA certificate")
georepCreateCmd.Flags().BoolVar(&flagRemoteInsecure, "remote-insecure", false,
"Skip remote server certificate validation")
georepCmd.AddCommand(georepCreateCmd)
// Geo-rep Start
georepStartCmd.Flags().BoolVarP(&flagGeorepCmdForce, "force", "f", false, "Force")
georepCmd.AddCommand(georepStartCmd)
// Geo-rep Stop
georepStopCmd.Flags().BoolVarP(&flagGeorepCmdForce, "force", "f", false, "Force")
georepCmd.AddCommand(georepStopCmd)
// Geo-rep Delete
georepDeleteCmd.Flags().BoolVarP(&flagGeorepCmdForce, "force", "f", false, "Force")
georepCmd.AddCommand(georepDeleteCmd)
// Geo-rep Pause
georepPauseCmd.Flags().BoolVarP(&flagGeorepCmdForce, "force", "f", false, "Force")
georepCmd.AddCommand(georepPauseCmd)
// Geo-rep Resume
georepResumeCmd.Flags().BoolVarP(&flagGeorepCmdForce, "force", "f", false, "Force")
georepCmd.AddCommand(georepResumeCmd)
// Geo-rep Status
georepCmd.AddCommand(georepStatusCmd)
// Geo-rep Config
georepGetCmd.Flags().BoolVarP(&flagGeorepShowAllConfig, "show-all", "a", false, "Show all Configurations")
georepCmd.AddCommand(georepGetCmd)
georepCmd.AddCommand(georepSetCmd)
georepCmd.AddCommand(georepResetCmd)
}
var georepCmd = &cobra.Command{
Use: "geo-replication",
Short: helpGeorepCmd,
}
type volumeDetails struct {
volname string
id string
nodes []georepapi.GeorepRemoteHostReq
}
func getVolumeDetails(volname string, rclient *restclient.Client) (*volumeDetails, error) {
var c *restclient.Client
var master bool
if rclient == nil {
// Local Cluster, Use Default Client
c = client
master = true
} else {
// Use the Client specified(Client to connect to remote Cluster)
c = rclient
master = false
}
vols, err := c.Volumes(volname)
if err != nil || len(vols) == 0 {
emsg := errGeorepMasterInfoNotAvailable
if !master {
emsg = errGeorepRemoteInfoNotAvailable
}
if GlobalFlag.Verbose {
log.WithError(err).WithField("volume", volname).Error("failed to get Volume details")
}
return nil, errors.New(emsg)
}
var nodesdata []georepapi.GeorepRemoteHostReq
if rclient != nil {
// Node details are not required for Master Volume
nodes := make(map[string]bool)
for _, subvol := range vols[0].Subvols {
for _, brick := range subvol.Bricks {
if _, ok := nodes[brick.PeerID.String()]; !ok {
nodes[brick.PeerID.String()] = true
nodesdata = append(nodesdata, georepapi.GeorepRemoteHostReq{PeerID: brick.PeerID.String(), Hostname: brick.Hostname})
}
}
}
}
return &volumeDetails{
volname: volname,
id: vols[0].ID.String(),
nodes: nodesdata,
}, nil
}
func parseRemoteData(data string) (string, string, string, error) {
remotehostvol := strings.Split(data, "::")
if len(remotehostvol) != 2 {
return "", "", "", errors.New("invalid Remote Volume details, use <remoteuser>@<remotehost>::<remotevol> format")
}
remoteuserhost := strings.Split(remotehostvol[0], "@")
remoteuser := "root"
remotehost := remoteuserhost[0]
remotevol := remotehostvol[1]
if len(remoteuserhost) > 1 {
remotehost = remoteuserhost[1]
remoteuser = remoteuserhost[0]
}
return remoteuser, remotehost, remotevol, nil
}
var georepCreateCmd = &cobra.Command{
Use: "create <master-volume> [<remote-user>@]<remote-host>::<remote-volume>",
Short: helpGeorepCreateCmd,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
volname := args[0]
remoteuser, remotehost, remotevol, err := parseRemoteData(args[1])
if err != nil {
failure(errGeorepSessionCreationFailed, err, 1)
}
masterdata, err := getVolumeDetails(volname, nil)
if err != nil {
failure(errGeorepSessionCreationFailed, err, 1)
}
remoteEndpoint, rclient, err := getRemoteClient(remotehost)
if err != nil {
failure(errGeorepSessionCreationFailed, err, 1)
}
remotevoldata, err := getVolumeDetails(remotevol, rclient)
if err != nil {
handleGlusterdConnectFailure(errGeorepSessionCreationFailed, remoteEndpoint, err, 1)
// If not Glusterd connect Failure
failure(errGeorepSessionCreationFailed, err, 1)
}
// Generate SSH Keys from all nodes of Master Volume
sshkeys, err := client.GeorepSSHKeysGenerate(volname)
if err != nil {
if GlobalFlag.Verbose {
log.WithError(err).WithField("volume", volname).Error("failed to generate SSH Keys")
}
failure(errGeorepSessionCreationFailed+errGeorepSSHKeysGenerate, err, 1)
}
_, err = client.GeorepCreate(masterdata.id, remotevoldata.id, georepapi.GeorepCreateReq{
MasterVol: volname,
RemoteUser: remoteuser,
RemoteHosts: remotevoldata.nodes,
RemoteVol: remotevol,
Force: flagGeorepCmdForce,
})
if err != nil {
if GlobalFlag.Verbose {
log.WithError(err).WithField("volume", volname).Error("georep session creation failed")
}
failure(errGeorepSessionCreationFailed, err, 1)
}
err = rclient.GeorepSSHKeysPush(remotevol, sshkeys)
if err != nil {
if GlobalFlag.Verbose {
log.WithError(err).WithField("volume", volname).Error("failed to push SSH Keys to Remote Cluster")
}
handleGlusterdConnectFailure(errGeorepSessionCreationFailed, remoteEndpoint, err, 1)
// If not Glusterd connect issue
failure(errGeorepSSHKeysPush, err, 1)
}
fmt.Println("Geo-replication session created successfully")
},
}
type georepAction int8
const (
georepStart georepAction = iota
georepStop
georepPause
georepResume
georepDelete
)
func (a *georepAction) String() string {
switch *a {
case georepStart:
return "Start"
case georepStop:
return "Stop"
case georepPause:
return "Pause"
case georepResume:
return "Resume"
case georepDelete:
return "Delete"
}
return ""
}
func handleGeorepAction(args []string, action georepAction) {
masterVolID, remoteVolID, err := getVolIDs(args)
if err != nil {
failure(fmt.Sprintf("Geo-replication %s failed.\n", action.String()), err, 1)
}
switch action {
case georepStart:
_, err = client.GeorepStart(masterVolID, remoteVolID, flagGeorepCmdForce)
case georepStop:
_, err = client.GeorepStop(masterVolID, remoteVolID, flagGeorepCmdForce)
case georepPause:
_, err = client.GeorepPause(masterVolID, remoteVolID, flagGeorepCmdForce)
case georepResume:
_, err = client.GeorepResume(masterVolID, remoteVolID, flagGeorepCmdForce)
case georepDelete:
err = client.GeorepDelete(masterVolID, remoteVolID, flagGeorepCmdForce)
}
if err != nil {
if GlobalFlag.Verbose {
log.WithError(err).WithField("volume", args[0]).Error("geo-replication", action.String(), "failed")
}
failure(fmt.Sprintf("Geo-replication %s failed", action.String()), err, 1)
}
fmt.Println("Geo-replication session", action.String(), "successful")
}
var georepStartCmd = &cobra.Command{
Use: "start <master-volume> [<remote-user>@]<remote-host>::<remote-volume>",
Short: helpGeorepStartCmd,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
handleGeorepAction(args, georepStart)
},
}
var georepStopCmd = &cobra.Command{
Use: "stop <master-volume> [<remote-user>@]<remote-host>::<remote-volume>",
Short: helpGeorepStopCmd,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
handleGeorepAction(args, georepStop)
},
}
var georepPauseCmd = &cobra.Command{
Use: "pause <master-volume> [<remote-user>@]<remote-host>::<remote-volume>",
Short: helpGeorepPauseCmd,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
handleGeorepAction(args, georepPause)
},
}
var georepResumeCmd = &cobra.Command{
Use: "resume <master-volume> [<remote-user>@]<remote-host>::<remote-volume>",
Short: helpGeorepResumeCmd,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
handleGeorepAction(args, georepResume)
},
}
var georepDeleteCmd = &cobra.Command{
Use: "delete <master-volume> [<remote-user>@]<remote-host>::<remote-volume>",
Short: helpGeorepDeleteCmd,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
handleGeorepAction(args, georepDelete)
},
}
func getRemoteClient(host string) (string, *restclient.Client, error) {
clienturl := flagGeorepRemoteEndpoints
if flagGeorepRemoteEndpoints != "" {
_, err := url.Parse(flagGeorepRemoteEndpoints)
if err != nil {
return "", nil, errors.New("failed to parse geo-replication remote endpoints")
}
} else {
clienturl = fmt.Sprintf("%s://%s:%d", geoRepHTTPScheme, host, geoRepGlusterdPort)
}
remoteSecret := ""
// Secret is taken in following order of precedence (highest to lowest):
// --remote-secret
// --remote-secret-file
// GD2_REMOTE_AUTH_SECRET (environment variable)
// Secret set for Master cluster itself
// Default secret
// Remote Cluster secret --remote-secret
if flagRemoteSecret != "" {
remoteSecret = flagRemoteSecret
}
// Remote Cluster's secret file --remote-secret-file
if flagRemoteSecretFile != "" && remoteSecret == "" {
data, err := ioutil.ReadFile(flagRemoteSecretFile)
if err != nil {
failure(fmt.Sprintf("failed to read remote secret file %s", flagRemoteSecretFile),
err, 1)
}
remoteSecret = string(data)
}
// GD2_REMOTE_AUTH_SECRET
if remoteSecret == "" {
remoteSecret = os.Getenv("GD2_REMOTE_AUTH_SECRET")
}
// Below option of local cluster is used because --remote-* options
// are not specified and Remote volume may exists in same cluster where
// Master Volume exists
if remoteSecret == "" {
remoteSecret = GlobalFlag.Secret
}
client, err := restclient.New(clienturl, flagRemoteUser, remoteSecret, flagRemoteCacert, flagRemoteInsecure)
if err != nil {
failure("failed to setup remote client", err, 1)
}
client.SetTimeout(time.Duration(GlobalFlag.Timeout) * time.Second)
return clienturl, client, nil
}
func getVolIDs(pargs []string) (string, string, error) {
var (
masterVolID string
remoteVolID string
)
allSessions, err := client.GeorepStatus("", "")
if err != nil {
failure(errGeorepStatusCommandFailed, err, 1)
}
if len(pargs) >= 1 {
for _, s := range allSessions {
if s.MasterVol == pargs[0] {
masterVolID = s.MasterID.String()
}
}
if masterVolID == "" {
return "", "", errors.New("failed to get master volume info")
}
}
if len(pargs) >= 2 {
_, remotehost, remotevol, err := parseRemoteData(pargs[1])
if err != nil {
return "", "", err
}
for _, s := range allSessions {
if s.RemoteVol == remotevol {
for _, host := range s.RemoteHosts {
if host.Hostname == remotehost {
remoteVolID = s.RemoteID.String()
}
}
}
}
if remoteVolID == "" {
return "", "", errors.New("failed to get remote volume info")
}
}
return masterVolID, remoteVolID, nil
}
var georepStatusCmd = &cobra.Command{
Use: "status [<master-volume> [[<remote-user>@]<remote-host>::<remote-volume>]]",
Short: helpGeorepStatusCmd,
Args: cobra.RangeArgs(0, 2),
Run: func(cmd *cobra.Command, args []string) {
var err error
masterVolID, remoteVolID, err := getVolIDs(args)
if err != nil {
failure(errGeorepStatusCommandFailed, err, 1)
}
var sessions georepapi.GeorepSessionList
// If masterVolID or remoteVolID is empty then get status of all and then filter
if masterVolID == "" || remoteVolID == "" {
allSessions, err := client.GeorepStatus("", "")
if err != nil {
failure(errGeorepStatusCommandFailed, err, 1)
}
for _, s := range allSessions {
if masterVolID != "" && s.MasterID.String() != masterVolID {
continue
}
if remoteVolID != "" && s.RemoteID.String() != remoteVolID {
continue
}
sessionDetail, err := client.GeorepStatus(s.MasterID.String(), s.RemoteID.String())
if err != nil {
failure(errGeorepStatusCommandFailed, err, 1)
}
sessions = append(sessions, sessionDetail[0])
}
} else {
sessions, err = client.GeorepStatus(masterVolID, remoteVolID)
if err != nil {
failure(errGeorepStatusCommandFailed, err, 1)
}
}
for _, session := range sessions {
fmt.Println()
fmt.Printf("SESSION: %s ==> %s@%s::%s STATUS: %s\n",
session.MasterVol,
session.RemoteUser,
session.RemoteHosts[0].Hostname,
session.RemoteVol,
session.Status,
)
// Status Detail
if len(session.Workers) > 0 {
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Master Brick", "Status", "Crawl Status", "Remote Node", "Last Synced", "Checkpoint Time", "Checkpoint Completion Time"})
for _, worker := range session.Workers {
table.Append([]string{
worker.MasterPeerHostname + ":" + worker.MasterBrickPath,
worker.Status,
worker.CrawlStatus,
worker.RemotePeerHostname,
worker.LastSyncedTime,
worker.CheckpointTime,
worker.CheckpointCompletedTime,
})
}
table.Render()
fmt.Println()
}
}
},
}
var georepGetCmd = &cobra.Command{
Use: "get <master-volume> [<remote-user>@]<remote-host>::<remote-volume>",
Short: helpGeorepConfigGetCmd,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
var err error
masterVolID, remoteVolID, err := getVolIDs(args)
if err != nil {
failure("Error getting Volume IDs", err, 1)
}
opts, err := client.GeorepGet(masterVolID, remoteVolID)
if err != nil {
failure("Error getting Options", err, 1)
}
table := tablewriter.NewWriter(os.Stdout)
table.SetAlignment(tablewriter.ALIGN_LEFT)
if len(opts) > 0 {
table.SetHeader([]string{"Name", "Value", "Default Value"})
}
// User Configured Values
numOpts := 0
for _, opt := range opts {
if !opt.Modified {
continue
}
configurableMsg := ""
if !opt.Configurable {
configurableMsg = "(Not configurable)"
}
numOpts++
table.Append([]string{opt.Name + configurableMsg, opt.Value, opt.DefaultValue})
}
if numOpts > 0 {
fmt.Println()
fmt.Println("Session Configurations:")
table.Render()
}
// Other Configs
if flagGeorepShowAllConfig {
table = tablewriter.NewWriter(os.Stdout)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetHeader([]string{"Name", "Value"})
numOpts = 0
for _, opt := range opts {
if opt.Modified {
continue
}
configurableMsg := ""
if !opt.Configurable {
configurableMsg = "(Not configurable)"
}
numOpts++
// Show value as empty since not modified
table.Append([]string{opt.Name + configurableMsg, opt.Value})
}
if numOpts > 0 {
fmt.Println()
fmt.Println("Default Configurations:")
table.Render()
}
}
},
}
var georepSetCmd = &cobra.Command{
Use: "set <master-volume> [<remote-user>@]<remote-host>::<remote-volume> <name> <value>",
Short: helpGeorepConfigSetCmd,
Args: cobra.ExactArgs(4),
Run: func(cmd *cobra.Command, args []string) {
var err error
masterVolID, remoteVolID, err := getVolIDs(args)
if err != nil {
failure("Error getting Volume IDs", err, 1)
}
opts := make(map[string]string)
opts[args[2]] = args[3]
err = client.GeorepSet(masterVolID, remoteVolID, opts)
if err != nil {
failure("Geo-replication session config set failed", err, 1)
}
fmt.Println("Geo-replication session config set successfully")
},
}
var georepResetCmd = &cobra.Command{
Use: "reset <master-volume> [<remote-user>@]<remote-host>::<remote-volume> <name>",
Short: helpGeorepConfigResetCmd,
Args: cobra.MinimumNArgs(3),
Run: func(cmd *cobra.Command, args []string) {
var err error
masterVolID, remoteVolID, err := getVolIDs(args)
if err != nil {
failure(err.Error(), err, 1)
}
err = client.GeorepReset(masterVolID, remoteVolID, args[2:])
if err != nil {
failure("Geo-replication session config reset failed", err, 1)
}
fmt.Println("Geo-replication session config reset successfully")
},
}