From 6e6ad53ea8be1ee3dbc8c2eca2500fe9e2dbbeb5 Mon Sep 17 00:00:00 2001 From: Casey Callendrello Date: Fri, 17 Feb 2017 12:19:38 +0100 Subject: [PATCH] libcni: up-convert a Config to a ConfigList when no other configs are found. --- cnitool/cni.go | 11 +++++--- libcni/conf.go | 66 ++++++++++++++++++++++++++++++++++++++----- libcni/conf_test.go | 69 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 132 insertions(+), 14 deletions(-) diff --git a/cnitool/cni.go b/cnitool/cni.go index 691a02e0..81128d5e 100644 --- a/cnitool/cni.go +++ b/cnitool/cni.go @@ -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)) } } diff --git a/libcni/conf.go b/libcni/conf.go index 4a9d11be..8257d9f1 100644 --- a/libcni/conf.go +++ b/libcni/conf.go @@ -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) +} diff --git a/libcni/conf_test.go b/libcni/conf_test.go index 466e440b..de68d98c 100644 --- a/libcni/conf_test.go +++ b/libcni/conf_test.go @@ -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)) + }) + +})