From d5e2e375d47a409347ccf311c734e935f59394e6 Mon Sep 17 00:00:00 2001 From: Gabe Rosenhouse Date: Tue, 6 Sep 2016 11:22:27 -0400 Subject: [PATCH] versioning: misc cleanups highlights: - NetConf struct finally includes cniVersion field - improve test coverage of current version report behavior - godoc a few key functions - allow tests to control version list reported by no-op plugin --- libcni/api.go | 4 + pkg/invoke/exec.go | 6 +- ...est.go => get_version_integration_test.go} | 17 ++++ pkg/types/types.go | 2 + pkg/version/plugin.go | 77 +++++++++++++++++++ .../{version_test.go => plugin_test.go} | 6 +- pkg/version/version.go | 62 --------------- plugins/test/noop/debug/debug.go | 16 ++-- plugins/test/noop/main.go | 21 ++++- plugins/test/noop/noop_test.go | 25 +++++- 10 files changed, 162 insertions(+), 74 deletions(-) rename pkg/invoke/{exec_integration_test.go => get_version_integration_test.go} (78%) create mode 100644 pkg/version/plugin.go rename pkg/version/{version_test.go => plugin_test.go} (94%) diff --git a/libcni/api.go b/libcni/api.go index 273123d8..8ba78b26 100644 --- a/libcni/api.go +++ b/libcni/api.go @@ -43,6 +43,7 @@ type CNIConfig struct { Path []string } +// AddNetwork executes the plugin with the ADD command func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Result, error) { pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) if err != nil { @@ -52,6 +53,7 @@ func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Resu return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt)) } +// DelNetwork executes the plugin with the DEL command func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error { pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) if err != nil { @@ -61,6 +63,8 @@ func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error { return invoke.ExecPluginWithoutResult(pluginPath, net.Bytes, c.args("DEL", rt)) } +// GetVersionInfo reports which versions of the CNI spec are supported by +// the given plugin. func (c *CNIConfig) GetVersionInfo(pluginType string) (version.PluginInfo, error) { pluginPath, err := invoke.FindInPath(pluginType, c.Path) if err != nil { diff --git a/pkg/invoke/exec.go b/pkg/invoke/exec.go index 68efbb9b..7eb06156 100644 --- a/pkg/invoke/exec.go +++ b/pkg/invoke/exec.go @@ -36,7 +36,7 @@ func GetVersionInfo(pluginPath string) (version.PluginInfo, error) { var defaultPluginExec = &PluginExec{ RawExec: &RawExec{Stderr: os.Stderr}, - VersionDecoder: &version.Decoder{}, + VersionDecoder: &version.PluginDecoder{}, } type PluginExec struct { @@ -64,6 +64,10 @@ func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIAr return err } +// GetVersionInfo returns the version information available about the plugin. +// For recent-enough plugins, it uses the information returned by the VERSION +// command. For older plugins which do not recognize that command, it reports +// version 0.1.0 func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, error) { args := &Args{ Command: "VERSION", diff --git a/pkg/invoke/exec_integration_test.go b/pkg/invoke/get_version_integration_test.go similarity index 78% rename from pkg/invoke/exec_integration_test.go rename to pkg/invoke/get_version_integration_test.go index f02374c0..d10826dd 100644 --- a/pkg/invoke/exec_integration_test.go +++ b/pkg/invoke/get_version_integration_test.go @@ -54,9 +54,26 @@ var _ = Describe("GetVersion, integration tests", func() { }, Entry("old plugin, before VERSION was introduced", git_ref_v010, plugin_source_v010, version.PluginSupports("0.1.0")), Entry("when VERSION was introduced", git_ref_v020, plugin_source_v010, version.PluginSupports("0.1.0", "0.2.0")), + Entry("when plugins report their own version support", git_ref_v030, plugin_source_v030, version.PluginSupports("0.3.0", "0.999.0")), + Entry("HEAD", "HEAD", plugin_source_v030, version.PluginSupports("0.3.0", "0.999.0")), ) }) +// a 0.3.0 plugin that can report its own versions +const plugin_source_v030 = `package main + +import ( + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/version" + "fmt" +) + +func c(_ *skel.CmdArgs) error { fmt.Println("{}"); return nil } + +func main() { skel.PluginMain(c, c, version.PluginSupports("0.3.0", "0.999.0")) } +` +const git_ref_v030 = "bf31ed15" + // a minimal 0.1.0 / 0.2.0 plugin const plugin_source_v010 = `package main diff --git a/pkg/types/types.go b/pkg/types/types.go index 6948dcb1..ba02580a 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -57,6 +57,8 @@ func (n *IPNet) UnmarshalJSON(data []byte) error { // NetConf describes a network. type NetConf struct { + CNIVersion string `json:"cniVersion,omitempty"` + Name string `json:"name,omitempty"` Type string `json:"type,omitempty"` IPAM struct { diff --git a/pkg/version/plugin.go b/pkg/version/plugin.go new file mode 100644 index 00000000..9bd7dc83 --- /dev/null +++ b/pkg/version/plugin.go @@ -0,0 +1,77 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import ( + "encoding/json" + "fmt" + "io" +) + +// PluginInfo reports information about CNI versioning +type PluginInfo interface { + // SupportedVersions returns one or more CNI spec versions that the plugin + // supports. If input is provided in one of these versions, then the plugin + // promises to use the same CNI version in its response + SupportedVersions() []string + + // Encode writes this CNI version information as JSON to the given Writer + Encode(io.Writer) error +} + +type pluginInfo struct { + CNIVersion_ string `json:"cniVersion"` + SupportedVersions_ []string `json:"supportedVersions,omitempty"` +} + +func (p *pluginInfo) Encode(w io.Writer) error { + return json.NewEncoder(w).Encode(p) +} + +func (p *pluginInfo) SupportedVersions() []string { + return p.SupportedVersions_ +} + +// PluginSupports returns a new PluginInfo that will report the given versions +// as supported +func PluginSupports(supportedVersions ...string) PluginInfo { + if len(supportedVersions) < 1 { + panic("programmer error: you must support at least one version") + } + return &pluginInfo{ + CNIVersion_: Current(), + SupportedVersions_: supportedVersions, + } +} + +type PluginDecoder struct{} + +func (_ *PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) { + var info pluginInfo + err := json.Unmarshal(jsonBytes, &info) + if err != nil { + return nil, fmt.Errorf("decoding version info: %s", err) + } + if info.CNIVersion_ == "" { + return nil, fmt.Errorf("decoding version info: missing field cniVersion") + } + if len(info.SupportedVersions_) == 0 { + if info.CNIVersion_ == "0.2.0" { + return PluginSupports("0.1.0", "0.2.0"), nil + } + return nil, fmt.Errorf("decoding version info: missing field supportedVersions") + } + return &info, nil +} diff --git a/pkg/version/version_test.go b/pkg/version/plugin_test.go similarity index 94% rename from pkg/version/version_test.go rename to pkg/version/plugin_test.go index 98a386d7..a58bd35a 100644 --- a/pkg/version/version_test.go +++ b/pkg/version/plugin_test.go @@ -20,11 +20,11 @@ import ( . "github.com/onsi/gomega" ) -var _ = Describe("Decode", func() { - var decoder *version.Decoder +var _ = Describe("Decoding versions reported by a plugin", func() { + var decoder *version.PluginDecoder BeforeEach(func() { - decoder = &version.Decoder{} + decoder = &version.PluginDecoder{} }) It("returns a PluginInfo that represents the given json bytes", func() { diff --git a/pkg/version/version.go b/pkg/version/version.go index cdb531c0..5f937f7e 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -14,73 +14,11 @@ package version -import ( - "encoding/json" - "fmt" - "io" -) - // Current reports the version of the CNI spec implemented by this library func Current() string { return "0.3.0" } -// PluginInfo reports information about CNI versioning -type PluginInfo interface { - // SupportedVersions returns one or more CNI spec versions that the plugin - // supports. If input is provided in one of these versions, then the plugin - // promises to use the same CNI version in its response - SupportedVersions() []string - - // Encode writes this CNI version information as JSON to the given Writer - Encode(io.Writer) error -} - -type simple struct { - CNIVersion_ string `json:"cniVersion"` - SupportedVersions_ []string `json:"supportedVersions,omitempty"` -} - -func (p *simple) Encode(w io.Writer) error { - return json.NewEncoder(w).Encode(p) -} - -func (p *simple) SupportedVersions() []string { - return p.SupportedVersions_ -} - -// PluginSupports returns a new PluginInfo that will report the given versions -// as supported -func PluginSupports(supportedVersions ...string) PluginInfo { - if len(supportedVersions) < 1 { - panic("programmer error: you must support at least one version") - } - return &simple{ - CNIVersion_: Current(), - SupportedVersions_: supportedVersions, - } -} - -type Decoder struct{} - -func (_ *Decoder) Decode(jsonBytes []byte) (PluginInfo, error) { - var info simple - err := json.Unmarshal(jsonBytes, &info) - if err != nil { - return nil, fmt.Errorf("decoding version info: %s", err) - } - if info.CNIVersion_ == "" { - return nil, fmt.Errorf("decoding version info: missing field cniVersion") - } - if len(info.SupportedVersions_) == 0 { - if info.CNIVersion_ == "0.2.0" { - return PluginSupports("0.1.0", "0.2.0"), nil - } - return nil, fmt.Errorf("decoding version info: missing field supportedVersions") - } - return &info, nil -} - // Legacy PluginInfo describes a plugin that is backwards compatible with the // CNI spec version 0.1.0. In particular, a runtime compiled against the 0.1.0 // library ought to work correctly with a plugin that reports support for diff --git a/plugins/test/noop/debug/debug.go b/plugins/test/noop/debug/debug.go index 71770bd3..5bc6e4f0 100644 --- a/plugins/test/noop/debug/debug.go +++ b/plugins/test/noop/debug/debug.go @@ -24,11 +24,17 @@ import ( // Debug is used to control and record the behavior of the noop plugin type Debug struct { - ReportResult string - ReportError string - ReportStderr string - Command string - CmdArgs skel.CmdArgs + // Report* fields allow the test to control the behavior of the no-op plugin + ReportResult string + ReportError string + ReportStderr string + ReportVersionSupport []string + + // Command stores the CNI command that the plugin received + Command string + + // CmdArgs stores the CNI Args and Env Vars that the plugin recieved + CmdArgs skel.CmdArgs } // ReadDebug will return a debug file recorded by the noop plugin diff --git a/plugins/test/noop/main.go b/plugins/test/noop/main.go index 1ebc7350..6d6237df 100644 --- a/plugins/test/noop/main.go +++ b/plugins/test/noop/main.go @@ -63,6 +63,23 @@ func debugBehavior(args *skel.CmdArgs, command string) error { return nil } +func debugGetSupportedVersions() []string { + vers := []string{"0.-42.0", "0.1.0", "0.2.0", "0.3.0"} + cniArgs := os.Getenv("CNI_ARGS") + if cniArgs == "" { + return vers + } + debugFilePath := strings.TrimPrefix(cniArgs, "DEBUG=") + debug, err := debug.ReadDebug(debugFilePath) + if err != nil { + panic("test setup error: unable to read debug file: " + err.Error()) + } + if debug.ReportVersionSupport == nil { + return vers + } + return debug.ReportVersionSupport +} + func cmdAdd(args *skel.CmdArgs) error { return debugBehavior(args, "ADD") } @@ -72,6 +89,6 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, - version.PluginSupports("0.-42.0", "0.1.0", "0.2.0", "0.3.0")) + supportedVersions := debugGetSupportedVersions() + skel.PluginMain(cmdAdd, cmdDel, version.PluginSupports(supportedVersions...)) } diff --git a/plugins/test/noop/noop_test.go b/plugins/test/noop/noop_test.go index c6ce3dd4..968cc838 100644 --- a/plugins/test/noop/noop_test.go +++ b/plugins/test/noop/noop_test.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/version" noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -38,7 +39,10 @@ var _ = Describe("No-op plugin", func() { const reportResult = `{ "ip4": { "ip": "10.1.2.3/24" }, "dns": {} }` BeforeEach(func() { - debug = &noop_debug.Debug{ReportResult: reportResult} + debug = &noop_debug.Debug{ + ReportResult: reportResult, + ReportVersionSupport: []string{"0.1.0", "0.2.0", "0.3.0"}, + } debugFile, err := ioutil.TempFile("", "cni_debug") Expect(err).NotTo(HaveOccurred()) @@ -122,6 +126,25 @@ var _ = Describe("No-op plugin", func() { Expect(debug.Command).To(Equal("DEL")) Expect(debug.CmdArgs).To(Equal(expectedCmdArgs)) }) + }) + Context("when the CNI_COMMAND is VERSION", func() { + BeforeEach(func() { + cmd.Env[0] = "CNI_COMMAND=VERSION" + debug.ReportVersionSupport = []string{"0.123.0", "0.3.0"} + + Expect(debug.WriteDebug(debugFileName)).To(Succeed()) + }) + + It("claims to support the specified versions", func() { + session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) + Expect(err).NotTo(HaveOccurred()) + Eventually(session).Should(gexec.Exit(0)) + decoder := &version.PluginDecoder{} + pluginInfo, err := decoder.Decode(session.Out.Contents()) + Expect(err).NotTo(HaveOccurred()) + Expect(pluginInfo.SupportedVersions()).To(ConsistOf( + "0.123.0", "0.3.0")) + }) }) })