Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support importing libvirt VMs with multiple network interfaces. #626

Merged
merged 3 commits into from
Jul 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions cmd/vm-control/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,17 @@ Most operations only require a certificate that proves *identity*. The
issuing these certificates.

## Importing virsh (libvirt) VMs
A libvirt VM may be imported into the *Hypervisor*. This is only supported for
simple VMs with a single network interface and IP address. Once the VM is
*committed* it is removed from the libvirt database and is fully "owned" by the
A libvirt VM may be imported into the *Hypervisor*. Once the VM is *committed*
it is removed from the libvirt database and is fully "owned" by the
*Hypervisor*. Importing a VM requires root access on the *Hypervisor* (the
*vm-control* tool will use the `sudo` command if needed).

There are a few simple steps that should be followed to import a VM. In the
example below, the MAC address of the VM to be imported is `52:54:de:ad:be:ef`
and the hostname (DNS entry) is `jump.prod.company.com`. The IP address of the
VM may also be used. In either case, the hostname or IP address provided must
match the libvirt *domain name*.
match the libvirt *domain name*. If the VM has multiple network interfaces, the
MAC and IP address/FQDN for each interface must be provided in pairs.
- log into the VM and determine its MAC address
- run `vm-control import-virsh-vm 52:54:de:ad:be:ef jump.prod.company.com`
- enter `shutdown` at the prompt
Expand Down
52 changes: 42 additions & 10 deletions cmd/vm-control/importVirshVm.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,29 @@ type volumeType struct {
}

