Compare commits

..

5 Commits

Author SHA1 Message Date
35f3a090b2 pkg/ns: introduce error types indicate NS verification 2016-05-27 13:50:16 +02:00
131ecc4055 Merge pull request #230 from steveeJ/netns-optional-on-del
plugins: don't require CNI_NETNS for DEL command
2016-05-27 13:49:05 +02:00
d582c9ce8f skel/test: add case for empty NETNS 2016-05-27 12:26:42 +02:00
72337159c1 plugins: don't require CNI_NETNS for DEL command
This will allow to free up the IPAM allocations when the caller doesn't
have access to the network namespace anymore, e.g. due to a reboot.
2016-05-27 10:57:39 +02:00
7f90f9d559 pkg/skel: allow arg requriements specified by CMD 2016-05-27 10:56:24 +02:00
8 changed files with 117 additions and 34 deletions

View File

@ -81,11 +81,23 @@ const (
PROCFS_MAGIC = 0x9fa0 PROCFS_MAGIC = 0x9fa0
) )
func IsNS(nspath string) (isNS bool, msg string, err error) { type NSPathNotExistErr struct{ msg string }
func (e NSPathNotExistErr) Error() string { return e.msg }
type NSPathNotNSErr struct{ msg string }
func (e NSPathNotNSErr) Error() string { return e.msg }
func IsNSorErr(nspath string) error {
stat := syscall.Statfs_t{} stat := syscall.Statfs_t{}
if err = syscall.Statfs(nspath, &stat); err != nil { if err := syscall.Statfs(nspath, &stat); err != nil {
err = fmt.Errorf("failed to Statfs %s: %v", nspath, err) if os.IsNotExist(err) {
return err = NSPathNotExistErr{msg: fmt.Sprintf("failed to Statfs %q: %v", nspath, err)}
} else {
err = fmt.Errorf("failed to Statfs %q: %v", nspath, err)
}
return err
} }
switch stat.Type { switch stat.Type {
@ -95,33 +107,29 @@ func IsNS(nspath string) (isNS bool, msg string, err error) {
validPathContent := "ns/" validPathContent := "ns/"
validName := strings.Contains(nspath, validPathContent) validName := strings.Contains(nspath, validPathContent)
if !validName { if !validName {
msg = fmt.Sprintf("path doesn't contain %q", validPathContent) return NSPathNotNSErr{msg: fmt.Sprintf("path %q doesn't contain %q", nspath, validPathContent)}
return
} }
isNS = true
return nil
case NSFS_MAGIC: case NSFS_MAGIC:
// Kernel >= 3.19 // Kernel >= 3.19
isNS = true return nil
default: default:
msg = fmt.Sprintf("unknown FS magic: %x", stat.Type) return NSPathNotNSErr{msg: fmt.Sprintf("unknown FS magic on %q: %x", nspath, stat.Type)}
} }
return
} }
// Returns an object representing the namespace referred to by @path // Returns an object representing the namespace referred to by @path
func GetNS(nspath string) (NetNS, error) { func GetNS(nspath string) (NetNS, error) {
isNS, msg, err := IsNS(nspath) err := IsNSorErr(nspath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !isNS {
return nil, fmt.Errorf("no network namespace detected on %s: %s", nspath, msg)
}
fd, err := os.Open(nspath) fd, err := os.Open(nspath)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to open %v: %v", nspath, err) return nil, err
} }
return &netNS{file: fd}, nil return &netNS{file: fd}, nil

View File

@ -181,8 +181,8 @@ var _ = Describe("Linux namespace operations", func() {
_, err = ns.GetNS(nspath) _, err = ns.GetNS(nspath)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
errString := fmt.Sprintf("%v", err) Expect(err).To(BeAssignableToTypeOf(ns.NSPathNotNSErr{}))
Expect(errString).To(HavePrefix("no network namespace detected on %s", nspath)) Expect(err).NotTo(BeAssignableToTypeOf(ns.NSPathNotExistErr{}))
}) })
}) })
@ -214,13 +214,11 @@ var _ = Describe("Linux namespace operations", func() {
}) })
}) })
Describe("IsNS", func() { Describe("IsNSorErr", func() {
It("should detect a namespace", func() { It("should detect a namespace", func() {
createdNetNS, err := ns.NewNS() createdNetNS, err := ns.NewNS()
isNSFS, msg, err := ns.IsNS(createdNetNS.Path()) err = ns.IsNSorErr(createdNetNS.Path())
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(msg).To(Equal(""))
Expect(isNSFS).To(Equal(true))
}) })
It("should refuse other paths", func() { It("should refuse other paths", func() {
@ -231,9 +229,17 @@ var _ = Describe("Linux namespace operations", func() {
nspath := tempFile.Name() nspath := tempFile.Name()
defer os.Remove(nspath) defer os.Remove(nspath)
isNSFS, _, err := ns.IsNS(nspath) err = ns.IsNSorErr(nspath)
Expect(err).NotTo(HaveOccurred()) Expect(err).To(HaveOccurred())
Expect(isNSFS).To(Equal(false)) Expect(err).To(BeAssignableToTypeOf(ns.NSPathNotNSErr{}))
Expect(err).NotTo(BeAssignableToTypeOf(ns.NSPathNotExistErr{}))
})
It("should error on non-existing paths", func() {
err := ns.IsNSorErr("/tmp/IDoNotExist")
Expect(err).To(HaveOccurred())
Expect(err).To(BeAssignableToTypeOf(ns.NSPathNotExistErr{}))
Expect(err).NotTo(BeAssignableToTypeOf(ns.NSPathNotNSErr{}))
}) })
}) })
}) })

