Skip to content

Commit 5bbfd5a

Browse files
committed
Add network management commands
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.
1 parent ed6ccb0 commit 5bbfd5a

26 files changed

+812
-96
lines changed

cmd/disk.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import (
88

99
// DiskArg represents a disk that can be passed to virter via a command line argument.
1010
type DiskArg struct {
11-
Name string `arg:"name"`
12-
Size Size `arg:"size"`
13-
Format string `arg:"format,qcow2"`
14-
Bus string `arg:"bus,virtio"`
11+
Name string `arg:"name"`
12+
Size Size `arg:"size"`
13+
Format string `arg:"format,qcow2"`
14+
Bus string `arg:"bus,virtio"`
1515
}
1616

1717
type Size struct {

cmd/image_build.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,10 @@ step, and then committing the resulting volume.`,
115115
}
116116

117117
buildConfig := virter.ImageBuildConfig{
118-
ContainerName: containerName,
119-
ShutdownTimeout: shutdownTimeout,
120-
ProvisionConfig: provisionConfig,
121-
ResetMachineID: resetMachineID,
118+
ContainerName: containerName,
119+
ShutdownTimeout: shutdownTimeout,
120+
ProvisionConfig: provisionConfig,
121+
ResetMachineID: resetMachineID,
122122
}
123123

124124
err = pullIfNotExists(v, vmConfig.ImageName)

cmd/network.go

+4
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,9 @@ func networkCommand() *cobra.Command {
1212
}
1313

1414
networkCmd.AddCommand(networkHostCommand())
15+
networkCmd.AddCommand(networkLsCommand())
16+
networkCmd.AddCommand(networkAddCommand())
17+
networkCmd.AddCommand(networkRmCommand())
18+
networkCmd.AddCommand(networkListAttachedCommand())
1519
return networkCmd
1620
}

cmd/network_add.go

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package cmd
2+
3+
import (
4+
libvirtxml "github.com/libvirt/libvirt-go-xml"
5+
log "github.com/sirupsen/logrus"
6+
"github.com/spf13/cobra"
7+
"net"
8+
)
9+
10+
func networkAddCommand() *cobra.Command {
11+
var forward string
12+
var dhcp bool
13+
var network string
14+
var domain string
15+
16+
addCmd := &cobra.Command{
17+
Use: "add <name>",
18+
Short: "Add a new network",
19+
Long: `Add a new network. VMs can be attached to such a network in addition to the default network used by virter.`,
20+
Args: cobra.ExactArgs(1),
21+
Run: func(cmd *cobra.Command, args []string) {
22+
v, err := InitVirter()
23+
if err != nil {
24+
log.Fatal(err)
25+
}
26+
defer v.ForceDisconnect()
27+
28+
var forwardDesc *libvirtxml.NetworkForward
29+
if forward != "" {
30+
forwardDesc = &libvirtxml.NetworkForward{
31+
Mode: forward,
32+
}
33+
}
34+
35+
var addressesDesc []libvirtxml.NetworkIP
36+
if network != "" {
37+
ip, n, err := net.ParseCIDR(network)
38+
if err != nil {
39+
log.Fatal(err)
40+
}
41+
42+
var dhcpDesc *libvirtxml.NetworkDHCP
43+
if dhcp {
44+
start := nextIP(ip)
45+
end := previousIP(broadcastAddress(n))
46+
47+
n.Network()
48+
dhcpDesc = &libvirtxml.NetworkDHCP{
49+
Ranges: []libvirtxml.NetworkDHCPRange{{Start: start.String(), End: end.String()}},
50+
}
51+
}
52+
53+
addressesDesc = append(addressesDesc, libvirtxml.NetworkIP{
54+
Address: ip.String(),
55+
Netmask: net.IP(n.Mask).String(),
56+
DHCP: dhcpDesc,
57+
})
58+
}
59+
60+
var domainDesc *libvirtxml.NetworkDomain
61+
if domain != "" {
62+
domainDesc = &libvirtxml.NetworkDomain{
63+
Name: domain,
64+
LocalOnly: "yes",
65+
}
66+
}
67+
68+
desc := libvirtxml.Network{
69+
Name: args[0],
70+
Forward: forwardDesc,
71+
IPs: addressesDesc,
72+
Domain: domainDesc,
73+
}
74+
75+
err = v.NetworkAdd(desc)
76+
if err != nil {
77+
log.Fatal(err)
78+
}
79+
},
80+
}
81+
82+
addCmd.Flags().StringVarP(&forward, "forward-mode", "m", "", "Set the forward mode, for example 'nat'")
83+
addCmd.Flags().StringVarP(&network, "network-cidr", "n", "", "Configure the network range (IPv4) in CIDR notation. The IP will be assigned to the host device.")
84+
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")
85+
addCmd.Flags().StringVarP(&domain, "domain", "d", "", "Configure DNS names for the network")
86+
return addCmd
87+
}
88+
89+
func nextIP(ip net.IP) net.IP {
90+
dup := make(net.IP, len(ip))
91+
copy(dup, ip)
92+
for j := len(dup) - 1; j >= 0; j-- {
93+
dup[j]++
94+
if dup[j] > 0 {
95+
break
96+
}
97+
}
98+
return dup
99+
}
100+
101+
func previousIP(ip net.IP) net.IP {
102+
dup := make(net.IP, len(ip))
103+
copy(dup, ip)
104+
for j := len(dup) - 1; j >= 0; j-- {
105+
dup[j]--
106+
if dup[j] < 255 {
107+
break
108+
}
109+
}
110+
return dup
111+
}
112+
113+
func broadcastAddress(ipnet *net.IPNet) net.IP {
114+
dup := make(net.IP, len(ipnet.IP))
115+
copy(dup, ipnet.IP)
116+
for i := range dup {
117+
dup[i] = dup[i] | ^ipnet.Mask[i]
118+
}
119+
return dup
120+
}

cmd/network_list_attached.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package cmd
2+
3+
import (
4+
"github.com/rodaine/table"
5+
log "github.com/sirupsen/logrus"
6+
"github.com/spf13/cobra"
7+
)
8+
9+
func networkListAttachedCommand() *cobra.Command {
10+
listAttachedCmd := &cobra.Command{
11+
Use: "list-attached <network-name>",
12+
Short: "List VMs attached to a network",
13+
Long: `List VMs attached to a network. Includes IP address and hostname if available.`,
14+
Args: cobra.ExactArgs(1),
15+
Run: func(cmd *cobra.Command, args []string) {
16+
v, err := InitVirter()
17+
if err != nil {
18+
log.Fatal(err)
19+
}
20+
defer v.ForceDisconnect()
21+
22+
vmnics, err := v.NetworkListAttached(args[0])
23+
if err != nil {
24+
log.Fatal(err)
25+
}
26+
27+
tbl := table.New("VM", "MAC", "IP", "Hostname", "Host Device")
28+
for _, vmnic := range vmnics {
29+
tbl.AddRow(vmnic.VMName, vmnic.MAC, vmnic.IP, vmnic.HostName, vmnic.HostDevice)
30+
}
31+
tbl.Print()
32+
},
33+
}
34+
35+
return listAttachedCmd
36+
}

cmd/network_ls.go

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"github.com/rodaine/table"
6+
log "github.com/sirupsen/logrus"
7+
"github.com/spf13/viper"
8+
"net"
9+
"strings"
10+
11+
"github.com/spf13/cobra"
12+
)
13+
14+
func networkLsCommand() *cobra.Command {
15+
lsCmd := &cobra.Command{
16+
Use: "ls",
17+
Short: "List available networks",
18+
Long: `List available networks that VMs can be attached to.`,
19+
Args: cobra.NoArgs,
20+
Run: func(cmd *cobra.Command, args []string) {
21+
v, err := InitVirter()
22+
if err != nil {
23+
log.Fatal(err)
24+
}
25+
defer v.ForceDisconnect()
26+
27+
xmls, err := v.NetworkList()
28+
if err != nil {
29+
log.Fatal(err)
30+
}
31+
32+
virterNet := viper.GetString("libvirt.network")
33+
34+
tbl := table.New("Name", "Forward-Type", "IP-Range", "Domain", "DHCP", "Bridge")
35+
for _, desc := range xmls {
36+
name := desc.Name
37+
if name == virterNet {
38+
name = fmt.Sprintf("%s (virter default)", name)
39+
}
40+
ty := ""
41+
if desc.Forward != nil {
42+
ty = desc.Forward.Mode
43+
}
44+
45+
var ranges []string
46+
netrg := make([]string, len(desc.IPs))
47+
for i, n := range desc.IPs {
48+
ip := net.ParseIP(n.Address)
49+
mask := net.ParseIP(n.Netmask)
50+
netrg[i] = (&net.IPNet{IP: ip, Mask: net.IPMask(mask)}).String()
51+
52+
if n.DHCP != nil {
53+
for _, r := range n.DHCP.Ranges {
54+
ranges = append(ranges, fmt.Sprintf("%s-%s", r.Start, r.End))
55+
}
56+
}
57+
}
58+
netranges := strings.Join(netrg, ",")
59+
60+
domain := ""
61+
if desc.Domain != nil {
62+
domain = desc.Domain.Name
63+
}
64+
65+
dhcp := strings.Join(ranges, ",")
66+
67+
bridge := ""
68+
if desc.Bridge != nil {
69+
bridge = desc.Bridge.Name
70+
}
71+
72+
tbl.AddRow(name, ty, netranges, domain, dhcp, bridge)
73+
}
74+
75+
tbl.Print()
76+
},
77+
}
78+
79+
return lsCmd
80+
}

cmd/network_rm.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package cmd
2+
3+
import (
4+
log "github.com/sirupsen/logrus"
5+
"github.com/spf13/cobra"
6+
)
7+
8+
func networkRmCommand() *cobra.Command {
9+
rmCmd := &cobra.Command{
10+
Use: "rm <name>",
11+
Short: "Remove a network",
12+
Long: `Remove the named network.`,
13+
Args: cobra.ExactArgs(1),
14+
Run: func(cmd *cobra.Command, args []string) {
15+
v, err := InitVirter()
16+
if err != nil {
17+
log.Fatal(err)
18+
}
19+
defer v.ForceDisconnect()
20+
21+
err = v.NetworkRemove(args[0])
22+
if err != nil {
23+
log.Fatal(err)
24+
}
25+
},
26+
}
27+
return rmCmd
28+
}

cmd/nic.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package cmd
2+
3+
import (
4+
"github.com/LINBIT/virter/pkg/cliutils"
5+
)
6+
7+
type NICArg struct {
8+
NicType string `arg:"type"`
9+
Source string `arg:"source"`
10+
Model string `arg:"model,virtio"`
11+
MAC string `arg:"mac,"`
12+
}
13+
14+
func (n *NICArg) GetType() string {
15+
return n.NicType
16+
}
17+
18+
func (n *NICArg) GetSource() string {
19+
return n.Source
20+
}
21+
22+
func (n *NICArg) GetModel() string {
23+
return n.Model
24+
}
25+
26+
func (n *NICArg) GetMAC() string {
27+
return n.MAC
28+
}
29+
30+
func (n *NICArg) Set(str string) error {
31+
return cliutils.Parse(str, n)
32+
}
33+
34+
func (n *NICArg) Type() string {
35+
return "nic"
36+
}

cmd/vm_run.go

+15
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ func vmRunCommand() *cobra.Command {
105105
var diskStrings []string
106106
var disks []virter.Disk
107107

108+
var nicStrings []string
109+
var nics []virter.NIC
110+
108111
var provisionFile string
109112
var provisionOverrides []string
110113

@@ -125,6 +128,15 @@ func vmRunCommand() *cobra.Command {
125128
}
126129
disks = append(disks, &d)
127130
}
131+
132+
for _, s := range nicStrings {
133+
var n NICArg
134+
err := n.Set(s)
135+
if err != nil {
136+
log.Fatalf("Invalid nic: %v", err)
137+
}
138+
nics = append(nics, &n)
139+
}
128140
},
129141
Run: func(cmd *cobra.Command, args []string) {
130142
ctx, cancel := onInterruptWrap(context.Background())
@@ -206,6 +218,7 @@ func vmRunCommand() *cobra.Command {
206218
ExtraSSHPublicKeys: extraAuthorizedKeys,
207219
ConsolePath: consolePath,
208220
Disks: disks,
221+
ExtraNics: nics,
209222
GDBPort: thisGDBPort,
210223
}
211224

@@ -265,6 +278,8 @@ func vmRunCommand() *cobra.Command {
265278
// If this ever gets implemented in pflag , we will be able to solve this
266279
// in a much smoother way.
267280
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`)
281+
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`)
282+
268283
runCmd.Flags().StringVarP(&provisionFile, "provision", "p", "", "name of toml file containing provisioning steps")
269284
runCmd.Flags().StringArrayVarP(&provisionOverrides, "set", "s", []string{}, "set/override provisioning steps")
270285

0 commit comments

Comments
 (0)