From eff6b76535a0667ab6be1feaebfffba66c494f25 Mon Sep 17 00:00:00 2001 From: Tiffany Jernigan Date: Wed, 23 Mar 2016 16:51:44 -0700 Subject: [PATCH] Fixes #385: Added swap plugins to snapctl and client --- cmd/snapctl/commands.go | 11 ++++ cmd/snapctl/plugin.go | 84 +++++++++++++++++++++++++- mgmt/rest/client/client_func_test.go | 43 +++++++++++++ mgmt/rest/client/plugin.go | 90 ++++++++++++++++++++++++++++ mgmt/rest/rbody/body.go | 2 + 5 files changed, 228 insertions(+), 2 deletions(-) diff --git a/cmd/snapctl/commands.go b/cmd/snapctl/commands.go index cb389fe93..e5773d3d8 100644 --- a/cmd/snapctl/commands.go +++ b/cmd/snapctl/commands.go @@ -112,6 +112,17 @@ var ( flPluginVersion, }, }, + { + Name: "swap", + Usage: "swap :: or swap -t -n -v ", + Action: swapPlugins, + Flags: []cli.Flag{ + flPluginAsc, + flPluginType, + flPluginName, + flPluginVersion, + }, + }, { Name: "list", Usage: "list", diff --git a/cmd/snapctl/plugin.go b/cmd/snapctl/plugin.go index ffb6c8797..ed2c7e20a 100644 --- a/cmd/snapctl/plugin.go +++ b/cmd/snapctl/plugin.go @@ -69,8 +69,7 @@ func loadPlugin(ctx *cli.Context) { func unloadPlugin(ctx *cli.Context) { pDetails := filepath.SplitList(ctx.Args().First()) - var pType string - var pName string + var pType, pName string var pVer int var err error @@ -116,6 +115,87 @@ func unloadPlugin(ctx *cli.Context) { fmt.Printf("Type: %s\n", r.Type) } +func swapPlugins(ctx *cli.Context) { + // plugin to load + pAsc := ctx.String("plugin-asc") + var paths []string + if len(ctx.Args()) < 1 || len(ctx.Args()) > 2 { + fmt.Println("Incorrect usage:") + cli.ShowCommandHelp(ctx, ctx.Command.Name) + os.Exit(1) + } + paths = append(paths, ctx.Args().First()) + if pAsc != "" { + if !strings.Contains(pAsc, ".asc") { + fmt.Println("Must be a .asc file for the -a flag") + cli.ShowCommandHelp(ctx, ctx.Command.Name) + os.Exit(1) + } + paths = append(paths, pAsc) + } + + // plugin to unload + var pDetails []string + var pType, pName string + var pVer int + var err error + + if len(ctx.Args()) == 2 { + pDetails = filepath.SplitList(ctx.Args()[1]) + if len(pDetails) == 3 { + pType = pDetails[0] + pName = pDetails[1] + pVer, err = strconv.Atoi(pDetails[2]) + if err != nil { + fmt.Println("Can't convert version string to integer") + cli.ShowCommandHelp(ctx, ctx.Command.Name) + os.Exit(1) + } + } else { + fmt.Println("Missing type, name, or version") + cli.ShowCommandHelp(ctx, ctx.Command.Name) + os.Exit(1) + } + } else { + pType = ctx.String("plugin-type") + pName = ctx.String("plugin-name") + pVer = ctx.Int("plugin-version") + } + if pType == "" { + fmt.Println("Must provide plugin type") + cli.ShowCommandHelp(ctx, ctx.Command.Name) + os.Exit(1) + } + if pName == "" { + fmt.Println("Must provide plugin name") + cli.ShowCommandHelp(ctx, ctx.Command.Name) + os.Exit(1) + } + if pVer < 1 { + fmt.Println("Must provide plugin version") + cli.ShowCommandHelp(ctx, ctx.Command.Name) + os.Exit(1) + } + + r := pClient.SwapPlugin(paths, pType, pName, pVer) + if r.Err != nil { + fmt.Printf("Error swapping plugins:\n%v\n", r.Err.Error()) + os.Exit(1) + } + + fmt.Println("Plugin loaded") + fmt.Printf("Name: %s\n", r.LoadedPlugin.Name) + fmt.Printf("Version: %d\n", r.LoadedPlugin.Version) + fmt.Printf("Type: %s\n", r.LoadedPlugin.Type) + fmt.Printf("Signed: %v\n", r.LoadedPlugin.Signed) + fmt.Printf("Loaded Time: %s\n\n", r.LoadedPlugin.LoadedTime().Format(timeFormat)) + + fmt.Println("\nPlugin unloaded") + fmt.Printf("Name: %s\n", r.UnloadedPlugin.Name) + fmt.Printf("Version: %d\n", r.UnloadedPlugin.Version) + fmt.Printf("Type: %s\n", r.UnloadedPlugin.Type) +} + func listPlugins(ctx *cli.Context) { plugins := pClient.GetPlugins(ctx.Bool("running")) if plugins.Err != nil { diff --git a/mgmt/rest/client/client_func_test.go b/mgmt/rest/client/client_func_test.go index d8a37ff19..efbd8ac18 100644 --- a/mgmt/rest/client/client_func_test.go +++ b/mgmt/rest/client/client_func_test.go @@ -205,6 +205,49 @@ func TestSnapClient(t *testing.T) { }) }) + Convey("SwapPlugins", t, func() { + Convey("Swap with different types should fail", func() { + sp := c.SwapPlugin(FILE_PLUGIN_PATH, p1.LoadedPlugins[0].Type, p1.LoadedPlugins[0].Name, p1.LoadedPlugins[0].Version) + So(sp.Err, ShouldNotBeNil) + So(sp.Err.Error(), ShouldEqual, "Plugins do not have the same type and name.") + lps := c.GetPlugins(false) + So(len(lps.LoadedPlugins), ShouldEqual, 1) + }) + Convey("Swap with same plugin should fail", func() { + sp := c.SwapPlugin(MOCK_PLUGIN_PATH1, p1.LoadedPlugins[0].Type, p1.LoadedPlugins[0].Name, p1.LoadedPlugins[0].Version) + So(sp.Err, ShouldNotBeNil) + So(sp.Err.Error(), ShouldEqual, "plugin is already loaded") + lps := c.GetPlugins(false) + So(len(lps.LoadedPlugins), ShouldEqual, 1) + }) + Convey("Swap with plugin that is not loaded should fail", func() { + sp := c.SwapPlugin(MOCK_PLUGIN_PATH1, "collector", "mock", 2) + So(sp.Err.Error(), ShouldEqual, "plugin not found collector:mock:2") + So(sp.Err, ShouldNotBeNil) + }) + Convey("Swap with plugins with the same type and name", func() { + sp := c.SwapPlugin(MOCK_PLUGIN_PATH2, p1.LoadedPlugins[0].Type, p1.LoadedPlugins[0].Name, p1.LoadedPlugins[0].Version) + So(sp.Err, ShouldBeNil) + lps := c.GetPlugins(false) + So(len(lps.LoadedPlugins), ShouldEqual, 1) + So(lps.LoadedPlugins[0].Type, ShouldEqual, "collector") + So(lps.LoadedPlugins[0].Name, ShouldEqual, "mock") + So(lps.LoadedPlugins[0].Type, ShouldEqual, p1.LoadedPlugins[0].Type) + So(lps.LoadedPlugins[0].Name, ShouldEqual, p1.LoadedPlugins[0].Name) + So(lps.LoadedPlugins[0].Version, ShouldNotEqual, p1.LoadedPlugins[0].Version) + + sp2 := c.SwapPlugin(MOCK_PLUGIN_PATH1, sp.LoadedPlugin.Type, sp.LoadedPlugin.Name, sp.LoadedPlugin.Version) + So(sp2.Err, ShouldBeNil) + lps2 := c.GetPlugins(false) + So(len(lps.LoadedPlugins), ShouldEqual, 1) + So(lps2.LoadedPlugins[0].Type, ShouldEqual, "collector") + So(lps2.LoadedPlugins[0].Name, ShouldEqual, "mock") + So(lps2.LoadedPlugins[0].Type, ShouldEqual, sp.LoadedPlugin.Type) + So(lps2.LoadedPlugins[0].Name, ShouldEqual, sp.LoadedPlugin.Name) + So(lps2.LoadedPlugins[0].Version, ShouldNotEqual, sp.LoadedPlugin.Version) + }) + }) + if cerr == nil { p2 = c.LoadPlugin(MOCK_PLUGIN_PATH2) } diff --git a/mgmt/rest/client/plugin.go b/mgmt/rest/client/plugin.go index eec524c7a..77ee4b492 100644 --- a/mgmt/rest/client/plugin.go +++ b/mgmt/rest/client/plugin.go @@ -20,8 +20,10 @@ limitations under the License. package client import ( + "errors" "fmt" "net/url" + "strconv" "time" "github.com/intelsdi-x/snap/core/serror" @@ -78,6 +80,50 @@ func (c *Client) UnloadPlugin(pluginType, name string, version int) *UnloadPlugi return r } +// SwapPlugin swaps two plugins with the same type and name e.g. collector:mock:1 with collector:mock:2 +func (c *Client) SwapPlugin(loadPath []string, unloadType, unloadName string, unloadVersion int) *SwapPluginsResult { + r := &SwapPluginsResult{} + + // Check if plugin you are trying to unload is loaded + rp := c.GetPlugin(unloadType, unloadName, unloadVersion) + if rp.Err != nil { + r.Err = fmt.Errorf("%v %v:%v:%v", rp.Err.Error(), unloadType, unloadName, unloadVersion) + return r + } + // Load plugin + lp := c.LoadPlugin(loadPath) + if lp.Err != nil { + r.Err = errors.New(lp.Err.Error()) + return r + } + lpr := lp.LoadedPlugins[0].LoadedPlugin + + // Make sure both plugins have the same type and name before unloading. If not, rollback. + if lpr.Type != unloadType || lpr.Name != unloadName { + up := c.UnloadPlugin(lpr.Type, lpr.Name, lpr.Version) + if up.Err != nil { + r.Err = errors.New("Plugins do not have the same type and name. Failed to rollback after error.") + return r + } + r.Err = errors.New("Plugins do not have the same type and name.") + return r + } + // Unload plugin + up := c.UnloadPlugin(unloadType, unloadName, unloadVersion) + if up.Err != nil { + r.Err = up.Err + up2 := c.UnloadPlugin(lpr.Type, lpr.Name, lpr.Version) + if up2.Err != nil { + r.Err = errors.New("Failed to rollback after error unloading plugin.") + } + return r + } + upr := up.PluginUnloaded + r.LoadedPlugin = lp.LoadedPlugins[0] + r.UnloadedPlugin = upr + return r +} + // GetPlugins returns the loaded and available plugins through an HTTP GET request. // By specifying the details flag to tweak output info. An error returns if it failed. func (c *Client) GetPlugins(details bool) *GetPluginsResult { @@ -112,6 +158,39 @@ func (c *Client) GetPlugins(details bool) *GetPluginsResult { return r } +// GetPlugin returns the requested plugin through an HTTP GET request. An error returns if it failed. +func (c *Client) GetPlugin(typ, name string, ver int) *GetPluginResult { + r := &GetPluginResult{} + + path := "/plugins/" + typ + "/" + name + "/" + strconv.Itoa(ver) + + resp, err := c.do("GET", path, ContentTypeJSON) + if err != nil { + r.Err = err + return r + } + + switch resp.Meta.Type { + // TODO change this to concrete const type when Joel adds it + case rbody.PluginReturnedType: + // Success + b := resp.Body.(*rbody.PluginReturned) + r.ReturnedPlugin = ReturnedPlugin{b} + return r + case rbody.ErrorType: + r.Err = resp.Body.(*rbody.Error) + default: + r.Err = ErrAPIResponseMetaType + } + return r +} + +// GetPluginResult +type GetPluginResult struct { + ReturnedPlugin ReturnedPlugin + Err error +} + // GetPluginsResult is the response from snap/client on a GetPlugins call. type GetPluginsResult struct { LoadedPlugins []LoadedPlugin @@ -131,6 +210,12 @@ type UnloadPluginResult struct { Err error } +type SwapPluginsResult struct { + LoadedPlugin LoadedPlugin + UnloadedPlugin *rbody.PluginUnloaded + Err error +} + // We wrap this so we can provide some functionality (like LoadedTime) type LoadedPlugin struct { *rbody.LoadedPlugin @@ -146,6 +231,11 @@ type AvailablePlugin struct { *rbody.AvailablePlugin } +// The wrapper for ReturnedPlugin struct defined inside rbody package. +type ReturnedPlugin struct { + *rbody.PluginReturned +} + func convertLoadedPlugins(r []rbody.LoadedPlugin) []LoadedPlugin { lp := make([]LoadedPlugin, len(r)) for i := range r { diff --git a/mgmt/rest/rbody/body.go b/mgmt/rest/rbody/body.go index 3af9835ba..7d26981dc 100644 --- a/mgmt/rest/rbody/body.go +++ b/mgmt/rest/rbody/body.go @@ -82,6 +82,8 @@ func UnmarshalBody(t string, b []byte) (Body, error) { return unmarshalAndHandleError(b, &PluginsLoaded{}) case PluginUnloadedType: return unmarshalAndHandleError(b, &PluginUnloaded{}) + case PluginReturnedType: + return unmarshalAndHandleError(b, &PluginReturned{}) case ScheduledTaskListReturnedType: return unmarshalAndHandleError(b, &ScheduledTaskListReturned{}) case ScheduledTaskReturnedType: