libcni: up-convert a Config to a ConfigList when no other configs are found.
authorCasey Callendrello <casey.Callendrello@coreos.com>
Fri, 17 Feb 2017 11:19:38 +0000 (12:19 +0100)
committerCasey Callendrello <casey.Callendrello@coreos.com>
Fri, 24 Feb 2017 18:44:06 +0000 (19:44 +0100)
cnitool/cni.go
libcni/conf.go
libcni/conf_test.go

index 691a02e..81128d5 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2015 CoreOS, Inc.
+// Copyright 2015 CNI authors
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -42,7 +42,7 @@ func main() {
        if netdir == "" {
                netdir = DefaultNetDir
        }
-       netconf, err := libcni.LoadConf(netdir, os.Args[2])
+       netconf, err := libcni.LoadConfList(netdir, os.Args[2])
        if err != nil {
                exit(err)
        }
@@ -61,10 +61,13 @@ func main() {
 
        switch os.Args[1] {
        case CmdAdd:
-               _, err := cninet.AddNetwork(netconf, rt)
+               result, err := cninet.AddNetworkList(netconf, rt)
+               if result != nil {
+                       _ = result.Print()
+               }
                exit(err)
        case CmdDel:
-               exit(cninet.DelNetwork(netconf, rt))
+               exit(cninet.DelNetworkList(netconf, rt))
        }
 }
 
index 4a9d11b..8257d9f 100644 (file)
@@ -16,6 +16,7 @@ package libcni
 
 import (
        "encoding/json"
+       "errors"
        "fmt"
        "io/ioutil"
        "os"
@@ -23,6 +24,17 @@ import (
        "sort"
 )
 
+type NotFoundError struct {
+       Dir  string
+       Name string
+}
+
+func (e NotFoundError) Error() string {
+       return fmt.Sprintf(`no net configuration with name "%s" in %s`, e.Name, e.Dir)
+}
+
+var NoConfigsFoundError = errors.New("no net configurations found")
+
 func ConfFromBytes(bytes []byte) (*NetworkConfig, error) {
        conf := &NetworkConfig{Bytes: bytes}
        if err := json.Unmarshal(bytes, &conf.Network); err != nil {
@@ -137,7 +149,7 @@ func LoadConf(dir, name string) (*NetworkConfig, error) {
        case err != nil:
                return nil, err
        case len(files) == 0:
-               return nil, fmt.Errorf("no net configurations found")
+               return nil, NoConfigsFoundError
        }
        sort.Strings(files)
 
@@ -150,16 +162,13 @@ func LoadConf(dir, name string) (*NetworkConfig, error) {
                        return conf, nil
                }
        }
-       return nil, fmt.Errorf(`no net configuration with name "%s" in %s`, name, dir)
+       return nil, NotFoundError{dir, name}
 }
 
 func LoadConfList(dir, name string) (*NetworkConfigList, error) {
        files, err := ConfFiles(dir, []string{".conflist"})
-       switch {
-       case err != nil:
+       if err != nil {
                return nil, err
-       case len(files) == 0:
-               return nil, fmt.Errorf("no net configuration lists found")
        }
        sort.Strings(files)
 
@@ -172,7 +181,24 @@ func LoadConfList(dir, name string) (*NetworkConfigList, error) {
                        return conf, nil
                }
        }
-       return nil, fmt.Errorf(`no net configuration list with name "%s" in %s`, name, dir)
+
+       // Try and load a network configuration file (instead of list)
+       // from the same name, then upconvert.
+       singleConf, err := LoadConf(dir, name)
+       if err != nil {
+               // A little extra logic so the error makes sense
+               switch {
+               // neither configlists nor config files found
+               case len(files) == 0 && err == NoConfigsFoundError:
+                       return nil, err
+               // config lists found but no config files found
+               case len(files) != 0 && err == NoConfigsFoundError:
+                       return nil, NotFoundError{dir, name}
+               default: // either not found or parse error
+                       return nil, err
+               }
+       }
+       return ConfListFromConf(singleConf)
 }
 
 func InjectConf(original *NetworkConfig, key string, newValue interface{}) (*NetworkConfig, error) {
@@ -199,3 +225,29 @@ func InjectConf(original *NetworkConfig, key string, newValue interface{}) (*Net
 
        return ConfFromBytes(newBytes)
 }
+
+// ConfListFromConf "upconverts" a network config in to a NetworkConfigList,
+// with the single network as the only entry in the list.
+func ConfListFromConf(original *NetworkConfig) (*NetworkConfigList, error) {
+       // Re-deserialize the config's json, then make a raw map configlist.
+       // This may seem a bit strange, but it's to make the Bytes fields
+       // actually make sense. Otherwise, the generated json is littered with
+       // golang default values.
+
+       rawConfig := make(map[string]interface{})
+       if err := json.Unmarshal(original.Bytes, &rawConfig); err != nil {
+               return nil, err
+       }
+
+       rawConfigList := map[string]interface{}{
+               "name":       original.Network.Name,
+               "cniVersion": original.Network.CNIVersion,
+               "plugins":    []interface{}{rawConfig},
+       }
+
+       b, err := json.Marshal(rawConfigList)
+       if err != nil {
+               return nil, err
+       }
+       return ConfListFromBytes(b)
+}
index 466e440..de68d98 100644 (file)
@@ -184,6 +184,32 @@ var _ = Describe("Loading configuration from disk", func() {
                        }))
                })
 
+               Context("when there is a config file with the same name as the list", func() {
+                       BeforeEach(func() {
+                               configFile := []byte(`{
+                                       "name": "some-list",
+                                       "cniVersion": "0.2.0",
+                                       "type": "bridge"
+                               }`)
+                               Expect(ioutil.WriteFile(filepath.Join(configDir, "49-whatever.conf"), configFile, 0600)).To(Succeed())
+                       })
+
+                       It("Loads the config list first", func() {
+                               netConfigList, err := libcni.LoadConfList(configDir, "some-list")
+                               Expect(err).NotTo(HaveOccurred())
+                               Expect(len(netConfigList.Plugins)).To(Equal(3))
+                       })
+
+                       It("falls back to the config file", func() {
+                               Expect(os.Remove(filepath.Join(configDir, "50-whatever.conflist"))).To(Succeed())
+
+                               netConfigList, err := libcni.LoadConfList(configDir, "some-list")
+                               Expect(err).NotTo(HaveOccurred())
+                               Expect(len(netConfigList.Plugins)).To(Equal(1))
+                               Expect(netConfigList.Plugins[0].Network.Type).To(Equal("bridge"))
+                       })
+               })
+
                Context("when the config directory does not exist", func() {
                        BeforeEach(func() {
                                Expect(os.RemoveAll(configDir)).To(Succeed())
@@ -191,14 +217,14 @@ var _ = Describe("Loading configuration from disk", func() {
 
                        It("returns a useful error", func() {
                                _, err := libcni.LoadConfList(configDir, "some-plugin")
-                               Expect(err).To(MatchError("no net configuration lists found"))
+                               Expect(err).To(MatchError("no net configurations found"))
                        })
                })
 
                Context("when there is no config for the desired plugin list", func() {
                        It("returns a useful error", func() {
                                _, err := libcni.LoadConfList(configDir, "some-other-plugin")
-                               Expect(err).To(MatchError(ContainSubstring(`no net configuration list with name "some-other-plugin" in`)))
+                               Expect(err).To(MatchError(libcni.NotFoundError{configDir, "some-other-plugin"}))
                        })
                })
 
@@ -233,7 +259,7 @@ var _ = Describe("Loading configuration from disk", func() {
 
                        It("will not find the config", func() {
                                _, err := libcni.LoadConfList(configDir, "deep")
-                               Expect(err).To(MatchError(HavePrefix("no net configuration list with name")))
+                               Expect(err).To(MatchError(HavePrefix("no net configuration with name")))
                        })
                })
        })
@@ -339,3 +365,40 @@ var _ = Describe("Loading configuration from disk", func() {
                })
        })
 })
+
+var _ = Describe("ConfListFromConf", func() {
+       var testNetConfig *libcni.NetworkConfig
+
+       BeforeEach(func() {
+               pb := []byte(`{"name":"some-plugin","cniVersion":"0.3.0" }`)
+               tc, err := libcni.ConfFromBytes(pb)
+               Expect(err).NotTo(HaveOccurred())
+               testNetConfig = tc
+       })
+
+       It("correctly upconverts a NetworkConfig to a NetworkConfigList", func() {
+               ncl, err := libcni.ConfListFromConf(testNetConfig)
+               Expect(err).NotTo(HaveOccurred())
+               bytes := ncl.Bytes
+
+               // null out the json - we don't care about the exact marshalling
+               ncl.Bytes = nil
+               ncl.Plugins[0].Bytes = nil
+               testNetConfig.Bytes = nil
+
+               Expect(ncl).To(Equal(&libcni.NetworkConfigList{
+                       Name:       "some-plugin",
+                       CNIVersion: "0.3.0",
+                       Plugins:    []*libcni.NetworkConfig{testNetConfig},
+               }))
+
+               //Test that the json unmarshals to the same data
+               ncl2, err := libcni.ConfListFromBytes(bytes)
+               Expect(err).NotTo(HaveOccurred())
+               ncl2.Bytes = nil
+               ncl2.Plugins[0].Bytes = nil
+
+               Expect(ncl2).To(Equal(ncl))
+       })
+
+})