View File

@ -36,28 +36,72 @@ type CmdArgs struct {
StdinData []byte StdinData []byte
} }
type reqForCmdEntry map[string]bool
// PluginMain is the "main" for a plugin. It accepts // PluginMain is the "main" for a plugin. It accepts
// two callback functions for add and del commands. // two callback functions for add and del commands.
func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error) { func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error) {
var cmd, contID, netns, ifName, args, path string var cmd, contID, netns, ifName, args, path string
vars := []struct { vars := []struct {
name string name string
val *string val *string
req bool reqForCmd reqForCmdEntry
}{ }{
{"CNI_COMMAND", &cmd, true}, {
{"CNI_CONTAINERID", &contID, false}, "CNI_COMMAND",
{"CNI_NETNS", &netns, true}, &cmd,
{"CNI_IFNAME", &ifName, true}, reqForCmdEntry{
{"CNI_ARGS", &args, false}, "ADD": true,
{"CNI_PATH", &path, true}, "DEL": true,
},
},
{
"CNI_CONTAINERID",
&contID,
reqForCmdEntry{
"ADD": false,
"DEL": false,
},
},
{
"CNI_NETNS",
&netns,
reqForCmdEntry{
"ADD": true,
"DEL": false,
},
},
{
"CNI_IFNAME",
&ifName,
reqForCmdEntry{
"ADD": true,
"DEL": true,
},
},
{
"CNI_ARGS",
&args,
reqForCmdEntry{
"ADD": false,
"DEL": false,
},
},
{
"CNI_PATH",
&path,
reqForCmdEntry{
"ADD": true,
"DEL": true,
},
},
} }
argsMissing := false argsMissing := false
for _, v := range vars { for _, v := range vars {
*v.val = os.Getenv(v.name) *v.val = os.Getenv(v.name)
if v.req && *v.val == "" { if v.reqForCmd[cmd] && *v.val == "" {
log.Printf("%v env variable missing", v.name) log.Printf("%v env variable missing", v.name)
argsMissing = true argsMissing = true
} }

View File

@ -71,5 +71,14 @@ var _ = Describe("Skel", func() {
// Expect(err).NotTo(HaveOccurred()) // Expect(err).NotTo(HaveOccurred())
// PluginMain(fErr, nil) // PluginMain(fErr, nil)
// }) // })
It("should not fail with DEL and no NETNS and noop callback", func() {
err := os.Setenv("CNI_COMMAND", "DEL")
Expect(err).NotTo(HaveOccurred())
err = os.Unsetenv("CNI_NETNS")
Expect(err).NotTo(HaveOccurred())
PluginMain(nil, fNoop)
})
}) })
}) })

View File

@ -289,6 +289,10 @@ func cmdDel(args *skel.CmdArgs) error {
return err return err
} }
if args.Netns == "" {
return nil
}
var ipn *net.IPNet var ipn *net.IPNet
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error { err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
var err error var err error

View File

@ -152,6 +152,10 @@ func cmdDel(args *skel.CmdArgs) error {
return err return err
} }
if args.Netns == "" {
return nil
}
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error { return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
return ip.DelLinkByName(args.IfName) return ip.DelLinkByName(args.IfName)
}) })

View File

@ -170,6 +170,10 @@ func cmdDel(args *skel.CmdArgs) error {
return err return err
} }
if args.Netns == "" {
return nil
}
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error { return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
return ip.DelLinkByName(args.IfName) return ip.DelLinkByName(args.IfName)
}) })

View File

@ -199,6 +199,10 @@ func cmdDel(args *skel.CmdArgs) error {
return err return err
} }
if args.Netns == "" {
return nil
}
var ipn *net.IPNet var ipn *net.IPNet
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error { err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
var err error var err error