Skip to content

Commit

Permalink
feat(apps): shell trigger support batch reload (#6870)
Browse files Browse the repository at this point in the history
  • Loading branch information
dingshun-cmss authored Mar 27, 2024
1 parent 135e94e commit b3fcb4d
Show file tree
Hide file tree
Showing 6 changed files with 420 additions and 38 deletions.
55 changes: 55 additions & 0 deletions apis/apps/v1alpha1/configconstraint_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,61 @@ type ShellTrigger struct {
//
// +optional
Sync *bool `json:"sync,omitempty"`

// Specifies whether to reconfigure dynamic parameters individually or in a batch.
// - Set to 'True' to execute the reload action in a batch, incorporating all parameter changes.
// - Set to 'False' to execute the reload action for each parameter change individually.
// The default value is 'False'.
//
// +optional
BatchReload *bool `json:"batchReload,omitempty"`

// When `batchReload` is set to 'True', this parameter allows for the optional specification
// of the batch input format that is passed into the STDIN of the script.
// The format should be provided as a Go template string.
// In the template, the updated parameters' key-value map can be referenced using the dollar sign ('$') variable.
// Here's an example of an input template:
//
// ```yaml
//
// batchInputTemplate: |-
//
// {{- range $pKey, $pValue := $ }}
//
// {{ printf "%s:%s" $pKey $pValue }}
//
// {{- end }}
//
// ```
//
// In this example, each updated parameter is iterated over in a sorted order by keys to generate the batch input data as follows:
//
// ```
//
// key1:value1
//
// key2:value2
//
// key3:value3
//
// ```
//
// If this parameter is not specified, the default format used for STDIN is as follows:
// Each updated parameter generates a line that concatenates the parameter's key and value with a equal sign ('=').
// These lines are then sorted by their keys and inserted accordingly. Here's an example of the batch input data using the default template:
//
// ```
//
// key1=value1
//
// key2=value2
//
// key3=value3
//
// ```
//
// +optional
BatchInputTemplate string `json:"batchInputTemplate,omitempty"`
}

type TPLScriptTrigger struct {
Expand Down
10 changes: 8 additions & 2 deletions apis/apps/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions config/crd/bases/apps.kubeblocks.io_configconstraints.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,34 @@ spec:
shellTrigger:
description: Used to perform the reload command in shell script.
properties:
batchInputTemplate:
description: "When `batchReload` is set to 'True', this parameter
allows for the optional specification of the batch input
format that is passed into the STDIN of the script. The
format should be provided as a Go template string. In the
template, the updated parameters' key-value map can be referenced
using the dollar sign ('$') variable. Here's an example
of an input template: \n ```yaml \n batchInputTemplate:
|- \n {{- range $pKey, $pValue := $ }} \n {{ printf \"%s:%s\"
$pKey $pValue }} \n {{- end }} \n ``` \n In this example,
each updated parameter is iterated over in a sorted order
by keys to generate the batch input data as follows: \n
``` \n key1:value1 \n key2:value2 \n key3:value3 \n ```
\n If this parameter is not specified, the default format
used for STDIN is as follows: Each updated parameter generates
a line that concatenates the parameter's key and value with
a equal sign ('='). These lines are then sorted by their
keys and inserted accordingly. Here's an example of the
batch input data using the default template: \n ``` \n key1=value1
\n key2=value2 \n key3=value3 \n ```"
type: string
batchReload:
description: Specifies whether to reconfigure dynamic parameters
individually or in a batch. - Set to 'True' to execute the
reload action in a batch, incorporating all parameter changes.
- Set to 'False' to execute the reload action for each parameter
change individually. The default value is 'False'.
type: boolean
command:
description: Specifies the list of commands for reload.
items:
Expand Down
28 changes: 28 additions & 0 deletions deploy/helm/crds/apps.kubeblocks.io_configconstraints.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,34 @@ spec:
shellTrigger:
description: Used to perform the reload command in shell script.
properties:
batchInputTemplate:
description: "When `batchReload` is set to 'True', this parameter
allows for the optional specification of the batch input
format that is passed into the STDIN of the script. The
format should be provided as a Go template string. In the
template, the updated parameters' key-value map can be referenced
using the dollar sign ('$') variable. Here's an example
of an input template: \n ```yaml \n batchInputTemplate:
|- \n {{- range $pKey, $pValue := $ }} \n {{ printf \"%s:%s\"
$pKey $pValue }} \n {{- end }} \n ``` \n In this example,
each updated parameter is iterated over in a sorted order
by keys to generate the batch input data as follows: \n
``` \n key1:value1 \n key2:value2 \n key3:value3 \n ```
\n If this parameter is not specified, the default format
used for STDIN is as follows: Each updated parameter generates
a line that concatenates the parameter's key and value with
a equal sign ('='). These lines are then sorted by their
keys and inserted accordingly. Here's an example of the
batch input data using the default template: \n ``` \n key1=value1
\n key2=value2 \n key3=value3 \n ```"
type: string
batchReload:
description: Specifies whether to reconfigure dynamic parameters
individually or in a batch. - Set to 'True' to execute the
reload action in a batch, incorporating all parameter changes.
- Set to 'False' to execute the reload action for each parameter
change individually. The default value is 'False'.
type: boolean
command:
description: Specifies the list of commands for reload.
items:
Expand Down
122 changes: 116 additions & 6 deletions pkg/configuration/config_manager/config_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,27 @@ package configmanager
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/fsnotify/fsnotify"
"github.com/pkg/errors"

appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1"
cfgcore "github.com/apecloud/kubeblocks/pkg/configuration/core"
cfgutil "github.com/apecloud/kubeblocks/pkg/configuration/util"
"github.com/apecloud/kubeblocks/pkg/gotemplate"
)

// According to 'https://pkg.go.dev/text/template' :
// For `range`, if the value is a map and the keys can be sorted, the elements will be visited in sorted key order.
const defaultBatchInputTemplate string = `{{- range $pKey, $pValue := $ }}
{{ printf "%s=%s" $pKey $pValue }}
{{- end }}`

type configVolumeHandleMeta struct {
ConfigHandler

Expand Down Expand Up @@ -172,20 +181,72 @@ type shellCommandHandler struct {
backupPath string
configMeta *ConfigSpecInfo
filter regexFilter

isBatchReload bool
batchInputTemplate string
}

func generateBatchStdinData(updatedParams map[string]string, batchInputTemplate string) (string, error) {
tplValues := gotemplate.TplValues{}
for k, v := range updatedParams {
tplValues[k] = v
}
engine := gotemplate.NewTplEngine(
&tplValues, nil, "render-batch-input-parameters", nil, context.TODO(),
)
stdinStr, err := engine.Render(batchInputTemplate)
return strings.TrimSpace(stdinStr) + "\n", err
}

func (s *shellCommandHandler) OnlineUpdate(ctx context.Context, name string, updatedParams map[string]string) error {
logger.V(1).Info(fmt.Sprintf("online update[%v]", updatedParams))
logger.Info(fmt.Sprintf("updated parameters: %v", updatedParams))
args := make([]string, len(s.arg))
return s.execHandler(ctx, updatedParams, args)
copy(args, s.arg)
return s.execHandler(ctx, updatedParams, args...)
}

func execWithBatchReload(ctx context.Context, updatedParams map[string]string, batchInputTemplate string, commandName string, args ...string) (string, error) {
command := exec.CommandContext(ctx, commandName, args...)
stdin, err := command.StdinPipe()
if err != nil {
return "", errors.Wrap(err, "cannot create a pipe connecting to the STDIN of the command")
}
var batchStdinStr string
go func() {
defer stdin.Close()
var err error
batchStdinStr, err = generateBatchStdinData(updatedParams, batchInputTemplate)
if err != nil {
logger.Error(err, "cannot generate batch stdin data")
return
}
if _, err := io.WriteString(stdin, batchStdinStr); err != nil {
logger.Error(err, "cannot write batch stdin data into STDIN stream")
return
}
}()
stdout, err := cfgutil.ExecShellCommand(command)
logger.Info("batch execute",
"exec", command.String(),
"stdin", batchStdinStr,
"stdout", stdout,
"error", err,
)
return stdout, err
}

func (s *shellCommandHandler) execHandler(ctx context.Context, updatedParams map[string]string, args []string) error {
func execWithSeparateReload(ctx context.Context, updatedParams map[string]string, commandName string, args ...string) ([]string, error) {
stdouts := []string{}
commonHandle := func(args []string) error {
command := exec.CommandContext(ctx, s.command, args...)
command := exec.CommandContext(ctx, commandName, args...)
stdout, err := cfgutil.ExecShellCommand(command)
logger.Info(fmt.Sprintf("exec: [%s], stdout: [%s], stderr:%v", command.String(), stdout, err))
logger.Info("execute single param reload",
"exec", command.String(),
"stdout", stdout,
"err", err,
)
stdouts = append(stdouts, stdout)
return err
}
volumeHandle := func(baseCMD []string, paramName, paramValue string) error {
Expand All @@ -196,7 +257,35 @@ func (s *shellCommandHandler) execHandler(ctx context.Context, updatedParams map
}
for key, value := range updatedParams {
if err := volumeHandle(args, key, value); err != nil {
return err
return nil, err
}
}
return stdouts, nil
}

func (s *shellCommandHandler) execHandler(ctx context.Context, updatedParams map[string]string, args ...string) error {
if s.isBatchReload {
stdout, err := execWithBatchReload(ctx, updatedParams, s.batchInputTemplate, s.command, args...)
logger.Info("execute with batch reload",
"updated_params", updatedParams,
"batch_input_template", s.batchInputTemplate,
"execute_args", append([]string{s.command}, args...),
"stdout", stdout,
)
if err != nil {
return errors.Wrap(err, "execute with batch reload failed")
}
} else {
stdouts, err := execWithSeparateReload(ctx, updatedParams, s.command, args...)
execArgs := append([]string{s.command}, args...)
execArgs = append(execArgs, "param_key_N", "param_value_N")
logger.Info("execute with individual param reload",
"updated_params", updatedParams,
"execute_args", execArgs,
"stdouts", stdouts,
)
if err != nil {
return errors.Wrap(err, "execute with individual param reload failed")
}
}
return nil
Expand Down Expand Up @@ -232,7 +321,7 @@ func (s *shellCommandHandler) VolumeHandle(ctx context.Context, event fsnotify.E
}

logger.Info(fmt.Sprintf("updated parameters: %v", updatedParams))
if err := s.execHandler(ctx, updatedParams, args); err != nil {
if err := s.execHandler(ctx, updatedParams, args...); err != nil {
return err
}
if len(files) != 0 {
Expand Down Expand Up @@ -288,6 +377,25 @@ func createConfigVolumeMeta(configSpecName string, reloadType appsv1alpha1.CfgRe
}
}

func isBatchReload(configMeta *ConfigSpecInfo) bool {
return configMeta != nil &&
configMeta.ReloadOptions != nil &&
configMeta.ReloadOptions.ShellTrigger != nil &&
configMeta.ReloadOptions.ShellTrigger.BatchReload != nil &&
*(configMeta.ShellTrigger.BatchReload)
}

func getBatchInputTemplate(configMeta *ConfigSpecInfo) string {
batchInputTemplate := defaultBatchInputTemplate
if configMeta != nil &&
configMeta.ReloadOptions != nil &&
configMeta.ReloadOptions.ShellTrigger != nil &&
len(configMeta.ReloadOptions.ShellTrigger.BatchInputTemplate) > 0 {
batchInputTemplate = configMeta.ShellTrigger.BatchInputTemplate
}
return batchInputTemplate
}

func CreateExecHandler(command []string, mountPoint string, configMeta *ConfigSpecInfo, backupPath string) (ConfigHandler, error) {
if len(command) == 0 {
return nil, cfgcore.MakeError("invalid command: %s", command)
Expand Down Expand Up @@ -319,6 +427,8 @@ func CreateExecHandler(command []string, mountPoint string, configMeta *ConfigSp
downwardAPIMountPoint: cfgutil.ToSet(handler).AsSlice(),
downwardAPIHandler: handler,
configVolumeHandleMeta: createConfigVolumeMeta(configMeta.ConfigSpec.Name, appsv1alpha1.ShellType, []string{mountPoint}, formatterConfig),
isBatchReload: isBatchReload(configMeta),
batchInputTemplate: getBatchInputTemplate(configMeta),
}
return shellTrigger, nil
}
Expand Down
Loading

0 comments on commit b3fcb4d

Please sign in to comment.