Skip to content

Commit

Permalink
Add network management commands
Browse files Browse the repository at this point in the history
This commit allows virter to create new virtual networks, optionally
with DHCP and NAT. It also allows adding interfaces when creating VMs,
similar to the way additional disks can be provisioned.

The following new commands are introduced:
* network ls, showing all currently started networks
* network add, adding a new virtual network
* network rm, removing an existing network
* network list-attached, listing VMs attached to a network

The following commands are updated:
* vm run: added the `--nic` flag to add network interfaces (like `--disk`)

The main motivation for adding this was work on a multinode openstack CI
system, which work best if openstack has complete control over a single
interface.
  • Loading branch information
WanzenBug committed Apr 19, 2021
1 parent ed6ccb0 commit 5bbfd5a
Show file tree
Hide file tree
Showing 26 changed files with 812 additions and 96 deletions.
8 changes: 4 additions & 4 deletions cmd/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (

// DiskArg represents a disk that can be passed to virter via a command line argument.
type DiskArg struct {
Name string `arg:"name"`
Size Size `arg:"size"`
Format string `arg:"format,qcow2"`
Bus string `arg:"bus,virtio"`
Name string `arg:"name"`
Size Size `arg:"size"`
Format string `arg:"format,qcow2"`
Bus string `arg:"bus,virtio"`
}

type Size struct {
Expand Down
8 changes: 4 additions & 4 deletions cmd/image_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ step, and then committing the resulting volume.`,
}

buildConfig := virter.ImageBuildConfig{
ContainerName: containerName,
ShutdownTimeout: shutdownTimeout,
ProvisionConfig: provisionConfig,
ResetMachineID: resetMachineID,
ContainerName: containerName,
ShutdownTimeout: shutdownTimeout,
ProvisionConfig: provisionConfig,
ResetMachineID: resetMachineID,
}

err = pullIfNotExists(v, vmConfig.ImageName)
Expand Down
4 changes: 4 additions & 0 deletions cmd/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,9 @@ func networkCommand() *cobra.Command {
}

networkCmd.AddCommand(networkHostCommand())
networkCmd.AddCommand(networkLsCommand())
networkCmd.AddCommand(networkAddCommand())
networkCmd.AddCommand(networkRmCommand())
networkCmd.AddCommand(networkListAttachedCommand())
return networkCmd
}
120 changes: 120 additions & 0 deletions cmd/network_add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package cmd

import (
libvirtxml "github.com/libvirt/libvirt-go-xml"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"net"
)

func networkAddCommand() *cobra.Command {
var forward string
var dhcp bool
var network string
var domain string

addCmd := &cobra.Command{
Use: "add <name>",
Short: "Add a new network",
Long: `Add a new network. VMs can be attached to such a network in addition to the default network used by virter.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
v, err := InitVirter()
if err != nil {
log.Fatal(err)
}
defer v.ForceDisconnect()

var forwardDesc *libvirtxml.NetworkForward
if forward != "" {
forwardDesc = &libvirtxml.NetworkForward{
Mode: forward,
}
}

var addressesDesc []libvirtxml.NetworkIP
if network != "" {
ip, n, err := net.ParseCIDR(network)
if err != nil {
log.Fatal(err)
}

var dhcpDesc *libvirtxml.NetworkDHCP
if dhcp {
start := nextIP(ip)
end := previousIP(broadcastAddress(n))

n.Network()
dhcpDesc = &libvirtxml.NetworkDHCP{
Ranges: []libvirtxml.NetworkDHCPRange{{Start: start.String(), End: end.String()}},
}
}

addressesDesc = append(addressesDesc, libvirtxml.NetworkIP{
Address: ip.String(),
Netmask: net.IP(n.Mask).String(),
DHCP: dhcpDesc,
})
}

var domainDesc *libvirtxml.NetworkDomain
if domain != "" {
domainDesc = &libvirtxml.NetworkDomain{
Name: domain,
LocalOnly: "yes",
}
}

desc := libvirtxml.Network{
Name: args[0],
Forward: forwardDesc,
IPs: addressesDesc,
Domain: domainDesc,
}

err = v.NetworkAdd(desc)
if err != nil {
log.Fatal(err)
}
},
}

