Some of the OSS changes were clobbered when merging with quotas out of, master (#9343)
* OSS side of Global Plugin Reload
This commit is contained in:
parent
a83fe0fc6d
commit
cc51427584
|
@ -257,26 +257,30 @@ func (c *Sys) ReloadPlugin(i *ReloadPluginInput) (string, error) {
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if i.Scope == "cluster" {
|
||||
if i.Scope == "global" {
|
||||
// Get the reload id
|
||||
secret, parseErr := ParseSecret(resp.Body)
|
||||
if parseErr != nil {
|
||||
return "", err
|
||||
}
|
||||
return secret.Data["reload_id"].(string), nil
|
||||
if _, ok := secret.Data["reload_id"]; ok {
|
||||
return secret.Data["reload_id"].(string), nil
|
||||
}
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
type PluginReloadStatus struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
// ReloadStatus is the status of an individual node's plugin reload
|
||||
type ReloadStatus struct {
|
||||
Timestamp time.Time `json:"timestamp" mapstructure:"timestamp"`
|
||||
Success bool `json:"success" mapstructure:"success"`
|
||||
Message string `json:"message" mapstructure:"message"`
|
||||
}
|
||||
|
||||
type PluginReloadStatusResponse struct {
|
||||
ReloadID string
|
||||
Results map[string]interface{}
|
||||
// ReloadStatusResponse is the combined response of all known completed plugin reloads
|
||||
type ReloadStatusResponse struct {
|
||||
ReloadID string `mapstructure:"reload_id"`
|
||||
Results map[string]*ReloadStatus `mapstructure:"results"`
|
||||
}
|
||||
|
||||
// ReloadPluginStatusInput is used as input to the ReloadStatusPlugin function.
|
||||
|
@ -286,10 +290,10 @@ type ReloadPluginStatusInput struct {
|
|||
}
|
||||
|
||||
// ReloadPluginStatus retrieves the status of a reload operation
|
||||
func (c *Sys) ReloadPluginStatus(reloadID string) (map[string]interface{}, error) {
|
||||
func (c *Sys) ReloadPluginStatus(reloadStatusInput *ReloadPluginStatusInput) (*ReloadStatusResponse, error) {
|
||||
path := "/v1/sys/plugins/reload/backend/status"
|
||||
req := c.c.NewRequest(http.MethodGet, path)
|
||||
req.Params.Add("reload_id", reloadID)
|
||||
req.Params.Add("reload_id", reloadStatusInput.ReloadID)
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
defer cancelFunc()
|
||||
|
@ -305,7 +309,19 @@ func (c *Sys) ReloadPluginStatus(reloadID string) (map[string]interface{}, error
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return secret.Data, nil
|
||||
var r ReloadStatusResponse
|
||||
d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
DecodeHook: mapstructure.StringToTimeHookFunc(time.RFC3339),
|
||||
Result: &r,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = d.Decode(secret.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &r, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ func (c *PluginReloadCommand) Flags() *FlagSets {
|
|||
Name: "scope",
|
||||
Target: &c.scope,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: `The scope of the reload, omitted for local, "cluster", for cluster-wide`,
|
||||
Usage: "The scope of the reload, omitted for local, 'global', for replicated reloads",
|
||||
})
|
||||
|
||||
return set
|
||||
|
@ -92,7 +92,7 @@ func (c *PluginReloadCommand) Run(args []string) int {
|
|||
case c.plugin != "" && len(c.mounts) > 0:
|
||||
c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
case c.scope != "" && c.scope != "cluster":
|
||||
case c.scope != "" && c.scope != "global":
|
||||
c.UI.Error(fmt.Sprintf("Invalid reload scope: %s", c.scope))
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,7 @@ func (c *PluginReloadCommand) Run(args []string) int {
|
|||
if rid != "" {
|
||||
c.UI.Output(fmt.Sprintf("Success! Reloading plugin: %s, reload_id: %s", c.plugin, rid))
|
||||
} else {
|
||||
c.UI.Output(fmt.Sprintf("Success! Reloaded plugin: %s", c.mounts))
|
||||
c.UI.Output(fmt.Sprintf("Success! Reloaded plugin: %s", c.plugin))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,17 @@ func testPluginReloadCommand(tb testing.TB) (*cli.MockUi, *PluginReloadCommand)
|
|||
}
|
||||
}
|
||||
|
||||
func testPluginReloadStatusCommand(tb testing.TB) (*cli.MockUi, *PluginReloadStatusCommand) {
|
||||
tb.Helper()
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
return ui, &PluginReloadStatusCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginReloadCommand_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -108,3 +119,86 @@ func TestPluginReloadCommand_Run(t *testing.T) {
|
|||
})
|
||||
|
||||
}
|
||||
|
||||
func TestPluginReloadStatusCommand_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
args []string
|
||||
out string
|
||||
code int
|
||||
}{
|
||||
{
|
||||
"not_enough_args",
|
||||
nil,
|
||||
"Not enough arguments",
|
||||
1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, closer := testVaultServer(t)
|
||||
defer closer()
|
||||
|
||||
ui, cmd := testPluginReloadCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
args := append([]string{}, tc.args...)
|
||||
code := cmd.Run(args)
|
||||
if code != tc.code {
|
||||
t.Errorf("expected %d to be %d", code, tc.code)
|
||||
}
|
||||
|
||||
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||
if !strings.Contains(combined, tc.out) {
|
||||
t.Errorf("expected %q to contain %q", combined, tc.out)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("integration", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pluginDir, cleanup := testPluginDir(t)
|
||||
defer cleanup(t)
|
||||
|
||||
client, _, closer := testVaultServerPluginDir(t, pluginDir)
|
||||
defer closer()
|
||||
|
||||
pluginName := "my-plugin"
|
||||
_, sha256Sum := testPluginCreateAndRegister(t, client, pluginDir, pluginName, consts.PluginTypeCredential)
|
||||
|
||||
ui, cmd := testPluginReloadStatusCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
if err := client.Sys().RegisterPlugin(&api.RegisterPluginInput{
|
||||
Name: pluginName,
|
||||
Type: consts.PluginTypeCredential,
|
||||
Command: pluginName,
|
||||
SHA256: sha256Sum,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
code := cmd.Run([]string{
|
||||
"-reload_id", pluginName,
|
||||
})
|
||||
if exp := 0; code != exp {
|
||||
t.Errorf("expected %d to be %d", code, exp)
|
||||
}
|
||||
|
||||
expected := "Success! Reloaded plugin: "
|
||||
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||
if !strings.Contains(combined, expected) {
|
||||
t.Errorf("expected %q to contain %q", combined, expected)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ import (
|
|||
)
|
||||
|
||||
const maxBytes = 128 * 1024
|
||||
const clusterScope = "cluster"
|
||||
const globalScope = "global"
|
||||
|
||||
func systemBackendMemDBSchema() *memdb.DBSchema {
|
||||
systemSchema := &memdb.DBSchema{
|
||||
|
@ -439,6 +439,10 @@ func (b *SystemBackend) handlePluginReloadUpdate(ctx context.Context, req *logic
|
|||
pluginMounts := d.Get("mounts").([]string)
|
||||
scope := d.Get("scope").(string)
|
||||
|
||||
if scope != "" && scope != globalScope {
|
||||
return logical.ErrorResponse("reload scope must be omitted or 'global'"), nil
|
||||
}
|
||||
|
||||
if pluginName != "" && len(pluginMounts) > 0 {
|
||||
return logical.ErrorResponse("plugin and mounts cannot be set at the same time"), nil
|
||||
}
|
||||
|
@ -447,12 +451,12 @@ func (b *SystemBackend) handlePluginReloadUpdate(ctx context.Context, req *logic
|
|||
}
|
||||
|
||||
if pluginName != "" {
|
||||
err := b.Core.reloadMatchingPlugin(ctx, pluginName, time.Now())
|
||||
err := b.Core.reloadMatchingPlugin(ctx, pluginName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if len(pluginMounts) > 0 {
|
||||
err := b.Core.reloadMatchingPluginMounts(ctx, pluginMounts, time.Now())
|
||||
err := b.Core.reloadMatchingPluginMounts(ctx, pluginMounts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -466,8 +470,11 @@ func (b *SystemBackend) handlePluginReloadUpdate(ctx context.Context, req *logic
|
|||
},
|
||||
}
|
||||
|
||||
if scope == clusterScope {
|
||||
go handleClusterPluginReload(b, req.ID, pluginName, pluginMounts)
|
||||
if scope == globalScope {
|
||||
err := handleGlobalPluginReload(ctx, b.Core, req.ID, pluginName, pluginMounts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return logical.RespondWithStatusCode(&r, req, http.StatusAccepted)
|
||||
}
|
||||
return &r, nil
|
||||
|
@ -4281,11 +4288,11 @@ This path responds to the following HTTP methods.
|
|||
"",
|
||||
},
|
||||
"plugin-backend-reload-scope": {
|
||||
`The scope of the reload`,
|
||||
`Either absent or the empty string for local reload, or "cluster" for a cluster wide reload`,
|
||||
`The scope of the plugin reload, either omitted or 'global'`,
|
||||
"",
|
||||
},
|
||||
"plugin-reload-backend-status": {
|
||||
`Retrieve the status of a cluster-wide plugin reload`,
|
||||
`Retrieve the status of a global plugin reload`,
|
||||
"",
|
||||
},
|
||||
"hash": {
|
||||
|
|
|
@ -84,10 +84,10 @@ var (
|
|||
},
|
||||
}
|
||||
}
|
||||
handleClusterPluginReload = func(*SystemBackend, string, string, []string) error {
|
||||
handleGlobalPluginReload = func(context.Context, *Core, string, string, []string) error {
|
||||
return nil
|
||||
}
|
||||
handleSetupPluginReload = func(*SystemBackend) error {
|
||||
handleSetupPluginReload = func(*Core) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -718,7 +718,7 @@ func (b *SystemBackend) pluginsReloadPath() *framework.Path {
|
|||
logical.UpdateOperation: &framework.PathOperation{
|
||||
Callback: b.handlePluginReloadUpdate,
|
||||
Summary: "Reload mounted plugin backends.",
|
||||
Description: "Either the plugin name (`plugin`) or the desired plugin backend mounts (`mounts`) must be provided, but not both. In the case that the plugin name is provided, all mounted paths that use that plugin backend will be reloaded. If (`scope`) is provided and is (`cluster`), the plugin(s) are reloaded cluster wide.",
|
||||
Description: "Either the plugin name (`plugin`) or the desired plugin backend mounts (`mounts`) must be provided, but not both. In the case that the plugin name is provided, all mounted paths that use that plugin backend will be reloaded. If (`scope`) is provided and is (`global`), the plugin(s) are reloaded globally.",
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -726,6 +726,7 @@ func (b *SystemBackend) pluginsReloadPath() *framework.Path {
|
|||
HelpDescription: strings.TrimSpace(sysHelp["plugin-reload"][1]),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *SystemBackend) toolsPaths() []*framework.Path {
|
||||
return []*framework.Path{
|
||||
{
|
||||
|
|
|
@ -257,26 +257,30 @@ func (c *Sys) ReloadPlugin(i *ReloadPluginInput) (string, error) {
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if i.Scope == "cluster" {
|
||||
if i.Scope == "global" {
|
||||
// Get the reload id
|
||||
secret, parseErr := ParseSecret(resp.Body)
|
||||
if parseErr != nil {
|
||||
return "", err
|
||||
}
|
||||
return secret.Data["reload_id"].(string), nil
|
||||
if _, ok := secret.Data["reload_id"]; ok {
|
||||
return secret.Data["reload_id"].(string), nil
|
||||
}
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
type PluginReloadStatus struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
// ReloadStatus is the status of an individual node's plugin reload
|
||||
type ReloadStatus struct {
|
||||
Timestamp time.Time `json:"timestamp" mapstructure:"timestamp"`
|
||||
Success bool `json:"success" mapstructure:"success"`
|
||||
Message string `json:"message" mapstructure:"message"`
|
||||
}
|
||||
|
||||
type PluginReloadStatusResponse struct {
|
||||
ReloadID string
|
||||
Results map[string]interface{}
|
||||
// ReloadStatusResponse is the combined response of all known completed plugin reloads
|
||||
type ReloadStatusResponse struct {
|
||||
ReloadID string `mapstructure:"reload_id"`
|
||||
Results map[string]*ReloadStatus `mapstructure:"results"`
|
||||
}
|
||||
|
||||
// ReloadPluginStatusInput is used as input to the ReloadStatusPlugin function.
|
||||
|
@ -286,10 +290,10 @@ type ReloadPluginStatusInput struct {
|
|||
}
|
||||
|
||||
// ReloadPluginStatus retrieves the status of a reload operation
|
||||
func (c *Sys) ReloadPluginStatus(reloadID string) (map[string]interface{}, error) {
|
||||
func (c *Sys) ReloadPluginStatus(reloadStatusInput *ReloadPluginStatusInput) (*ReloadStatusResponse, error) {
|
||||
path := "/v1/sys/plugins/reload/backend/status"
|
||||
req := c.c.NewRequest(http.MethodGet, path)
|
||||
req.Params.Add("reload_id", reloadID)
|
||||
req.Params.Add("reload_id", reloadStatusInput.ReloadID)
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
defer cancelFunc()
|
||||
|
@ -305,8 +309,19 @@ func (c *Sys) ReloadPluginStatus(reloadID string) (map[string]interface{}, error
|
|||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
return secret.Data, nil
|
||||
var r ReloadStatusResponse
|
||||
d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
DecodeHook: mapstructure.StringToTimeHookFunc(time.RFC3339),
|
||||
Result: &r,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = d.Decode(secret.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &r, nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
|
|
Loading…
Reference in New Issue