Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

libcni: up-convert a Config to a ConfigList when no other configs are found. #374

Merged
merged 1 commit into from
Feb 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions cnitool/cni.go
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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)
}
Expand All @@ -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))
}
}

Expand Down
66 changes: 59 additions & 7 deletions libcni/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,25 @@ package libcni

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"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 {
Expand Down Expand Up @@ -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)

Expand All @@ -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)

Expand All @@ -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) {
Expand All @@ -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)
}
69 changes: 66 additions & 3 deletions libcni/conf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,21 +184,47 @@ 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())
})

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"}))
})
})

Expand Down Expand Up @@ -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")))
})
})
})
Expand Down Expand Up @@ -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))
})

})