addCmd.Flags().StringVarP(&forward, "forward-mode", "m", "", "Set the forward mode, for example 'nat'")
addCmd.Flags().StringVarP(&network, "network-cidr", "n", "", "Configure the network range (IPv4) in CIDR notation. The IP will be assigned to the host device.")
addCmd.Flags().BoolVarP(&dhcp, "dhcp", "p", false, "Configure DHCP. Use together with '--network-cidr'. DHCP range is configured starting from --network-cidr+1 until the broadcast address")
addCmd.Flags().StringVarP(&domain, "domain", "d", "", "Configure DNS names for the network")
return addCmd
}

func nextIP(ip net.IP) net.IP {
dup := make(net.IP, len(ip))
copy(dup, ip)
for j := len(dup) - 1; j >= 0; j-- {
dup[j]++
if dup[j] > 0 {
break
}
}
return dup
}

func previousIP(ip net.IP) net.IP {
dup := make(net.IP, len(ip))
copy(dup, ip)
for j := len(dup) - 1; j >= 0; j-- {
dup[j]--
if dup[j] < 255 {
break
}
}
return dup
}

func broadcastAddress(ipnet *net.IPNet) net.IP {
dup := make(net.IP, len(ipnet.IP))
copy(dup, ipnet.IP)
for i := range dup {
dup[i] = dup[i] | ^ipnet.Mask[i]
}
return dup
}
36 changes: 36 additions & 0 deletions cmd/network_list_attached.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cmd

import (
"github.com/rodaine/table"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

func networkListAttachedCommand() *cobra.Command {
listAttachedCmd := &cobra.Command{
Use: "list-attached <network-name>",
Short: "List VMs attached to a network",
Long: `List VMs attached to a network. Includes IP address and hostname if available.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
v, err := InitVirter()
if err != nil {
log.Fatal(err)
}
defer v.ForceDisconnect()

vmnics, err := v.NetworkListAttached(args[0])
if err != nil {
log.Fatal(err)
}

tbl := table.New("VM", "MAC", "IP", "Hostname", "Host Device")
for _, vmnic := range vmnics {
tbl.AddRow(vmnic.VMName, vmnic.MAC, vmnic.IP, vmnic.HostName, vmnic.HostDevice)
}
tbl.Print()
},
}

return listAttachedCmd
}
80 changes: 80 additions & 0 deletions cmd/network_ls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package cmd

import (
"fmt"
"github.com/rodaine/table"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
"net"
"strings"

"github.com/spf13/cobra"
)

func networkLsCommand() *cobra.Command {
lsCmd := &cobra.Command{
Use: "ls",
Short: "List available networks",
Long: `List available networks that VMs can be attached to.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
v, err := InitVirter()
if err != nil {
log.Fatal(err)
}
defer v.ForceDisconnect()

xmls, err := v.NetworkList()
if err != nil {
log.Fatal(err)
}

virterNet := viper.GetString("libvirt.network")

tbl := table.New("Name", "Forward-Type", "IP-Range", "Domain", "DHCP", "Bridge")
for _, desc := range xmls {
name := desc.Name
if name == virterNet {
name = fmt.Sprintf("%s (virter default)", name)
}
ty := ""
if desc.Forward != nil {
ty = desc.Forward.Mode
}

var ranges []string
netrg := make([]string, len(desc.IPs))
for i, n := range desc.IPs {
ip := net.ParseIP(n.Address)
mask := net.ParseIP(n.Netmask)
netrg[i] = (&net.IPNet{IP: ip, Mask: net.IPMask(mask)}).String()

if n.DHCP != nil {
for _, r := range n.DHCP.Ranges {
ranges = append(ranges, fmt.Sprintf("%s-%s", r.Start, r.End))
}
}
}
netranges := strings.Join(netrg, ",")

domain := ""
if desc.Domain != nil {
domain = desc.Domain.Name
}

dhcp := strings.Join(ranges, ",")

bridge := ""
if desc.Bridge != nil {
bridge = desc.Bridge.Name
}

tbl.AddRow(name, ty, netranges, domain, dhcp, bridge)
}

tbl.Print()
},
}

return lsCmd
}
28 changes: 28 additions & 0 deletions cmd/network_rm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cmd

