Skip to content
This repository has been archived by the owner on Dec 7, 2023. It is now read-only.

Enable multiple non-IP interface to be connected via tc redirect #836

Merged
merged 28 commits into from
Jun 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0d7046e
First crack at multinet implementation
networkop May 10, 2021
0b3dfdb
fix the dhcp server bug
networkop May 10, 2021
9acef4f
Merge branch 'weaveworks:main' into multinet
networkop May 10, 2021
56ca546
Added wait for X number of interfaces to be connected
networkop May 10, 2021
52776a6
Merge branch 'weaveworks:main' into multinet
networkop May 10, 2021
e3baa91
Merge branch 'multinet' of github.com:networkop/ignite into multinet
networkop May 10, 2021
17765be
Fixed multiple bugs
networkop May 11, 2021
6e19436
Ensure maxIntfs is at least 1
networkop May 11, 2021
39d395e
Fixed formatting
networkop May 11, 2021
9a31fb9
Forgot to assign envVars
networkop May 11, 2021
20b4aa8
Updated docs
networkop May 11, 2021
35dc891
More updated docs
networkop May 11, 2021
e8bd47c
More docs updates
networkop May 11, 2021
b8ef5d3
Not using env vars anymore
networkop May 22, 2021
47c9140
Re-introducing env var support
networkop May 23, 2021
6f1f14a
Reverting the order to error checks
networkop May 24, 2021
90eaa58
Added multinet tests
networkop Jun 9, 2021
1020f90
Fixing dox
networkop Jun 9, 2021
053def5
Refactored StartVM with async spawn channel
networkop Jun 10, 2021
eb8997f
Refactored ignite-spawn's network setup
networkop Jun 10, 2021
051ad2b
Fixed the CNI plugin bug
networkop Jun 11, 2021
1c70e78
Tested with containerlab
networkop Jun 12, 2021
9957b70
Merging suggested changes
networkop Jun 21, 2021
3161ef4
Alternative e2e test
networkop Jun 22, 2021
f8d361d
Removed --wait flag and fixed up multinet tests
networkop Jun 22, 2021
668b5fe
Removing waitForSSH from exec
networkop Jun 24, 2021
71c801c
Removing leftover comment
networkop Jun 24, 2021
abddda2
Adding waitforSSH back
networkop Jun 24, 2021
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
5 changes: 3 additions & 2 deletions cmd/ignite-spawn/ignite-spawn.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ func decodeVM(vmID string) (*api.VM, error) {
}

