Skip to content

Commit

Permalink
add option to assign ipv6 addresses to VMs
Browse files Browse the repository at this point in the history
Virter can optionally enable IPv6 support by setting DHCPv6 and forward modes.
This commit adds support for IPv6 both on the access and extra networks. The
default IPv6 subnet is taken from the "Unique Local IPv6 Unicast" pool, i.e.
it is a private address, which should be good enough for our tests.
  • Loading branch information
WanzenBug committed Oct 17, 2022
1 parent f226e3f commit 1a37b72
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 60 deletions.
16 changes: 11 additions & 5 deletions cmd/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,25 @@ import (
log "github.com/sirupsen/logrus"
)

func addNetwork(ctx context.Context, outDir string, networkName string, network virterNet, ipNet *net.IPNet, dhcpID int, dhcpCount int) error {
func addNetwork(ctx context.Context, outDir string, networkName string, network virterNet, ipV4Net, ipV6Net *net.IPNet, dhcpID int, dhcpCount int) error {
logger := log.WithFields(log.Fields{
"Action": "AddNetwork",
"NetworkName": networkName,
})

argv := []string{"virter", "network", "add", networkName}
if network.DHCP {
if ipNet == nil {
if ipV4Net == nil {
panic("cannot add network with DHCP without an IPNet")
}
gatewayAddress := cidr.Inc(ipNet.IP)
networkCidr := net.IPNet{IP: gatewayAddress, Mask: ipNet.Mask}
gatewayAddress := cidr.Inc(ipV4Net.IP)
networkCidr := net.IPNet{IP: gatewayAddress, Mask: ipV4Net.Mask}
argv = append(argv, "--network-cidr", networkCidr.String(), "--dhcp")
if ipV6Net != nil {
gatewayAddress := cidr.Inc(ipV6Net.IP)
networkCidr := net.IPNet{IP: gatewayAddress, Mask: ipV6Net.Mask}
argv = append(argv, "--network-v6-cidr", networkCidr.String())
}
}
if network.ForwardMode != "" {
argv = append(argv, "--forward-mode", network.ForwardMode)
Expand Down Expand Up @@ -70,10 +75,11 @@ func removeNetwork(outDir string, networkName string) error {
return nil
}

func accessNetwork() virterNet {
func accessNetwork(ipv6 bool) virterNet {
return virterNet{
Domain: "test",
ForwardMode: "nat",
DHCP: true,
IPv6: ipv6,
}
}
38 changes: 26 additions & 12 deletions cmd/network_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,48 @@ import (
)

type networkList struct {
current *net.IPNet
freeNets map[string]bool
currentV4 *net.IPNet
currentV6 *net.IPNet
freeNets map[string]bool
}

func NewNetworkList(current *net.IPNet) *networkList {
func NewNetworkList(currentV4, currentV6 *net.IPNet) *networkList {
return &networkList{
current: current,
freeNets: make(map[string]bool),
currentV4: currentV4,
currentV6: currentV6,
freeNets: make(map[string]bool),
}
}

func (n *networkList) ReserveNext() *net.IPNet {
func (n *networkList) ReserveNext(ipv6 bool) *net.IPNet {
for k, v := range n.freeNets {
if v {
ipNet := mustParse(k)
isIPv6 := ipNet.IP.To4() == nil

if v && isIPv6 == ipv6 {
n.freeNets[k] = false
return mustParse(k)
return ipNet
}
}

candidate := n.current
candidate := n.currentV4
if ipv6 {
candidate = n.currentV6
}

next, exceed := cidr.NextSubnet(n.current, len(n.current.IP)*8-8)
prefix, _ := candidate.Mask.Size()
next, exceed := cidr.NextSubnet(candidate, prefix)
if exceed {
panic("available subnets exhausted")
}

n.freeNets[n.current.String()] = false
n.current = next
n.freeNets[candidate.String()] = false
if ipv6 {
n.currentV6 = next
} else {
n.currentV4 = next
}

return candidate
}

Expand Down
20 changes: 13 additions & 7 deletions cmd/network_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,21 @@ import (

func TestNewNetworkList(t *testing.T) {
_, workingBase, _ := net.ParseCIDR("10.224.0.0/24")
list := cmd.NewNetworkList(workingBase)
first := list.ReserveNext()
second := list.ReserveNext()
third := list.ReserveNext()
_, ipv6Base, _ := net.ParseCIDR("fd62:a80c:412::/64")
list := cmd.NewNetworkList(workingBase, ipv6Base)
first := list.ReserveNext(false)
second := list.ReserveNext(false)
third := list.ReserveNext(false)
forth := list.ReserveNext(true)
fifth := list.ReserveNext(true)
assert.Equal(t, "10.224.0.0/24", first.String())
assert.Equal(t, "10.224.1.0/24", second.String())
assert.Equal(t, "10.224.2.0/24", third.String())
assert.Equal(t, "fd62:a80c:412::/64", forth.String())
assert.Equal(t, "fd62:a80c:412:1::/64", fifth.String())
list.Free(second)
assert.Equal(t, "10.224.1.0/24", list.ReserveNext().String())
assert.Equal(t, "10.224.3.0/24", list.ReserveNext().String())

list.Free(forth)
assert.Equal(t, "10.224.1.0/24", list.ReserveNext(false).String())
assert.Equal(t, "10.224.3.0/24", list.ReserveNext(false).String())
assert.Equal(t, "fd62:a80c:412::/64", list.ReserveNext(true).String())
}
21 changes: 13 additions & 8 deletions cmd/schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func runScheduler(ctx context.Context, suiteRun *testSuiteRun) map[string]testRe
}

func initializeState(suiteRun *testSuiteRun) *suiteState {
netlist := NewNetworkList(suiteRun.firstNet)
netlist := NewNetworkList(suiteRun.firstV4Net, suiteRun.firstV6Net)

state := suiteState{
networks: make(map[string]*networkState),
Expand Down Expand Up @@ -272,7 +272,7 @@ func allImagesReady(state *suiteState, run *testRun) bool {
}

func allNetworksReady(state *suiteState, run *testRun) bool {
networkName := findReadyNetwork(state, nil, accessNetwork(), true)
networkName := findReadyNetwork(state, nil, accessNetwork(run.variant.IPv6), true)
if networkName == "" {
return false
}
Expand Down Expand Up @@ -320,7 +320,8 @@ func findReadyNetwork(state *suiteState, exclude map[string]bool, network virter

if ns.network.ForwardMode != network.ForwardMode ||
ns.network.DHCP != network.DHCP ||
ns.network.Domain != network.Domain {
ns.network.Domain != network.Domain ||
ns.network.IPv6 != network.IPv6 {
continue
}

Expand Down Expand Up @@ -354,7 +355,7 @@ func nextActionRun(suiteRun *testSuiteRun, state *suiteState, run *testRun) acti
return nil
}

network := accessNetwork()
network := accessNetwork(run.variant.IPv6)
networkName := findReadyNetwork(state, nil, network, true)
if networkName == "" {
return makeAddNetworkAction(state, network, true)
Expand Down Expand Up @@ -388,7 +389,7 @@ func getIDs(suiteRun *testSuiteRun, state *suiteState, n int) []int {
}

func nextActionProvision(suiteRun *testSuiteRun, state *suiteState, v *vm) action {
network := accessNetwork()
network := accessNetwork(false)
networkName := findReadyNetwork(state, nil, network, true)
if networkName == "" {
return makeAddNetworkAction(state, network, true)
Expand Down Expand Up @@ -512,7 +513,8 @@ type addNetworkAction struct {
networkName string
network virterNet
access bool
ipNet *net.IPNet
ipv4Net *net.IPNet
ipv6Net *net.IPNet
err error
}

Expand All @@ -527,7 +529,10 @@ func (a *addNetworkAction) updatePre(state *suiteState) {
stage: networkAdd,
}
if a.network.DHCP {
a.ipNet = state.freeNets.ReserveNext()
a.ipv4Net = state.freeNets.ReserveNext(false)
if a.network.IPv6 {
a.ipv6Net = state.freeNets.ReserveNext(true)
}
}
}

Expand All @@ -536,7 +541,7 @@ func (a *addNetworkAction) exec(ctx context.Context, suiteRun *testSuiteRun) {
if a.access {
dhcpCount = suiteRun.nrVMs
}
a.err = addNetwork(ctx, suiteRun.outDir, a.networkName, a.network, a.ipNet, suiteRun.startVM, dhcpCount)
a.err = addNetwork(ctx, suiteRun.outDir, a.networkName, a.network, a.ipv4Net, a.ipv6Net, suiteRun.startVM, dhcpCount)
}

func (a *addNetworkAction) updatePost(state *suiteState) {
Expand Down
44 changes: 22 additions & 22 deletions cmd/schedule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ func TestChooseNextAction(t *testing.T) {
{
name: "prefer-larger-test",
suiteRun: testSuiteRun{
vmSpec: &vmSpecification{VMs: []vm{vm0, vm1}},
testRuns: []testRun{testRun1VM, testRun2VM},
startVM: 5,
nrVMs: 2,
firstNet: baseNet,
vmSpec: &vmSpecification{VMs: []vm{vm0, vm1}},
testRuns: []testRun{testRun1VM, testRun2VM},
startVM: 5,
nrVMs: 2,
firstV4Net: baseNet,
},
sequence: []step{
{
Expand All @@ -69,12 +69,12 @@ func TestChooseNextAction(t *testing.T) {
{
name: "test-fail",
suiteRun: testSuiteRun{
vmSpec: &vmSpecification{VMs: []vm{vm0, vm1}},
testRuns: []testRun{testRun1VM, testRun2VM},
startVM: 5,
nrVMs: 2,
failTest: true,
firstNet: baseNet,
vmSpec: &vmSpecification{VMs: []vm{vm0, vm1}},
testRuns: []testRun{testRun1VM, testRun2VM},
startVM: 5,
nrVMs: 2,
failTest: true,
firstV4Net: baseNet,
},
sequence: []step{
{
Expand All @@ -93,11 +93,11 @@ func TestChooseNextAction(t *testing.T) {
{
name: "provision",
suiteRun: testSuiteRun{
vmSpec: &vmSpecification{ProvisionFile: "/p", VMs: []vm{vm0, vm1}},
testRuns: []testRun{testRun1VM, testRun2VM},
startVM: 5,
nrVMs: 2,
firstNet: baseNet,
vmSpec: &vmSpecification{ProvisionFile: "/p", VMs: []vm{vm0, vm1}},
testRuns: []testRun{testRun1VM, testRun2VM},
startVM: 5,
nrVMs: 2,
firstV4Net: baseNet,
},
sequence: []step{
{
Expand Down Expand Up @@ -131,11 +131,11 @@ func TestChooseNextAction(t *testing.T) {
{
name: "provision-fail",
suiteRun: testSuiteRun{
vmSpec: &vmSpecification{ProvisionFile: "/p", VMs: []vm{vm0, vm1}},
testRuns: []testRun{testRun1VM, testRun2VM},
startVM: 5,
nrVMs: 2,
firstNet: baseNet,
vmSpec: &vmSpecification{ProvisionFile: "/p", VMs: []vm{vm0, vm1}},
testRuns: []testRun{testRun1VM, testRun2VM},
startVM: 5,
nrVMs: 2,
firstV4Net: baseNet,
},
sequence: []step{
{
Expand Down Expand Up @@ -265,5 +265,5 @@ func validateAction(t *testing.T, a, e action) {
}

func accessNetworkAction(name string) action {
return &addNetworkAction{networkName: name, network: accessNetwork()}
return &addNetworkAction{networkName: name, network: accessNetwork(false)}
}
22 changes: 16 additions & 6 deletions cmd/vmshed.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,13 @@ type testSpecification struct {
type variant struct {
Name string `toml:"name"`
Variables map[string]string `toml:"variables"`
IPv6 bool `toml:"ipv6"`
VMTags []string `toml:"vm_tags"`
}

type virterNet struct {
ForwardMode string `toml:"forward"`
IPv6 bool `toml:"ipv6"`
DHCP bool `toml:"dhcp"`
Domain string `toml:"domain"`
}
Expand All @@ -86,7 +88,8 @@ type testSuiteRun struct {
testRuns []testRun
startVM int
nrVMs int
firstNet *net.IPNet
firstV4Net *net.IPNet
firstV6Net *net.IPNet
failTest bool
printErrorDetails bool
}
Expand Down Expand Up @@ -126,7 +129,8 @@ func rootCommand() *cobra.Command {
var version bool
var variantsToRun []string
var errorDetails bool
var firstSubnet string
var firstv4Subnet string
var firstv6Subnet string

rootCmd := &cobra.Command{
Use: "vmshed",
Expand Down Expand Up @@ -192,7 +196,12 @@ current user.`,
log.Fatal(err)
}

_, firstNet, err := net.ParseCIDR(firstSubnet)
_, firstV4Net, err := net.ParseCIDR(firstv4Subnet)
if err != nil {
log.Fatal(err)
}

_, firstV6Net, err := net.ParseCIDR(firstv6Subnet)
if err != nil {
log.Fatal(err)
}
Expand All @@ -202,7 +211,8 @@ current user.`,
suiteRun.nrVMs = nrVMs
suiteRun.failTest = failTest
suiteRun.printErrorDetails = errorDetails
suiteRun.firstNet = firstNet
suiteRun.firstV4Net = firstV4Net
suiteRun.firstV6Net = firstV6Net

ctx, cancel := signalcontext.On(unix.SIGINT, unix.SIGTERM)
defer cancel()
Expand Down Expand Up @@ -240,8 +250,8 @@ current user.`,
rootCmd.Flags().Int64VarP(&randomSeed, "seed", "", 0, "The random number generator seed to use. Specifying 0 seeds with the current time (the default)")
rootCmd.Flags().StringSliceVarP(&variantsToRun, "variant", "", []string{}, "which variant to run (defaults to all)")
rootCmd.Flags().BoolVarP(&errorDetails, "error-details", "", true, "Show all test error logs at the end of the run")
rootCmd.Flags().StringVarP(&firstSubnet, "first-subnet", "", "10.224.0.0/24", "The first subnet to use for VMs. If more virtual networks are required, the next higher /24 networks will be used")

rootCmd.Flags().StringVarP(&firstv4Subnet, "first-subnet", "", "10.224.0.0/24", "The first subnet to use for VMs. If more virtual networks are required, the next higher network of the same size will be used")
rootCmd.Flags().StringVarP(&firstv6Subnet, "first-v6-subnet", "", "fd62:a80c:412::/64", "The first ipv6 subnet to use for VMs. If more virtual networks are required, the next higher network of the same size will be used")
return rootCmd
}

Expand Down
5 changes: 5 additions & 0 deletions example/tests.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ name = "maria"
variables = {dbtype = "mariadb"}
vm_tags = ["mariadb-server"]

[[variants]]
name = "ipv6"
variables = {dbtype = "mariadb"}
ipv6 = true

[tests]
[tests.test_podman]
vms = [2]
Expand Down

0 comments on commit 1a37b72

Please sign in to comment.