Skip to content

Commit

Permalink
Merge pull request #1388 from Luap99/netavark-plugin
Browse files Browse the repository at this point in the history
Netavark plugin support
  • Loading branch information
openshift-merge-robot authored Apr 4, 2023
2 parents 79b782d + e87d645 commit 4b81e08
Show file tree
Hide file tree
Showing 18 changed files with 355 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ contrib/spec/podman.spec
build
./generate
*.coverprofile
/bin
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ ifneq ($(shell uname -s), Darwin)
GOARCH=386 $(GO_BUILD) -tags $(BUILDTAGS) ./...
endif

.PHONY: bin/netavark-testplugin
bin/netavark-testplugin:
$(GO_BUILD) -o $@ ./libnetwork/netavark/testplugin/

.PHONY: netavark-testplugin
netavark-testplugin: bin/netavark-testplugin

.PHONY: docs
docs:
$(MAKE) -C docs
Expand Down Expand Up @@ -90,7 +97,7 @@ install:
test: test-unit

.PHONY: test-unit
test-unit:
test-unit: netavark-testplugin
go test --tags $(BUILDTAGS) -v ./libimage
go test --tags $(BUILDTAGS) -v ./libnetwork/...
go test --tags $(BUILDTAGS) -v ./pkg/...
Expand Down
14 changes: 14 additions & 0 deletions docs/containers.conf.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,20 @@ cni_plugin_dirs = [
]
```

**netavark_plugin_dirs**=[]

List of directories that will be searched for netavark plugins.

The default list is:
```
netavark_plugin_dirs = [
"/usr/local/libexec/netavark",
"/usr/libexec/netavark",
"/usr/local/lib/netavark",
"/usr/lib/netavark",
]
```

**default_network**="podman"

The network name of the default network to attach pods to.
Expand Down
58 changes: 57 additions & 1 deletion libnetwork/netavark/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,11 @@ func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bo
return nil, err
}
default:
return nil, fmt.Errorf("unsupported driver %s: %w", newNetwork.Driver, types.ErrInvalidArg)
net, err := n.createPlugin(newNetwork)
if err != nil {
return nil, err
}
newNetwork = net
}

// when we do not have ipam we must disable dns
Expand Down Expand Up @@ -403,3 +407,55 @@ func validateIPAMDriver(n *types.Network) error {
}
return nil
}

var errInvalidPluginResult = errors.New("invalid plugin result")

func (n *netavarkNetwork) createPlugin(net *types.Network) (*types.Network, error) {
path, err := getPlugin(net.Driver, n.pluginDirs)
if err != nil {
return nil, err
}
result := new(types.Network)
err = n.execPlugin(path, []string{"create"}, net, result)
if err != nil {
return nil, fmt.Errorf("plugin %s failed: %w", path, err)
}
// now make sure that neither the name, ID, driver were changed by the plugin
if net.Name != result.Name {
return nil, fmt.Errorf("%w: changed network name", errInvalidPluginResult)
}
if net.ID != result.ID {
return nil, fmt.Errorf("%w: changed network ID", errInvalidPluginResult)
}
if net.Driver != result.Driver {
return nil, fmt.Errorf("%w: changed network driver", errInvalidPluginResult)
}
return result, nil
}

func getAllPlugins(dirs []string) []string {
var plugins []string
for _, dir := range dirs {
entries, err := os.ReadDir(dir)
if err == nil {
for _, entry := range entries {
name := entry.Name()
if !util.StringInSlice(name, plugins) {
plugins = append(plugins, name)
}
}
}
}
return plugins
}

func getPlugin(name string, dirs []string) (string, error) {
for _, dir := range dirs {
fullpath := filepath.Join(dir, name)
st, err := os.Stat(fullpath)
if err == nil && st.Mode().IsRegular() {
return fullpath, nil
}
}
return "", fmt.Errorf("failed to find driver or plugin %q", name)
}
2 changes: 1 addition & 1 deletion libnetwork/netavark/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,7 @@ var _ = Describe("Config", func() {
}
_, err := libpodNet.NetworkCreate(network, nil)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("unsupported driver someDriver"))
Expect(err.Error()).To(ContainSubstring(`failed to find driver or plugin "someDriver"`))
})

It("network create internal and dns", func() {
Expand Down
31 changes: 20 additions & 11 deletions libnetwork/netavark/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,24 @@ func getRustLogEnv() string {
// used to marshal the netavark output into it. This can be nil.
// All errors return by this function should be of the type netavarkError
// to provide a helpful error message.
func (n *netavarkNetwork) execNetavark(args []string, stdin, result interface{}) error {
func (n *netavarkNetwork) execNetavark(args []string, needPlugin bool, stdin, result interface{}) error {
// set the netavark log level to the same as the podman
env := append(os.Environ(), getRustLogEnv())
// if we run with debug log level lets also set RUST_BACKTRACE=1 so we can get the full stack trace in case of panics
if logrus.IsLevelEnabled(logrus.DebugLevel) {
env = append(env, "RUST_BACKTRACE=1")
}
if n.dnsBindPort != 0 {
env = append(env, "NETAVARK_DNS_PORT="+strconv.Itoa(int(n.dnsBindPort)))
}
return n.execBinary(n.netavarkBinary, append(n.getCommonNetavarkOptions(needPlugin), args...), stdin, result, env)
}

func (n *netavarkNetwork) execPlugin(path string, args []string, stdin, result interface{}) error {
return n.execBinary(path, args, stdin, result, nil)
}

func (n *netavarkNetwork) execBinary(path string, args []string, stdin, result interface{}, env []string) error {
stdinR, stdinW, err := os.Pipe()
if err != nil {
return newNetavarkError("failed to create stdin pipe", err)
Expand Down Expand Up @@ -108,20 +125,12 @@ func (n *netavarkNetwork) execNetavark(args []string, stdin, result interface{})
logWriter = io.MultiWriter(logWriter, &logrusNetavarkWriter{})
}

cmd := exec.Command(n.netavarkBinary, append(n.getCommonNetavarkOptions(), args...)...)
cmd := exec.Command(path, args...)
// connect the pipes to stdin and stdout
cmd.Stdin = stdinR
cmd.Stdout = stdoutW
cmd.Stderr = logWriter
// set the netavark log level to the same as the podman
cmd.Env = append(os.Environ(), getRustLogEnv())
// if we run with debug log level lets also set RUST_BACKTRACE=1 so we can get the full stack trace in case of panics
if logrus.IsLevelEnabled(logrus.DebugLevel) {
cmd.Env = append(cmd.Env, "RUST_BACKTRACE=1")
}
if n.dnsBindPort != 0 {
cmd.Env = append(cmd.Env, "NETAVARK_DNS_PORT="+strconv.Itoa(int(n.dnsBindPort)))
}
cmd.Env = env

err = cmd.Start()
if err != nil {
Expand Down
9 changes: 9 additions & 0 deletions libnetwork/netavark/netavark_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ func getNetworkInterface(confDir string) (types.ContainerNetwork, error) {
})
}

func getNetworkInterfaceWithPlugins(confDir string, pluginDirs []string) (types.ContainerNetwork, error) {
return netavark.NewNetworkInterface(&netavark.InitConfig{
NetworkConfigDir: confDir,
NetavarkBinary: netavarkBinary,
NetworkRunDir: confDir,
PluginDirs: pluginDirs,
})
}

// EqualSubnet is a custom GomegaMatcher to match a subnet
// This makes sure to not use the 16 bytes ip representation.
func EqualSubnet(subnet *net.IPNet) gomegaTypes.GomegaMatcher {
Expand Down
12 changes: 11 additions & 1 deletion libnetwork/netavark/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ type netavarkNetwork struct {
// dnsBindPort is set the the port to pass to netavark for aardvark
dnsBindPort uint16

// pluginDirs list of directories were netavark plugins are located
pluginDirs []string

// ipamDBPath is the path to the ip allocation bolt db
ipamDBPath string

Expand Down Expand Up @@ -86,6 +89,9 @@ type InitConfig struct {
// DNSBindPort is set the the port to pass to netavark for aardvark
DNSBindPort uint16

// PluginDirs list of directories were netavark plugins are located
PluginDirs []string

// Syslog describes whenever the netavark debbug output should be log to the syslog as well.
// This will use logrus to do so, make sure logrus is set up to log to the syslog.
Syslog bool
Expand Down Expand Up @@ -143,17 +149,21 @@ func NewNetworkInterface(conf *InitConfig) (types.ContainerNetwork, error) {
defaultSubnet: defaultNet,
defaultsubnetPools: defaultSubnetPools,
dnsBindPort: conf.DNSBindPort,
pluginDirs: conf.PluginDirs,
lock: lock,
syslog: conf.Syslog,
}

return n, nil
}

var builtinDrivers = []string{types.BridgeNetworkDriver, types.MacVLANNetworkDriver, types.IPVLANNetworkDriver}

// Drivers will return the list of supported network drivers
// for this interface.
func (n *netavarkNetwork) Drivers() []string {
return []string{types.BridgeNetworkDriver, types.MacVLANNetworkDriver}
paths := getAllPlugins(n.pluginDirs)
return append(builtinDrivers, paths...)
}

// DefaultNetworkName will return the default netavark network name.
Expand Down
106 changes: 106 additions & 0 deletions libnetwork/netavark/plugin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//go:build linux
// +build linux

package netavark_test

import (
"bytes"
"os"
"path/filepath"

"github.com/containers/common/libnetwork/types"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/sirupsen/logrus"
)

const pluginName = "netavark-testplugin"

var _ = Describe("Plugins", func() {
var (
libpodNet types.ContainerNetwork
networkConfDir string
logBuffer bytes.Buffer
)

BeforeEach(func() {
var err error
networkConfDir, err = os.MkdirTemp("", "podman_netavark_test")
if err != nil {
Fail("Failed to create tmpdir")
}
logBuffer = bytes.Buffer{}
logrus.SetOutput(&logBuffer)
})

JustBeforeEach(func() {
var err error
libpodNet, err = getNetworkInterfaceWithPlugins(networkConfDir, []string{"../../bin"})
if err != nil {
Fail("Failed to create NewNetworkInterface")
}
})

AfterEach(func() {
os.RemoveAll(networkConfDir)
})

It("create plugin network", func() {
network := types.Network{Driver: pluginName}
network1, err := libpodNet.NetworkCreate(network, nil)
Expect(err).To(BeNil())
Expect(network1.Name).ToNot(BeEmpty())
Expect(network1.ID).ToNot(BeEmpty())
Expect(filepath.Join(networkConfDir, network1.Name+".json")).To(BeARegularFile())
})

It("create plugin network with name", func() {
name := "test123"
network := types.Network{Driver: pluginName, Name: name}
network1, err := libpodNet.NetworkCreate(network, nil)
Expect(err).To(BeNil())
Expect(network1.Name).To(Equal(name))
Expect(network1.ID).ToNot(BeEmpty())
Expect(filepath.Join(networkConfDir, network1.Name+".json")).To(BeARegularFile())
})

It("create plugin error", func() {
network := types.Network{
Driver: pluginName,
Options: map[string]string{"error": "my custom error"},
}
_, err := libpodNet.NetworkCreate(network, nil)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("plugin ../../bin/netavark-testplugin failed: netavark (exit code 1): my custom error"))
})

It("create plugin change name error", func() {
network := types.Network{
Driver: pluginName,
Options: map[string]string{"name": "newName"},
}
_, err := libpodNet.NetworkCreate(network, nil)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("invalid plugin result: changed network name"))
})

It("create plugin change id error", func() {
network := types.Network{
Driver: pluginName,
Options: map[string]string{"id": "newID"},
}
_, err := libpodNet.NetworkCreate(network, nil)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("invalid plugin result: changed network ID"))
})

It("create plugin change driver error", func() {
network := types.Network{
Driver: pluginName,
Options: map[string]string{"driver": "newDriver"},
}
_, err := libpodNet.NetworkCreate(network, nil)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("invalid plugin result: changed network driver"))
})
})
Loading

0 comments on commit 4b81e08

Please sign in to comment.