Skip to content

Commit

Permalink
Merge pull request #626 from rgooch/master
Browse files Browse the repository at this point in the history
Support importing libvirt VMs with multiple network interfaces.
  • Loading branch information
rgooch committed Jul 6, 2019
2 parents 3b98442 + 95b2389 commit b049cff
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 22 deletions.
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

0 comments on commit b049cff

Please sign in to comment.