From 6ec627534d854415a1edca3b1132ca9054282fab Mon Sep 17 00:00:00 2001 From: Michael Zappa Date: Fri, 5 Apr 2024 11:41:26 -0600 Subject: [PATCH] status should ensure plugins and conf are present Signed-off-by: Michael Zappa --- .github/workflows/ci.yml | 2 +- cni_test.go | 19 +++++----- errors.go | 14 ++++---- opts.go | 76 ++++++++++++++++++++++++++++++++++++++++ testutils.go | 14 +++++++- 5 files changed, 108 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 086343e..67db40e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,7 +64,7 @@ jobs: - uses: golangci/golangci-lint-action@v3 with: - version: v1.51.1 + version: v1.57.2 working-directory: src/github.com/containerd/go-cni tests: diff --git a/cni_test.go b/cni_test.go index c2b0b2f..2042664 100644 --- a/cni_test.go +++ b/cni_test.go @@ -39,6 +39,7 @@ func TestLibCNIType020(t *testing.T) { // Create a fake cni config directory and file cniDir, confDir := makeFakeCNIConfig(t) defer tearDownCNIConfig(t, cniDir) + l.pluginDirs = []string{cniDir} l.pluginConfDir = confDir // Set the minimum network count as 2 for this test l.networkCount = 2 @@ -99,7 +100,7 @@ func TestLibCNIType020(t *testing.T) { assert.NotNil(t, c.Prefix) assert.Equal(t, "eth", c.Prefix) assert.NotNil(t, c.PluginDirs) - assert.Equal(t, DefaultCNIDir, c.PluginDirs[0]) + assert.Equal(t, cniDir, c.PluginDirs[0]) assert.NotNil(t, c.PluginConfDir) assert.Equal(t, confDir, c.PluginConfDir) assert.NotNil(t, c.Networks) @@ -289,32 +290,32 @@ type MockCNI struct { mock.Mock } -func (m *MockCNI) AddNetwork(ctx context.Context, net *cnilibrary.NetworkConfig, rt *cnilibrary.RuntimeConf) (types.Result, error) { +func (m *MockCNI) AddNetwork(_ context.Context, net *cnilibrary.NetworkConfig, rt *cnilibrary.RuntimeConf) (types.Result, error) { args := m.Called(net, rt) return args.Get(0).(types.Result), args.Error(1) } -func (m *MockCNI) DelNetwork(ctx context.Context, net *cnilibrary.NetworkConfig, rt *cnilibrary.RuntimeConf) error { +func (m *MockCNI) DelNetwork(_ context.Context, net *cnilibrary.NetworkConfig, rt *cnilibrary.RuntimeConf) error { args := m.Called(net, rt) return args.Error(0) } -func (m *MockCNI) DelNetworkList(ctx context.Context, net *cnilibrary.NetworkConfigList, rt *cnilibrary.RuntimeConf) error { +func (m *MockCNI) DelNetworkList(_ context.Context, net *cnilibrary.NetworkConfigList, rt *cnilibrary.RuntimeConf) error { args := m.Called(net, rt) return args.Error(0) } -func (m *MockCNI) AddNetworkList(ctx context.Context, net *cnilibrary.NetworkConfigList, rt *cnilibrary.RuntimeConf) (types.Result, error) { +func (m *MockCNI) AddNetworkList(_ context.Context, net *cnilibrary.NetworkConfigList, rt *cnilibrary.RuntimeConf) (types.Result, error) { args := m.Called(net, rt) return args.Get(0).(types.Result), args.Error(1) } -func (m *MockCNI) CheckNetworkList(ctx context.Context, net *cnilibrary.NetworkConfigList, rt *cnilibrary.RuntimeConf) error { +func (m *MockCNI) CheckNetworkList(_ context.Context, net *cnilibrary.NetworkConfigList, rt *cnilibrary.RuntimeConf) error { args := m.Called(net, rt) return args.Error(0) } -func (m *MockCNI) CheckNetwork(ctx context.Context, net *cnilibrary.NetworkConfig, rt *cnilibrary.RuntimeConf) error { +func (m *MockCNI) CheckNetwork(_ context.Context, net *cnilibrary.NetworkConfig, rt *cnilibrary.RuntimeConf) error { args := m.Called(net, rt) return args.Error(0) } @@ -329,12 +330,12 @@ func (m *MockCNI) GetNetworkCachedResult(net *cnilibrary.NetworkConfig, rt *cnil return args.Get(0).(types.Result), args.Error(1) } -func (m *MockCNI) ValidateNetworkList(ctx context.Context, net *cnilibrary.NetworkConfigList) ([]string, error) { +func (m *MockCNI) ValidateNetworkList(_ context.Context, net *cnilibrary.NetworkConfigList) ([]string, error) { args := m.Called(net) return args.Get(0).([]string), args.Error(1) } -func (m *MockCNI) ValidateNetwork(ctx context.Context, net *cnilibrary.NetworkConfig) ([]string, error) { +func (m *MockCNI) ValidateNetwork(_ context.Context, net *cnilibrary.NetworkConfig) ([]string, error) { args := m.Called(net) return args.Get(0).([]string), args.Error(1) } diff --git a/errors.go b/errors.go index 9c670fe..31b557a 100644 --- a/errors.go +++ b/errors.go @@ -21,12 +21,14 @@ import ( ) var ( - ErrCNINotInitialized = errors.New("cni plugin not initialized") - ErrInvalidConfig = errors.New("invalid cni config") - ErrNotFound = errors.New("not found") - ErrRead = errors.New("failed to read config file") - ErrInvalidResult = errors.New("invalid result") - ErrLoad = errors.New("failed to load cni config") + ErrCNINotInitialized = errors.New("cni plugin not initialized") + ErrInvalidConfig = errors.New("invalid cni config") + ErrNotFound = errors.New("not found") + ErrRead = errors.New("failed to read config file") + ErrInvalidResult = errors.New("invalid result") + ErrLoad = errors.New("failed to load cni config") + ErrCNIPluginNotFound = errors.New("cni plugin not found") + ErrCNIPluginDirNotFound = errors.New("cni plugin directory not found") ) // IsCNINotInitialized returns true if the error is due to cni config not being initialized diff --git a/opts.go b/opts.go index 309d014..b536d35 100644 --- a/opts.go +++ b/opts.go @@ -19,6 +19,7 @@ package cni import ( "fmt" "os" + "path/filepath" "sort" "strings" @@ -94,6 +95,12 @@ func WithLoNetwork(c *libcni) error { }] }`)) + err := checkPluginExists(c, loConfig) + + if err != nil { + return err + } + c.networks = append(c.networks, &Network{ cni: c.cniConfig, config: loConfig, @@ -120,6 +127,13 @@ func WithConfIndex(bytes []byte, index int) Opt { if err != nil { return err } + + err = checkPluginExists(c, confList) + + if err != nil { + return err + } + c.networks = append(c.networks, &Network{ cni: c.cniConfig, config: confList, @@ -143,6 +157,13 @@ func WithConfFile(fileName string) Opt { if err != nil { return err } + + err = checkPluginExists(c, confList) + + if err != nil { + return err + } + c.networks = append(c.networks, &Network{ cni: c.cniConfig, config: confList, @@ -160,6 +181,13 @@ func WithConfListBytes(bytes []byte) Opt { if err != nil { return err } + + err = checkPluginExists(c, confList) + + if err != nil { + return err + } + i := len(c.networks) c.networks = append(c.networks, &Network{ cni: c.cniConfig, @@ -179,6 +207,13 @@ func WithConfListFile(fileName string) Opt { if err != nil { return err } + + err = checkPluginExists(c, confList) + + if err != nil { + return err + } + i := len(c.networks) c.networks = append(c.networks, &Network{ cni: c.cniConfig, @@ -253,13 +288,20 @@ func loadFromConfDir(c *libcni, max int) error { } if len(confList.Plugins) == 0 { return fmt.Errorf("CNI config list in config file %s has no networks, skipping: %w", confFile, ErrInvalidConfig) + } + + err := checkPluginExists(c, confList) + if err != nil { + return err } + networks = append(networks, &Network{ cni: c.cniConfig, config: confList, ifName: getIfName(c.prefix, i), }) + i++ if i == max { break @@ -271,3 +313,37 @@ func loadFromConfDir(c *libcni, max int) error { c.networks = append(c.networks, networks...) return nil } + +func checkPluginExists(c *libcni, confList *cnilibrary.NetworkConfigList) error { + missing := make(map[string]interface{}) + for _, plug := range confList.Plugins { + plugin := plug.Network.Type + for _, dir := range c.pluginDirs { + if !fileExistsInDir(dir, plugin) { + missing[plugin] = plugin + } else { + delete(missing, plugin) + break + } + } + } + + if len(missing) > 0 { + var plugins []string + for k := range missing { + plugins = append(plugins, k) + } + + return fmt.Errorf("unable to find cni plugins %s in directories %s: %v", + strings.Join(plugins, ", "), strings.Join(c.pluginDirs, ", "), ErrCNIPluginNotFound) + } + + return nil +} + +// FileExistsInDir checks if a file exists in a specific directory +func fileExistsInDir(directory, filename string) bool { + filePath := filepath.Join(directory, filename) + _, err := os.Stat(filePath) + return !os.IsExist(err) +} diff --git a/testutils.go b/testutils.go index 0807e20..32b28c8 100644 --- a/testutils.go +++ b/testutils.go @@ -43,6 +43,12 @@ func makeFakeCNIConfig(t *testing.T) (string, string) { t.Fatalf("Failed to create network config dir: %v", err) } + cniPluginDir := path.Join(cniDir, "bin") + err = os.MkdirAll(cniPluginDir, 0777) + if err != nil { + t.Fatalf("Failed to create plugin dir: %v", err) + } + networkConfig1 := path.Join(cniConfDir, "mocknetwork1.conf") f1, err := os.Create(networkConfig1) if err != nil { @@ -54,6 +60,12 @@ func makeFakeCNIConfig(t *testing.T) (string, string) { t.Fatalf("Failed to create network config %v: %v", f2, err) } + file, err := os.Create(path.Join(cniPluginDir, "fakecni")) + if err != nil { + t.Fatalf("Error creating file: %v", err) + } + defer file.Close() + cfg1 := fmt.Sprintf(`{ "name": "%s", "type": "%s", "capabilities": {"portMappings": true} }`, "plugin1", "fakecni") _, err = f1.WriteString(cfg1) if err != nil { @@ -66,7 +78,7 @@ func makeFakeCNIConfig(t *testing.T) (string, string) { t.Fatalf("Failed to write network config file %v: %v", f2, err) } f2.Close() - return cniDir, cniConfDir + return cniPluginDir, cniConfDir } func tearDownCNIConfig(t *testing.T, confDir string) {