import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

func networkRmCommand() *cobra.Command {
rmCmd := &cobra.Command{
Use: "rm <name>",
Short: "Remove a network",
Long: `Remove the named network.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
v, err := InitVirter()
if err != nil {
log.Fatal(err)
}
defer v.ForceDisconnect()

err = v.NetworkRemove(args[0])
if err != nil {
log.Fatal(err)
}
},
}
return rmCmd
}
36 changes: 36 additions & 0 deletions cmd/nic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cmd

import (
"github.com/LINBIT/virter/pkg/cliutils"
)

type NICArg struct {
NicType string `arg:"type"`
Source string `arg:"source"`
Model string `arg:"model,virtio"`
MAC string `arg:"mac,"`
}

func (n *NICArg) GetType() string {
return n.NicType
}

func (n *NICArg) GetSource() string {
return n.Source
}

func (n *NICArg) GetModel() string {
return n.Model
}

func (n *NICArg) GetMAC() string {
return n.MAC
}

func (n *NICArg) Set(str string) error {
return cliutils.Parse(str, n)
}

func (n *NICArg) Type() string {
return "nic"
}
15 changes: 15 additions & 0 deletions cmd/vm_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ func vmRunCommand() *cobra.Command {
var diskStrings []string
var disks []virter.Disk

var nicStrings []string
var nics []virter.NIC

var provisionFile string
var provisionOverrides []string

Expand All @@ -125,6 +128,15 @@ func vmRunCommand() *cobra.Command {
}
disks = append(disks, &d)
}

for _, s := range nicStrings {
var n NICArg
err := n.Set(s)
if err != nil {
log.Fatalf("Invalid nic: %v", err)
}
nics = append(nics, &n)
}
},
Run: func(cmd *cobra.Command, args []string) {
ctx, cancel := onInterruptWrap(context.Background())
Expand Down Expand Up @@ -206,6 +218,7 @@ func vmRunCommand() *cobra.Command {
ExtraSSHPublicKeys: extraAuthorizedKeys,
ConsolePath: consolePath,
Disks: disks,
ExtraNics: nics,
GDBPort: thisGDBPort,
}

Expand Down Expand Up @@ -265,6 +278,8 @@ func vmRunCommand() *cobra.Command {
// If this ever gets implemented in pflag , we will be able to solve this
// in a much smoother way.
runCmd.Flags().StringArrayVarP(&diskStrings, "disk", "d", []string{}, `Add a disk to the VM. Format: "name=disk1,size=100MiB,format=qcow2,bus=virtio". Can be specified multiple times`)
runCmd.Flags().StringArrayVarP(&nicStrings, "nic", "i", []string{}, `Add a NIC to the VM. Format: "type=network,source=some-net-name". Type can also be "bridge", in which case the source is the bridge device name. Additional config options are "model" (default: virtio) and "mac" (default chosen by libvirt). Can be specified multiple times`)

runCmd.Flags().StringVarP(&provisionFile, "provision", "p", "", "name of toml file containing provisioning steps")
runCmd.Flags().StringArrayVarP(&provisionOverrides, "set", "s", []string{}, "set/override provisioning steps")

Expand Down
Loading

0 comments on commit 5bbfd5a

Please sign in to comment.