func importVirshVmSubcommand(args []string, logger log.DebugLogger) error {
if err := importVirshVm(args[0], args[1], logger); err != nil {
macAddr := args[0]
domainName := args[1]
args = args[2:]
if len(args)%2 != 0 {
return fmt.Errorf("missing IP address for MAC: %s", args[len(args)-1])
}
sAddrs := make([]proto.Address, 0, len(args)/2)
for index := 0; index < len(args); index += 2 {
ipAddr := args[index+1]
ipList, err := net.LookupIP(ipAddr)
if err != nil {
return err
}
if len(ipList) != 1 {
return fmt.Errorf("number of IPs for %s: %d != 1",
ipAddr, len(ipList))
}
sAddrs = append(sAddrs, proto.Address{
IpAddress: ipList[0],
MacAddress: args[index],
})
}
if err := importVirshVm(macAddr, domainName, sAddrs, logger); err != nil {
return fmt.Errorf("Error importing VM: %s", err)
}
return nil
Expand Down Expand Up @@ -165,7 +187,8 @@ func getDomainState(domainName string) (string, error) {
return strings.TrimSpace(string(stdout)), nil
}

func importVirshVm(macAddr, domainName string, logger log.DebugLogger) error {
func importVirshVm(macAddr, domainName string, sAddrs []proto.Address,
logger log.DebugLogger) error {
ipList, err := net.LookupIP(domainName)
if err != nil {
return err
Expand All @@ -183,7 +206,7 @@ func importVirshVm(macAddr, domainName string, logger log.DebugLogger) error {
OwnerUsers: ownerUsers,
Tags: tags,
}}
request.VerificationCookie, err = readRootCookie(logger)
verificationCookie, err := readRootCookie(logger)
if err != nil {
return err
}
Expand Down Expand Up @@ -211,18 +234,28 @@ func importVirshVm(macAddr, domainName string, logger log.DebugLogger) error {
if err := xml.Unmarshal(stdout, &virshInfo); err != nil {
return err
}
json.WriteWithIndent(os.Stdout, " ", virshInfo)
if numIf := len(virshInfo.Devices.Interfaces); numIf != len(sAddrs)+1 {
return fmt.Errorf("number of interfaces %d != %d",
numIf, len(sAddrs)+1)
}
if macAddr != virshInfo.Devices.Interfaces[0].Mac.Address {
return fmt.Errorf("MAC address specified: %s != virsh data: %s",
macAddr, virshInfo.Devices.Interfaces[0].Mac.Address)
}
json.WriteWithIndent(os.Stdout, " ", virshInfo)
if numIf := len(virshInfo.Devices.Interfaces); numIf != 1 {
return fmt.Errorf("number of interfaces %d != 1", numIf)
}
request.VmInfo.Address = proto.Address{
IpAddress: ipList[0],
MacAddress: virshInfo.Devices.Interfaces[0].Mac.Address,
}
for index, sAddr := range sAddrs {
if sAddr.MacAddress !=
virshInfo.Devices.Interfaces[index+1].Mac.Address {
return fmt.Errorf("MAC address specified: %s != virsh data: %s",
sAddr.MacAddress,
virshInfo.Devices.Interfaces[index+1].Mac.Address)
}
request.SecondaryAddresses = append(request.SecondaryAddresses, sAddr)
}
switch virshInfo.Memory.Unit {
case "KiB":
request.VmInfo.MemoryInMiB = virshInfo.Memory.Value >> 10
Expand Down Expand Up @@ -278,9 +311,8 @@ func importVirshVm(macAddr, domainName string, logger log.DebugLogger) error {
request.VmInfo.Volumes = append(request.VmInfo.Volumes,
proto.Volume{Format: volumeFormat})
}
requestWithoutSecrets := request
requestWithoutSecrets.VerificationCookie = nil
json.WriteWithIndent(os.Stdout, " ", requestWithoutSecrets)
json.WriteWithIndent(os.Stdout, " ", request)
request.VerificationCookie = verificationCookie
var reply proto.GetVmInfoResponse
logger.Debugln(0, "issuing import RPC")
err = client.RequestReply("Hypervisor.ImportLocalVm", request, &reply)
Expand Down
4 changes: 2 additions & 2 deletions cmd/vm-control/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func printUsage() {
fmt.Fprintln(os.Stderr, " get-vm-user-data IPaddr")
fmt.Fprintln(os.Stderr, " get-vm-volume IPaddr")
fmt.Fprintln(os.Stderr, " import-local-vm info-file root-volume")
fmt.Fprintln(os.Stderr, " import-virsh-vm MACaddr domain")
fmt.Fprintln(os.Stderr, " import-virsh-vm MACaddr domain [[MAC IP]...]")
fmt.Fprintln(os.Stderr, " list-hypervisors")
fmt.Fprintln(os.Stderr, " list-locations [TopLocation]")
fmt.Fprintln(os.Stderr, " list-vms")
Expand Down Expand Up @@ -171,7 +171,7 @@ var subcommands = []subcommand{
{"get-vm-user-data", 1, 1, getVmUserDataSubcommand},
{"get-vm-volume", 1, 1, getVmVolumeSubcommand},
{"import-local-vm", 2, 2, importLocalVmSubcommand},
{"import-virsh-vm", 2, 2, importVirshVmSubcommand},
{"import-virsh-vm", 2, -1, importVirshVmSubcommand},
{"list-hypervisors", 0, 0, listHypervisorsSubcommand},
{"list-locations", 0, 1, listLocationsSubcommand},
{"list-vms", 0, 0, listVMsSubcommand},
Expand Down
16 changes: 13 additions & 3 deletions hypervisor/manager/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,25 @@ func newManager(startOptions StartOptions) (*Manager, error) {
for _, addr := range manager.addressPool.Free {
freeIPs[addr.IpAddress.String()] = struct{}{}
}
secondaryIPs := make(map[string]struct{})
for _, vm := range manager.vms {
for _, addr := range vm.SecondaryAddresses {
secondaryIPs[addr.IpAddress.String()] = struct{}{}
}
}
for _, addr := range manager.addressPool.Registered {
ipAddr := addr.IpAddress.String()
if _, ok := freeIPs[ipAddr]; ok {
continue
}
if _, ok := manager.vms[ipAddr]; !ok {
manager.Logger.Printf("%s shown as used but no corresponding VM\n",
ipAddr)
if _, ok := manager.vms[ipAddr]; ok {
continue
}
if _, ok := secondaryIPs[ipAddr]; ok {
continue
}
manager.Logger.Printf("%s shown as used but no corresponding VM\n",
ipAddr)
}
if len(manager.volumeDirectories) < 1 {
manager.volumeDirectories, err = getVolumeDirectories()
Expand Down
35 changes: 32 additions & 3 deletions hypervisor/manager/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,23 @@ func (m *Manager) getVmVolume(conn *srpc.Conn) error {

func (m *Manager) importLocalVm(authInfo *srpc.AuthInformation,
request proto.ImportLocalVmRequest) error {
requestedIpAddrs := make(map[string]struct{},
1+len(request.SecondaryAddresses))
requestedMacAddrs := make(map[string]struct{},
1+len(request.SecondaryAddresses))
requestedIpAddrs[request.Address.IpAddress.String()] = struct{}{}
requestedMacAddrs[request.Address.MacAddress] = struct{}{}
for _, addr := range request.SecondaryAddresses {
ipAddr := addr.IpAddress.String()
if _, ok := requestedIpAddrs[ipAddr]; ok {
return fmt.Errorf("duplicate address: %s", ipAddr)
}
requestedIpAddrs[ipAddr] = struct{}{}
if _, ok := requestedMacAddrs[addr.MacAddress]; ok {
return fmt.Errorf("duplicate address: %s", addr.MacAddress)
}
requestedIpAddrs[addr.MacAddress] = struct{}{}
}
if !bytes.Equal(m.rootCookie, request.VerificationCookie) {
return fmt.Errorf("bad verification cookie: you are not root")
}
Expand Down Expand Up @@ -1138,16 +1155,28 @@ func (m *Manager) importLocalVm(authInfo *srpc.AuthInformation,
return fmt.Errorf("%s already exists", ipAddress)
}
for _, poolAddress := range m.addressPool.Registered {
if poolAddress.IpAddress.Equal(request.Address.IpAddress) ||
poolAddress.MacAddress == request.Address.MacAddress {
return fmt.Errorf("%s is in address pool", ipAddress)
ipAddr := poolAddress.IpAddress.String()
if _, ok := requestedIpAddrs[ipAddr]; ok {
return fmt.Errorf("%s is in address pool", ipAddr)
}
if _, ok := requestedMacAddrs[poolAddress.MacAddress]; ok {
return fmt.Errorf("%s is in address pool", poolAddress.MacAddress)
}
}
subnetId := m.getMatchingSubnet(request.Address.IpAddress)
if subnetId == "" {
return fmt.Errorf("no matching subnet for: %s\n", ipAddress)
}
vm.VmInfo.SubnetId = subnetId
vm.VmInfo.SecondarySubnetIDs = nil
for _, addr := range request.SecondaryAddresses {
subnetId := m.getMatchingSubnet(addr.IpAddress)
if subnetId == "" {
return fmt.Errorf("no matching subnet for: %s\n", addr.IpAddress)
}
vm.VmInfo.SecondarySubnetIDs = append(vm.VmInfo.SecondarySubnetIDs,
subnetId)
}
defer func() {
if vm == nil {
return
Expand Down