From ea6bad25de47372d715014cb8eee9f220be37b92 Mon Sep 17 00:00:00 2001 From: Casey Callendrello Date: Mon, 23 Oct 2023 13:23:19 +0200 Subject: [PATCH] libcni, skel: implement STATUS This adds an implementation of STATUS to libcni and skel, along with tests. Signed-off-by: Casey Callendrello --- libcni/api.go | 36 +++++++++++++++++++++++ libcni/api_test.go | 38 +++++++++++++++++++++++++ pkg/skel/skel.go | 49 ++++++++++++++++++++++++-------- plugins/test/noop/debug/debug.go | 1 + plugins/test/noop/main.go | 23 +++++++++++---- 5 files changed, 129 insertions(+), 18 deletions(-) diff --git a/libcni/api.go b/libcni/api.go index 803c882a..f6b86c1d 100644 --- a/libcni/api.go +++ b/libcni/api.go @@ -114,6 +114,7 @@ type CNI interface { ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error) GCNetworkList(ctx context.Context, net *NetworkConfigList, args *GCArgs) error + GetStatusNetworkList(ctx context.Context, net *NetworkConfigList) error GetCachedAttachments(containerID string) ([]*NetworkAttachment, error) } @@ -829,6 +830,41 @@ func (c *CNIConfig) gcNetwork(ctx context.Context, net *NetworkConfig) error { return invoke.ExecPluginWithoutResult(ctx, pluginPath, net.Bytes, args, c.exec) } +func (c *CNIConfig) GetStatusNetworkList(ctx context.Context, list *NetworkConfigList) error { + // If the version doesn't support status, abort. + if gt, _ := version.GreaterThanOrEqualTo(list.CNIVersion, "1.1.0"); !gt { + return nil + } + + inject := map[string]interface{}{ + "name": list.Name, + "cniVersion": list.CNIVersion, + } + + for _, plugin := range list.Plugins { + // build config here + pluginConfig, err := InjectConf(plugin, inject) + if err != nil { + return fmt.Errorf("failed to generate configuration to get plugin STATUS %s: %w", plugin.Network.Type, err) + } + if err := c.getStatusNetwork(ctx, pluginConfig); err != nil { + return err // Don't collect errors here, so we return a clean error code. + } + } + return nil +} + +func (c *CNIConfig) getStatusNetwork(ctx context.Context, net *NetworkConfig) error { + c.ensureExec() + pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) + if err != nil { + return err + } + args := c.args("STATUS", &RuntimeConf{}) + + return invoke.ExecPluginWithoutResult(ctx, pluginPath, net.Bytes, args, c.exec) +} + // ===== func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args { return &invoke.Args{ diff --git a/libcni/api_test.go b/libcni/api_test.go index 1f552325..1b441e24 100644 --- a/libcni/api_test.go +++ b/libcni/api_test.go @@ -1580,6 +1580,44 @@ var _ = Describe("Invoking plugins", func() { } }) }) + Describe("GetStatusNetworkList", func() { + It("issues a STATUS request", func() { + netConfigList, plugins = makePluginList("1.1.0", ipResult, rcMap) + + err := cniConfig.GetStatusNetworkList(ctx, netConfigList) + Expect(err).NotTo(HaveOccurred()) + + debug, err := noop_debug.ReadDebug(plugins[0].debugFilePath) + Expect(err).NotTo(HaveOccurred()) + Expect(debug.Command).To(Equal("STATUS")) + }) + + It("correctly reports an error", func() { + netConfigList, plugins = makePluginList("1.1.0", ipResult, rcMap) + + plugins[1].debug.ReportError = "plugin error: banana" + plugins[1].debug.ReportErrorCode = 50 + Expect(plugins[1].debug.WriteDebug(plugins[1].debugFilePath)).To(Succeed()) + + err := cniConfig.GetStatusNetworkList(ctx, netConfigList) + Expect(err).To(HaveOccurred()) + var eerr *types.Error + Expect(errors.As(err, &eerr)).To(BeTrue()) + Expect(eerr.Code).To(Equal(uint(50))) + + debug, err := noop_debug.ReadDebug(plugins[0].debugFilePath) + Expect(err).NotTo(HaveOccurred()) + Expect(debug.Command).To(Equal("STATUS")) + + debug, err = noop_debug.ReadDebug(plugins[1].debugFilePath) + Expect(err).NotTo(HaveOccurred()) + Expect(debug.Command).To(Equal("STATUS")) + + debug, err = noop_debug.ReadDebug(plugins[2].debugFilePath) + Expect(err).NotTo(HaveOccurred()) + Expect(debug.Command).To(Equal("")) + }) + }) }) Describe("Invoking a sleep plugin", func() { diff --git a/pkg/skel/skel.go b/pkg/skel/skel.go index 432923dc..7e881545 100644 --- a/pkg/skel/skel.go +++ b/pkg/skel/skel.go @@ -69,10 +69,11 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) { "CNI_COMMAND", &cmd, reqForCmdEntry{ - "ADD": true, - "CHECK": true, - "DEL": true, - "GC": true, + "ADD": true, + "CHECK": true, + "DEL": true, + "GC": true, + "STATUS": true, }, nil, }, @@ -120,10 +121,11 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) { "CNI_PATH", &path, reqForCmdEntry{ - "ADD": true, - "CHECK": true, - "DEL": true, - "GC": true, + "ADD": true, + "CHECK": true, + "DEL": true, + "GC": true, + "STATUS": true, }, nil, }, @@ -310,6 +312,28 @@ func (t *dispatcher) pluginMain(funcs CNIFuncs, versionInfo version.PluginInfo, } } return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow GC", "") + case "STATUS": + configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData) + if err != nil { + return types.NewError(types.ErrDecodingFailure, err.Error(), "") + } + if gtet, err := version.GreaterThanOrEqualTo(configVersion, "1.1.0"); err != nil { + return types.NewError(types.ErrDecodingFailure, err.Error(), "") + } else if !gtet { + return types.NewError(types.ErrIncompatibleCNIVersion, "config version does not allow STATUS", "") + } + for _, pluginVersion := range versionInfo.SupportedVersions() { + gtet, err := version.GreaterThanOrEqualTo(pluginVersion, configVersion) + if err != nil { + return types.NewError(types.ErrDecodingFailure, err.Error(), "") + } else if gtet { + if err := t.checkVersionAndCall(cmdArgs, versionInfo, funcs.Status); err != nil { + return err + } + return nil + } + } + return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow STATUS", "") case "VERSION": if err := versionInfo.Encode(t.Stdout); err != nil { return types.NewError(types.ErrIOFailure, err.Error(), "") @@ -342,10 +366,11 @@ func PluginMainWithError(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error, versio // CNIFuncs contains a group of callback command funcs to be passed in as // parameters to the core "main" for a plugin. type CNIFuncs struct { - Add func(_ *CmdArgs) error - Del func(_ *CmdArgs) error - Check func(_ *CmdArgs) error - GC func(_ *CmdArgs) error + Add func(_ *CmdArgs) error + Del func(_ *CmdArgs) error + Check func(_ *CmdArgs) error + GC func(_ *CmdArgs) error + Status func(_ *CmdArgs) error } // PluginMainFuncsWithError is the core "main" for a plugin. It accepts diff --git a/plugins/test/noop/debug/debug.go b/plugins/test/noop/debug/debug.go index f9d661db..2077d835 100644 --- a/plugins/test/noop/debug/debug.go +++ b/plugins/test/noop/debug/debug.go @@ -29,6 +29,7 @@ type Debug struct { // Report* fields allow the test to control the behavior of the no-op plugin ReportResult string ReportError string + ReportErrorCode uint ReportStderr string ReportVersionSupport []string ExitWithCode int diff --git a/plugins/test/noop/main.go b/plugins/test/noop/main.go index 4bea7541..ab424339 100644 --- a/plugins/test/noop/main.go +++ b/plugins/test/noop/main.go @@ -23,7 +23,6 @@ package main import ( "encoding/json" - "errors" "fmt" "io" "os" @@ -136,7 +135,14 @@ func debugBehavior(args *skel.CmdArgs, command string) error { } switch { case debug.ReportError != "": - return errors.New(debug.ReportError) + ec := debug.ReportErrorCode + if ec == 0 { + ec = types.ErrInternal + } + return &types.Error{ + Msg: debug.ReportError, + Code: ec, + } case debug.ReportResult == "PASSTHROUGH" || debug.ReportResult == "INJECT-DNS": prevResult := netConf.PrevResult if debug.ReportResult == "INJECT-DNS" { @@ -212,6 +218,10 @@ func cmdGC(args *skel.CmdArgs) error { return debugBehavior(args, "GC") } +func cmdStatus(args *skel.CmdArgs) error { + return debugBehavior(args, "STATUS") +} + func saveStdin() ([]byte, error) { // Read original stdin stdinData, err := io.ReadAll(os.Stdin) @@ -244,9 +254,10 @@ func main() { supportedVersions := debugGetSupportedVersions(stdinData) skel.PluginMainFuncs(skel.CNIFuncs{ - Add: cmdAdd, - Check: cmdCheck, - Del: cmdDel, - GC: cmdGC, + Add: cmdAdd, + Check: cmdCheck, + Del: cmdDel, + GC: cmdGC, + Status: cmdStatus, }, version.PluginSupports(supportedVersions...), "CNI noop plugin v0.7.0") }