blob: 88e21ec9c9904f213b7bc94c762e425be0d5e509 [file]
package cmd
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"strings"
"github.com/apache/cloudberry-go-libs/cluster"
)
type envVar string
const (
gpHome envVar = "GPHOME"
pxfHome envVar = "PXF_HOME"
pxfBase envVar = "PXF_BASE"
javaHome envVar = "JAVA_HOME"
// For pxf migrate
pxfConf envVar = "PXF_CONF"
)
type messageType int
const (
success messageType = iota
status
err
warning
standby
)
type command struct {
name commandName
messages map[messageType]string
whereToRun cluster.Scope
envVars []envVar
warn bool // whether the command requires a warning/prompt
}
func (cmd *command) Warn(input io.Reader) error {
if !cmd.warn || promptUser(input, cmd.messages[warning]) {
return nil
}
return fmt.Errorf("pxf %s cancelled", cmd.name)
}
func (cmd *command) GetFunctionToExecute() (func(string) string, error) {
inputs, err := makeValidCliInputs(cmd)
if err != nil {
return nil, err
}
switch cmd.name {
case sync:
rsyncCommand := "rsync -az%s -e 'ssh -o StrictHostKeyChecking=no' '%s/conf' '%s/lib' '%s/servers' '%s:%s'"
deleteString := ""
if DeleteOnSync {
deleteString = " --delete"
}
return func(hostname string) string {
return fmt.Sprintf(
rsyncCommand,
deleteString,
inputs[pxfBase],
inputs[pxfBase],
inputs[pxfBase],
hostname,
inputs[pxfBase])
}, nil
default:
var effectivePxfBase string
pxfCommand := ""
if inputs[gpHome] != "" {
pxfCommand += "GPHOME=" + inputs[gpHome] + " "
}
if inputs[pxfConf] != "" {
pxfCommand += "PXF_CONF=" + inputs[pxfConf] + " "
}
if inputs[pxfBase] != "" {
pxfCommand += "PXF_BASE=" + inputs[pxfBase] + " "
effectivePxfBase = inputs[pxfBase]
} else {
// PXF_BASE defaults to PXF_HOME
effectivePxfBase = inputs[pxfHome]
}
if inputs[javaHome] != "" {
pxfCommand += "JAVA_HOME=" + inputs[javaHome] + " "
}
pxfCommand += inputs[pxfHome] + "/bin/pxf" + " " + string(cmd.name)
if cmd.name == prepare && inputs[pxfHome] == effectivePxfBase {
// error out when PXF_BASE equals PXF_HOME
return nil, errors.New("the PXF_BASE value must be different from your PXF installation directory")
}
if cmd.name == migrate && inputs[pxfConf] == effectivePxfBase {
// error out when PXF_BASE equals PXF_CONF
return nil, errors.New("your target PXF_BASE directory must be different from your existing PXF_CONF directory")
}
if cmd.name == reset {
pxfCommand += " --force" // there is a prompt for local reset as well
}
return func(_ string) string { return pxfCommand }, nil
}
}
func promptUser(input io.Reader, prompt string) bool {
reader := bufio.NewReader(input)
fmt.Print(prompt)
text, _ := reader.ReadString('\n')
text = strings.TrimRight(text, "\r\n")
return strings.ToLower(text) == "y"
}
type commandName string
const (
pxfInit = "init"
start = "start"
stop = "stop"
sync = "sync"
statuses = "status"
reset = "reset"
register = "register"
restart = "restart"
prepare = "prepare"
migrate = "migrate"
)
// The pxf cli commands, exported for testing
var (
InitCommand = command{
name: pxfInit,
messages: map[messageType]string{
success: "PXF initialized successfully on %d out of %d host%s\n",
status: "*****************************************************************************\n" +
"* DEPRECATION NOTICE:\n" +
"* The \"pxf cluster init\" command is deprecated and will be removed\n" +
"* in a future release of PXF.\n" +
"*\n" +
"* Use the \"pxf cluster register\" command instead.\n" +
"*\n" +
"*****************************************************************************\n\n" +
"Initializing PXF on coordinator host%s and %d segment host%s...\n",
standby: ", standby coordinator host,",
err: "PXF failed to initialize on %d out of %d host%s\n",
},
warn: false,
envVars: []envVar{gpHome, pxfHome, javaHome},
whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
}
StartCommand = command{
name: start,
messages: map[messageType]string{
success: "PXF started successfully on %d out of %d host%s\n",
status: "Starting PXF on coordinator host%s and %d segment host%s...\n",
standby: ", standby coordinator host,",
err: "PXF failed to start on %d out of %d host%s\n",
},
warn: false,
envVars: []envVar{pxfHome, pxfBase},
whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
}
StopCommand = command{
name: stop,
messages: map[messageType]string{
success: "PXF stopped successfully on %d out of %d host%s\n",
status: "Stopping PXF on coordinator host%s and %d segment host%s...\n",
standby: ", standby coordinator host,",
err: "PXF failed to stop on %d out of %d host%s\n",
},
warn: false,
envVars: []envVar{pxfHome, pxfBase},
whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
}
SyncCommand = command{
name: sync,
messages: map[messageType]string{
success: "PXF configs synced successfully on %d out of %d host%s\n",
status: "Syncing PXF configuration files from coordinator host to%s %d segment host%s...\n",
standby: " standby coordinator host and",
err: "PXF configs failed to sync on %d out of %d host%s\n",
},
warn: false,
envVars: []envVar{pxfBase},
// cluster.ON_LOCAL | cluster.ON_HOSTS: the command will target host%s, but be run from coordinator
// this is ideal for copying files from coordinator to segment host(s) using rsync.
// since the files are already on coordinator, we exclude coordinator but include standby coordinator
whereToRun: cluster.ON_LOCAL | cluster.ON_HOSTS | cluster.EXCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
}
StatusCommand = command{
name: statuses,
messages: map[messageType]string{
success: "PXF is running on %d out of %d host%s\n",
status: "Checking status of PXF servers on coordinator host%s and %d segment host%s...\n",
standby: ", standby coordinator host,",
err: "PXF is not running on %d out of %d host%s\n",
},
warn: false,
envVars: []envVar{pxfHome, pxfBase},
whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
}
RegisterCommand = command{
name: register,
messages: map[messageType]string{
success: "PXF extension has been installed on %d out of %d host%s\n",
status: "Installing PXF extension on coordinator host%s and %d segment host%s...\n",
standby: ", standby coordinator host,",
err: "Failed to install PXF extension on %d out of %d host%s\n",
},
warn: false,
envVars: []envVar{gpHome, pxfHome},
whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
}
ResetCommand = command{
name: reset,
messages: map[messageType]string{
success: "PXF has been reset on %d out of %d host%s\n",
status: "*****************************************************************************\n" +
"* DEPRECATION NOTICE:\n" +
"* The \"pxf cluster reset\" command is deprecated and will be removed\n" +
"* in a future release of PXF.\n" +
"*****************************************************************************\n\n" +
"Resetting PXF on coordinator host%s and %d segment host%s...\n",
standby: ", standby coordinator host,",
err: "Failed to reset PXF on %d out of %d host%s\n",
},
warn: false,
envVars: []envVar{pxfHome},
whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
}
RestartCommand = command{
name: restart,
messages: map[messageType]string{
success: "PXF restarted successfully on %d out of %d host%s\n",
status: "Restarting PXF on coordinator host%s and %d segment host%s...\n",
standby: ", standby coordinator host,",
err: "PXF failed to restart on %d out of %d host%s\n",
},
warn: false,
envVars: []envVar{pxfHome, pxfBase},
whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
}
PrepareCommand = command{
name: prepare,
messages: map[messageType]string{
success: "PXF prepared successfully on %d out of %d host%s\n",
status: "Preparing PXF on coordinator host%s and %d segment host%s...\n",
standby: ", standby coordinator host,",
err: "PXF failed to prepare on %d out of %d host%s\n",
},
warn: false,
envVars: []envVar{pxfHome, pxfBase},
whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
}
MigrateCommand = command{
name: migrate,
messages: map[messageType]string{
success: "PXF configuration migrated successfully on %d out of %d host%s\n",
status: "Migrating PXF configuration on coordinator host%s and %d segment host%s...\n",
standby: ", standby coordinator host,",
err: "PXF failed to migrate configuration on %d out of %d host%s\n",
},
warn: false,
envVars: []envVar{pxfHome, pxfConf, pxfBase},
whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
}
)
func makeValidCliInputs(cmd *command) (map[envVar]string, error) {
envVars := make(map[envVar]string)
for _, e := range cmd.envVars {
val, err := validateEnvVar(e)
if err != nil {
return nil, err
}
envVars[e] = val
}
return envVars, nil
}
func validateEnvVar(envVariable envVar) (string, error) {
envVarValue, isEnvVarSet := os.LookupEnv(string(envVariable))
if !isEnvVarSet {
return "", errors.New(string(envVariable) + " must be set")
}
if envVarValue == "" {
return "", errors.New(string(envVariable) + " cannot be blank")
}
return envVarValue, nil
}