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
This commit is contained in:
Gabe Rosenhouse 2016-09-06 11:22:27 -04:00
parent bf31ed1591
commit d5e2e375d4
10 changed files with 162 additions and 74 deletions

View File

@ -43,6 +43,7 @@ type CNIConfig struct {
Path []string Path []string
} }
// AddNetwork executes the plugin with the ADD command
func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Result, error) { func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Result, error) {
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
if err != nil { 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)) 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 { func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error {
pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
if err != nil { 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)) 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) { func (c *CNIConfig) GetVersionInfo(pluginType string) (version.PluginInfo, error) {
pluginPath, err := invoke.FindInPath(pluginType, c.Path) pluginPath, err := invoke.FindInPath(pluginType, c.Path)
if err != nil { if err != nil {

View File

@ -36,7 +36,7 @@ func GetVersionInfo(pluginPath string) (version.PluginInfo, error) {
var defaultPluginExec = &PluginExec{ var defaultPluginExec = &PluginExec{
RawExec: &RawExec{Stderr: os.Stderr}, RawExec: &RawExec{Stderr: os.Stderr},
VersionDecoder: &version.Decoder{}, VersionDecoder: &version.PluginDecoder{},
} }
type PluginExec struct { type PluginExec struct {
@ -64,6 +64,10 @@ func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIAr
return err 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) { func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, error) {
args := &Args{ args := &Args{
Command: "VERSION", Command: "VERSION",

View File

@ -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("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 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 // a minimal 0.1.0 / 0.2.0 plugin
const plugin_source_v010 = `package main const plugin_source_v010 = `package main

View File

@ -57,6 +57,8 @@ func (n *IPNet) UnmarshalJSON(data []byte) error {
// NetConf describes a network. // NetConf describes a network.
type NetConf struct { type NetConf struct {
CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
IPAM struct { IPAM struct {

77
pkg/version/plugin.go Normal file
View File

@ -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
}

View File

@ -20,11 +20,11 @@ import (
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
var _ = Describe("Decode", func() { var _ = Describe("Decoding versions reported by a plugin", func() {
var decoder *version.Decoder var decoder *version.PluginDecoder
BeforeEach(func() { BeforeEach(func() {
decoder = &version.Decoder{} decoder = &version.PluginDecoder{}
}) })
It("returns a PluginInfo that represents the given json bytes", func() { It("returns a PluginInfo that represents the given json bytes", func() {

View File

@ -14,73 +14,11 @@
package version package version
import (
"encoding/json"
"fmt"
"io"
)
// Current reports the version of the CNI spec implemented by this library // Current reports the version of the CNI spec implemented by this library
func Current() string { func Current() string {
return "0.3.0" 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 // 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 // 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 // library ought to work correctly with a plugin that reports support for

View File

@ -24,11 +24,17 @@ import (
// Debug is used to control and record the behavior of the noop plugin // Debug is used to control and record the behavior of the noop plugin
type Debug struct { type Debug struct {
ReportResult string // Report* fields allow the test to control the behavior of the no-op plugin
ReportError string ReportResult string
ReportStderr string ReportError string
Command string ReportStderr string
CmdArgs skel.CmdArgs 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 // ReadDebug will return a debug file recorded by the noop plugin

View File

@ -63,6 +63,23 @@ func debugBehavior(args *skel.CmdArgs, command string) error {
return nil 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 { func cmdAdd(args *skel.CmdArgs) error {
return debugBehavior(args, "ADD") return debugBehavior(args, "ADD")
} }
@ -72,6 +89,6 @@ func cmdDel(args *skel.CmdArgs) error {
} }
func main() { func main() {
skel.PluginMain(cmdAdd, cmdDel, supportedVersions := debugGetSupportedVersions()
version.PluginSupports("0.-42.0", "0.1.0", "0.2.0", "0.3.0")) skel.PluginMain(cmdAdd, cmdDel, version.PluginSupports(supportedVersions...))
} }

View File

@ -21,6 +21,7 @@ import (
"strings" "strings"
"github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/version"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug" noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
@ -38,7 +39,10 @@ var _ = Describe("No-op plugin", func() {
const reportResult = `{ "ip4": { "ip": "10.1.2.3/24" }, "dns": {} }` const reportResult = `{ "ip4": { "ip": "10.1.2.3/24" }, "dns": {} }`
BeforeEach(func() { 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") debugFile, err := ioutil.TempFile("", "cni_debug")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
@ -122,6 +126,25 @@ var _ = Describe("No-op plugin", func() {
Expect(debug.Command).To(Equal("DEL")) Expect(debug.Command).To(Equal("DEL"))
Expect(debug.CmdArgs).To(Equal(expectedCmdArgs)) 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"))
})
}) })
}) })