func StartVM(vm *api.VM) (err error) {

// Setup networking inside of the container, return the available interfaces
dhcpIfaces, err := container.SetupContainerNetworking()
fcIfaces, dhcpIfaces, err := container.SetupContainerNetworking(vm)
if err != nil {
return fmt.Errorf("network setup failed: %v", err)
}
Expand All @@ -66,7 +67,7 @@ func StartVM(vm *api.VM) (err error) {
defer util.DeferErr(&err, func() error { return os.Remove(metricsSocket) })

// Execute Firecracker
if err = container.ExecuteFirecracker(vm, dhcpIfaces); err != nil {
if err = container.ExecuteFirecracker(vm, fcIfaces); err != nil {
return fmt.Errorf("runtime error for VM %q: %v", vm.GetUID(), err)
}

Expand Down
4 changes: 4 additions & 0 deletions cmd/ignite/run/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package run

import (
api "github.com/weaveworks/ignite/pkg/apis/ignite"
"github.com/weaveworks/ignite/pkg/constants"
)

// ExecFlags contains the flags supported by the exec command.
Expand Down Expand Up @@ -30,5 +31,8 @@ func (ef *ExecFlags) NewExecOptions(vmMatch string, command ...string) (eo *Exec

// Exec executes command in a VM based on the provided ExecOptions.
func Exec(eo *ExecOptions) error {
if err := waitForSSH(eo.vm, constants.SSH_DEFAULT_TIMEOUT_SECONDS, int(eo.Timeout)); err != nil {
return err
}
return runSSH(eo.vm, eo.IdentityFile, eo.command, eo.Tty, eo.Timeout)
}
268 changes: 268 additions & 0 deletions e2e/multinet_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
package e2e

import (
"fmt"
"strings"
"testing"

"github.com/weaveworks/ignite/e2e/util"
api "github.com/weaveworks/ignite/pkg/apis/ignite"
meta "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1"
igniteConstants "github.com/weaveworks/ignite/pkg/constants"
"github.com/weaveworks/ignite/pkg/dmlegacy"
"github.com/weaveworks/ignite/pkg/metadata"
"github.com/weaveworks/ignite/pkg/network"
"github.com/weaveworks/ignite/pkg/operations"
"github.com/weaveworks/ignite/pkg/providers"
igniteDocker "github.com/weaveworks/ignite/pkg/providers/docker"
"github.com/weaveworks/ignite/pkg/providers/ignite"
"github.com/weaveworks/ignite/pkg/runtime"
igniteUtil "github.com/weaveworks/ignite/pkg/util"
"gotest.tools/assert"
)

var (
multinetVM = "e2e-test-vm-multinet"
sanboxImage = "weaveworks/ignite:dev"
kernelImage = "weaveworks/ignite-kernel:5.4.108"
vmImage = "weaveworks/ignite-ubuntu"
)

func startAsyncVM(t *testing.T, intfs []string) (*operations.VMChannels, string) {

assert.Assert(t, e2eHome != "", "IGNITE_E2E_HOME should be set")
igniteUtil.GenericCheckErr(providers.Populate(ignite.Preload))

_ = igniteDocker.SetDockerRuntime()
_ = igniteDocker.SetDockerNetwork()
Comment on lines +36 to +37
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a natural consequence of globals being side-effected here that these tests cannot be parallelized with other test runs that need different values.
This doesn't affect the existing test suites which make individual invocations of the ignite binary.
Also, we already run our e2e suite in serial anyway.

All of this is fine, but it's worth noting. 👍


providers.RuntimeName = runtime.RuntimeDocker
providers.NetworkPluginName = network.PluginDockerBridge
_ = providers.Populate(ignite.Providers)

vm := providers.Client.VMs().New()
vm.Status.Runtime.Name = runtime.RuntimeDocker
vm.Status.Network.Plugin = network.PluginDockerBridge

ociRef, err := meta.NewOCIImageRef(sanboxImage)
if err != nil {
t.Fatalf("Failed to parse OCI image ref %s: %s", sanboxImage, err)
}
vm.Spec.Sandbox.OCI = ociRef

ociRef, err = meta.NewOCIImageRef(kernelImage)
if err != nil {
t.Fatalf("Failed to parse OCI image ref %s: %s", kernelImage, err)
}
vm.Spec.Kernel.OCI = ociRef
k, _ := operations.FindOrImportKernel(providers.Client, ociRef)
vm.SetKernel(k)

ociRef, err = meta.NewOCIImageRef(vmImage)
if err != nil {
t.Fatalf("Failed to parse OCI image ref %s: %s", vmImage, err)
}
img, err := operations.FindOrImportImage(providers.Client, ociRef)
if err != nil {
t.Fatalf("Failed to find OCI image ref %s: %s", ociRef, err)
}
vm.SetImage(img)

vm.Name = multinetVM
vm.Spec.SSH = &api.SSH{Generate: true}

_ = metadata.SetNameAndUID(vm, providers.Client)

for _, intf := range intfs {
vm.SetAnnotation(igniteConstants.IGNITE_INTERFACE_ANNOTATION+intf, "tc-redirect")
}

_ = providers.Client.VMs().Set(vm)

err = dmlegacy.AllocateAndPopulateOverlay(vm)
if err != nil {
t.Fatalf("Error AllocateAndPopulateOverlay: %s", err)
}

vmChans, err := operations.StartVMNonBlocking(vm, false)
if err != nil {
t.Fatalf("failed to start a VM: \n%q\n", err)
}

return vmChans, vm.GetUID().String()
}

// TestMultipleInterface tests that a VM's can be configured with more than 1 interface
func TestOneExtraInterface(t *testing.T) {
assert.Assert(t, e2eHome != "", "IGNITE_E2E_HOME should be set")

igniteCmd := util.NewCommand(t, igniteBin)
dockerCmd := util.NewCommand(t, runtime.RuntimeDocker.String())

vmChans, vmID := startAsyncVM(t, []string{"foo"})

// Clean-up the following VM.
defer igniteCmd.New().
With("rm", "-f", multinetVM).
Run()

fooAddr := "aa:ca:e9:12:34:56"
dockerCmd.New().
With("exec", fmt.Sprintf("ignite-%s", vmID)).
With("ip", "link", "add", "foo", "type", "veth").
Run()

dockerCmd.New().
With("exec", fmt.Sprintf("ignite-%s", vmID)).
With("ip", "link", "set", "foo", "address", fooAddr).
Run()

// check that the VM has started before trying exec
if err := <-vmChans.SpawnFinished; err != nil {
t.Fatalf("failed to start a VM: \n%q\n", err)
}
Comment on lines +120 to +123
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that these tests exercise the wait code that polls for the Firecracker Prometheus socket.


eth1Addr := igniteCmd.New().
With("exec", multinetVM).
With("cat", "/sys/class/net/eth1/address")

foundEth1Addr, _ := eth1Addr.Cmd.CombinedOutput()
gotEth1Addr := strings.TrimSuffix(string(foundEth1Addr), "\n")
assert.Check(t, strings.Contains(gotEth1Addr, fooAddr), fmt.Sprintf("unexpected address found:\n\t(WNT): %q\n\t(GOT): %q", fooAddr, gotEth1Addr))

}

func TestMultipleInterface(t *testing.T) {
assert.Assert(t, e2eHome != "", "IGNITE_E2E_HOME should be set")

igniteCmd := util.NewCommand(t, igniteBin)
dockerCmd := util.NewCommand(t, runtime.RuntimeDocker.String())

vmChans, vmID := startAsyncVM(t, []string{"foo", "bar"})

// Clean-up the following VM.
defer igniteCmd.New().
With("rm", "-f", multinetVM).
Run()

fooAddr := "aa:ca:e9:12:34:56"
dockerCmd.New().
With("exec", fmt.Sprintf("ignite-%s", vmID)).
With("ip", "link", "add", "foo", "type", "veth").
Run()

dockerCmd.New().
With("exec", fmt.Sprintf("ignite-%s", vmID)).
With("ip", "link", "set", "foo", "address", fooAddr).
Run()

barAddr := "aa:ca:e9:12:34:78"
dockerCmd.New().
With("exec", fmt.Sprintf("ignite-%s", vmID)).
With("ip", "link", "add", "bar", "type", "veth").
Run()

dockerCmd.New().
With("exec", fmt.Sprintf("ignite-%s", vmID)).
With("ip", "link", "set", "bar", "address", barAddr).
Run()

// check that the VM has started before trying exec
if err := <-vmChans.SpawnFinished; err != nil {
t.Fatalf("failed to start a VM: \n%q\n", err)
}

eth1Addr := igniteCmd.New().
With("exec", multinetVM).
With("cat", "/sys/class/net/eth1/address")

foundEth1Addr, _ := eth1Addr.Cmd.CombinedOutput()
gotEth1Addr := strings.TrimSuffix(string(foundEth1Addr), "\n")
assert.Check(t, strings.Contains(gotEth1Addr, barAddr), fmt.Sprintf("unexpected address found:\n\t(WNT): %q\n\t(GOT): %q", barAddr, gotEth1Addr))

eth2Addr := igniteCmd.New().
With("exec", multinetVM).
With("cat", "/sys/class/net/eth2/address")

foundEth2Addr, _ := eth2Addr.Cmd.CombinedOutput()
gotEth2Addr := strings.TrimSuffix(string(foundEth2Addr), "\n")
assert.Check(t, strings.Contains(gotEth2Addr, fooAddr), fmt.Sprintf("unexpected address found:\n\t(WNT): %q\n\t(GOT): %q", fooAddr, gotEth2Addr))

}

func TestMultipleInterfaceImplicit(t *testing.T) {
assert.Assert(t, e2eHome != "", "IGNITE_E2E_HOME should be set")

igniteCmd := util.NewCommand(t, igniteBin)
dockerCmd := util.NewCommand(t, runtime.RuntimeDocker.String())

vmChans, vmID := startAsyncVM(t, []string{"foo", "bar"})

// Clean-up the following VM.
defer igniteCmd.New().
With("rm", "-f", multinetVM).
Run()

fooAddr := "aa:ca:e9:12:34:56"
dockerCmd.New().
With("exec", fmt.Sprintf("ignite-%s", vmID)).
With("ip", "link", "add", "foo", "type", "veth").
Run()

dockerCmd.New().
With("exec", fmt.Sprintf("ignite-%s", vmID)).
With("ip", "link", "set", "foo", "address", fooAddr).
Run()

barAddr := "aa:ca:e9:12:34:78"
dockerCmd.New().
With("exec", fmt.Sprintf("ignite-%s", vmID)).
With("ip", "link", "add", "bar", "type", "veth").
Run()

dockerCmd.New().
With("exec", fmt.Sprintf("ignite-%s", vmID)).
With("ip", "link", "set", "bar", "address", barAddr).
Run()

// this interface should never be found inside a VM
bazAddr := "aa:ca:e9:12:34:90"
dockerCmd.New().
With("exec", fmt.Sprintf("ignite-%s", vmID)).
With("ip", "link", "add", "baz", "type", "veth").
Run()

dockerCmd.New().
With("exec", fmt.Sprintf("ignite-%s", vmID)).
With("ip", "link", "set", "baz", "address", bazAddr).
Run()

// check that the VM has started before trying exec
if err := <-vmChans.SpawnFinished; err != nil {
t.Fatalf("failed to start a VM: \n%q\n", err)
}

eth1Addr := igniteCmd.New().
With("exec", multinetVM).
With("cat", "/sys/class/net/eth1/address")

foundEth1Addr, _ := eth1Addr.Cmd.CombinedOutput()
gotEth1Addr := strings.TrimSuffix(string(foundEth1Addr), "\n")
assert.Check(t, strings.Contains(gotEth1Addr, barAddr), fmt.Sprintf("unexpected address found:\n\t(WNT): %q\n\t(GOT): %q", barAddr, gotEth1Addr))

eth2Addr := igniteCmd.New().
With("exec", multinetVM).
With("cat", "/sys/class/net/eth2/address")

foundEth2Addr, _ := eth2Addr.Cmd.CombinedOutput()
gotEth2Addr := strings.TrimSuffix(string(foundEth2Addr), "\n")
assert.Check(t, strings.Contains(gotEth2Addr, fooAddr), fmt.Sprintf("unexpected address found:\n\t(WNT): %q\n\t(GOT): %q", fooAddr, gotEth2Addr))

eth3Addr := igniteCmd.New().
With("exec", multinetVM).
With("cat", "/sys/class/net/eth3/address")

_, foundEth3Err := eth3Addr.Cmd.CombinedOutput()
assert.Error(t, foundEth3Err, "exit status 1", fmt.Sprintf("unexpected output when looking for eth3 : \n%s", foundEth3Err))

}
6 changes: 6 additions & 0 deletions pkg/constants/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,10 @@ const (
// DEFAULT_SANDBOX_IMAGE_NAME is the name of the default sandbox container
// image to be used.
DEFAULT_SANDBOX_IMAGE_TAG = "dev"

// IGNITE_INTERFACE_ANNOTATION is the annotation prefix to store a list of extra interfaces
IGNITE_INTERFACE_ANNOTATION = "ignite.weave.works/interface/"

// IGNITE_SANDBOX_ENV_VAR is the annotation prefix to store a list of env variables
IGNITE_SANDBOX_ENV_VAR = "ignite.weave.works/sanbox-env/"
)
9 changes: 0 additions & 9 deletions pkg/container/dhcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,13 @@ import (
log "github.com/sirupsen/logrus"
api "github.com/weaveworks/ignite/pkg/apis/ignite"
"github.com/weaveworks/ignite/pkg/constants"
"github.com/weaveworks/ignite/pkg/util"
)

var leaseDuration, _ = time.ParseDuration(constants.DHCP_INFINITE_LEASE) // Infinite lease time

// StartDHCPServers starts multiple DHCP servers for the VM, one per interface
// It returns the IP addresses that the API object may post in .status, and a potential error
func StartDHCPServers(vm *api.VM, dhcpIfaces []DHCPInterface) error {
// Generate the MAC addresses for the VM's adapters
macAddresses := make([]string, 0, len(dhcpIfaces))
if err := util.NewMAC(&macAddresses); err != nil {
return fmt.Errorf("failed to generate MAC addresses: %v", err)
}

// Fetch the DNS servers given to the container
clientConfig, err := dns.ClientConfigFromFile("/etc/resolv.conf")
Expand All @@ -36,9 +30,6 @@ func StartDHCPServers(vm *api.VM, dhcpIfaces []DHCPInterface) error {
// Set the VM hostname to the VM ID
dhcpIface.Hostname = vm.GetUID().String()

// Set the MAC address filter for the DHCP server
dhcpIface.MACFilter = macAddresses[i]

// Add the DNS servers from the container
dhcpIface.SetDNSServers(clientConfig.Servers)

Expand Down
14 changes: 2 additions & 12 deletions pkg/container/firecracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,9 @@ import (
)

// ExecuteFirecracker executes the firecracker process using the Go SDK
func ExecuteFirecracker(vm *api.VM, dhcpIfaces []DHCPInterface) (err error) {
func ExecuteFirecracker(vm *api.VM, fcIfaces firecracker.NetworkInterfaces) (err error) {
drivePath := vm.SnapshotDev()

networkInterfaces := make([]firecracker.NetworkInterface, 0, len(dhcpIfaces))
for _, dhcpIface := range dhcpIfaces {
networkInterfaces = append(networkInterfaces, firecracker.NetworkInterface{
StaticConfiguration: &firecracker.StaticNetworkConfiguration{
MacAddress: dhcpIface.MACFilter,
HostDevName: dhcpIface.VMTAP,
},
})
}

vCPUCount := int64(vm.Spec.CPUs)
memSizeMib := int64(vm.Spec.Memory.MBytes())

Expand Down Expand Up @@ -67,7 +57,7 @@ func ExecuteFirecracker(vm *api.VM, dhcpIfaces []DHCPInterface) (err error) {
IsRootDevice: firecracker.Bool(true),
PathOnHost: &drivePath,
}},
NetworkInterfaces: networkInterfaces,
NetworkInterfaces: fcIfaces,
MachineCfg: models.MachineConfiguration{
VcpuCount: &vCPUCount,
MemSizeMib: &memSizeMib,
Expand Down
Loading