|
6 | 6 | "errors"
|
7 | 7 | "fmt"
|
8 | 8 | "log"
|
| 9 | + "net" |
9 | 10 | "net/http"
|
10 | 11 | "os"
|
11 | 12 | "strconv"
|
@@ -135,15 +136,17 @@ type Vm struct {
|
135 | 136 | // These fields are used for passing in disk inputs when
|
136 | 137 | // creating Vms, however, this is not a real field as far
|
137 | 138 | // as the XO api or XAPI is concerned
|
138 |
| - Disks []Disk `json:"-"` |
139 |
| - CloudNetworkConfig string `json:"-"` |
140 |
| - VIFsMap []map[string]string `json:"-"` |
141 |
| - WaitForIps bool `json:"-"` |
142 |
| - Installation Installation `json:"-"` |
143 |
| - ManagementAgentDetected bool `json:"managementAgentDetected"` |
144 |
| - PVDriversDetected bool `json:"pvDriversDetected"` |
145 |
| - DestroyCloudConfigVdiAfterBoot bool `json:"-"` |
146 |
| - CloneType string `json:"-"` |
| 139 | + Disks []Disk `json:"-"` |
| 140 | + CloudNetworkConfig string `json:"-"` |
| 141 | + VIFsMap []map[string]string `json:"-"` |
| 142 | + // Map where the key is the network interface index and the |
| 143 | + // value is a cidr range parsable by net.ParseCIDR |
| 144 | + WaitForIps map[string]string `json:"-"` |
| 145 | + Installation Installation `json:"-"` |
| 146 | + ManagementAgentDetected bool `json:"managementAgentDetected"` |
| 147 | + PVDriversDetected bool `json:"pvDriversDetected"` |
| 148 | + DestroyCloudConfigVdiAfterBoot bool `json:"-"` |
| 149 | + CloneType string `json:"-"` |
147 | 150 | }
|
148 | 151 |
|
149 | 152 | type Installation struct {
|
@@ -603,58 +606,117 @@ func (c *Client) waitForVmState(id string, stateConf StateChangeConf) error {
|
603 | 606 | return err
|
604 | 607 | }
|
605 | 608 |
|
606 |
| -func (c *Client) waitForModifyVm(id string, desiredPowerState string, waitForIp bool, timeout time.Duration) error { |
607 |
| - if !waitForIp { |
608 |
| - var pending []string |
609 |
| - target := desiredPowerState |
610 |
| - switch desiredPowerState { |
611 |
| - case RunningPowerState: |
612 |
| - pending = []string{HaltedPowerState} |
613 |
| - case HaltedPowerState: |
614 |
| - pending = []string{RunningPowerState} |
615 |
| - default: |
616 |
| - return errors.New(fmt.Sprintf("Invalid VM power state requested: %s\n", desiredPowerState)) |
| 609 | +func waitForPowerStateReached(c *Client, vmId, desiredPowerState string, timeout time.Duration) error { |
| 610 | + var pending []string |
| 611 | + target := desiredPowerState |
| 612 | + switch desiredPowerState { |
| 613 | + case RunningPowerState: |
| 614 | + pending = []string{HaltedPowerState} |
| 615 | + case HaltedPowerState: |
| 616 | + pending = []string{RunningPowerState} |
| 617 | + default: |
| 618 | + return errors.New(fmt.Sprintf("Invalid VM power state requested: %s\n", desiredPowerState)) |
| 619 | + } |
| 620 | + refreshFn := func() (result interface{}, state string, err error) { |
| 621 | + vm, err := c.GetVm(Vm{Id: vmId}) |
| 622 | + |
| 623 | + if err != nil { |
| 624 | + return vm, "", err |
617 | 625 | }
|
618 |
| - refreshFn := func() (result interface{}, state string, err error) { |
619 |
| - vm, err := c.GetVm(Vm{Id: id}) |
620 | 626 |
|
621 |
| - if err != nil { |
622 |
| - return vm, "", err |
623 |
| - } |
| 627 | + return vm, vm.PowerState, nil |
| 628 | + } |
| 629 | + stateConf := &StateChangeConf{ |
| 630 | + Pending: pending, |
| 631 | + Refresh: refreshFn, |
| 632 | + Target: []string{target}, |
| 633 | + Timeout: timeout, |
| 634 | + } |
| 635 | + _, err := stateConf.WaitForState() |
| 636 | + return err |
| 637 | +} |
| 638 | + |
| 639 | +type ifaceMatchCheck struct { |
| 640 | + cidrRange string |
| 641 | + ifaceIdx string |
| 642 | + ifaceAddrs []string |
| 643 | +} |
624 | 644 |
|
625 |
| - return vm, vm.PowerState, nil |
| 645 | +func waitForIPAssignment(c *Client, vmId string, waitForIps map[string]string, timeout time.Duration) error { |
| 646 | + var lastResult ifaceMatchCheck |
| 647 | + refreshFn := func() (result interface{}, state string, err error) { |
| 648 | + vm, err := c.GetVm(Vm{Id: vmId}) |
| 649 | + |
| 650 | + if err != nil { |
| 651 | + return vm, "", err |
626 | 652 | }
|
627 |
| - stateConf := &StateChangeConf{ |
628 |
| - Pending: pending, |
629 |
| - Refresh: refreshFn, |
630 |
| - Target: []string{target}, |
631 |
| - Timeout: timeout, |
| 653 | + |
| 654 | + addrs := vm.Addresses |
| 655 | + if len(addrs) == 0 || vm.PowerState != RunningPowerState { |
| 656 | + return addrs, "Waiting", nil |
632 | 657 | }
|
633 |
| - _, err := stateConf.WaitForState() |
634 |
| - return err |
635 |
| - } else { |
636 |
| - refreshFn := func() (result interface{}, state string, err error) { |
637 |
| - vm, err := c.GetVm(Vm{Id: id}) |
638 | 658 |
|
639 |
| - if err != nil { |
640 |
| - return vm, "", err |
| 659 | + netIfaces := map[string][]string{} |
| 660 | + for key, addr := range vm.Addresses { |
| 661 | + |
| 662 | + // key has the following format "{iface_id}/(ipv4|ipv6)/{iface_ip_id}" |
| 663 | + ifaceIdx, _, _ := strings.Cut(key, "/") |
| 664 | + if _, ok := netIfaces[ifaceIdx]; !ok { |
| 665 | + netIfaces[ifaceIdx] = []string{} |
641 | 666 | }
|
| 667 | + netIfaces[ifaceIdx] = append(netIfaces[ifaceIdx], addr) |
| 668 | + } |
642 | 669 |
|
643 |
| - l := len(vm.Addresses) |
644 |
| - if l == 0 || vm.PowerState != RunningPowerState { |
645 |
| - return vm, "Waiting", nil |
| 670 | + for ifaceIdx, cidrRange := range waitForIps { |
| 671 | + // VM's Addresses member does not contain this network interface yet |
| 672 | + if _, ok := netIfaces[ifaceIdx]; !ok { |
| 673 | + return addrs, "Waiting", nil |
646 | 674 | }
|
647 | 675 |
|
648 |
| - return vm, "Ready", nil |
649 |
| - } |
650 |
| - stateConf := &StateChangeConf{ |
651 |
| - Pending: []string{"Waiting"}, |
652 |
| - Refresh: refreshFn, |
653 |
| - Target: []string{"Ready"}, |
654 |
| - Timeout: timeout, |
| 676 | + found := false |
| 677 | + for _, ipAddr := range netIfaces[ifaceIdx] { |
| 678 | + _, ipNet, err := net.ParseCIDR(cidrRange) |
| 679 | + |
| 680 | + if err != nil { |
| 681 | + return addrs, "Waiting", err |
| 682 | + } |
| 683 | + |
| 684 | + if ipNet.Contains(net.ParseIP(ipAddr)) { |
| 685 | + found = true |
| 686 | + } |
| 687 | + } |
| 688 | + |
| 689 | + if !found { |
| 690 | + lastResult = ifaceMatchCheck{ |
| 691 | + cidrRange: cidrRange, |
| 692 | + ifaceIdx: ifaceIdx, |
| 693 | + ifaceAddrs: netIfaces[ifaceIdx], |
| 694 | + } |
| 695 | + |
| 696 | + return addrs, "Waiting", nil |
| 697 | + } |
655 | 698 | }
|
656 |
| - _, err := stateConf.WaitForState() |
657 |
| - return err |
| 699 | + |
| 700 | + return addrs, "Ready", nil |
| 701 | + } |
| 702 | + stateConf := &StateChangeConf{ |
| 703 | + Pending: []string{"Waiting"}, |
| 704 | + Refresh: refreshFn, |
| 705 | + Target: []string{"Ready"}, |
| 706 | + Timeout: timeout, |
| 707 | + } |
| 708 | + _, err := stateConf.WaitForState() |
| 709 | + if _, ok := err.(*TimeoutError); ok { |
| 710 | + return errors.New(fmt.Sprintf("network[%s] never converged to the following cidr: %s, addresses: %s failed to match", lastResult.ifaceIdx, lastResult.cidrRange, lastResult.ifaceAddrs)) |
| 711 | + } |
| 712 | + return err |
| 713 | +} |
| 714 | + |
| 715 | +func (c *Client) waitForModifyVm(id string, desiredPowerState string, waitForIps map[string]string, timeout time.Duration) error { |
| 716 | + if len(waitForIps) == 0 { |
| 717 | + return waitForPowerStateReached(c, id, desiredPowerState, timeout) |
| 718 | + } else { |
| 719 | + return waitForIPAssignment(c, id, waitForIps, timeout) |
658 | 720 | }
|
659 | 721 | }
|
660 | 722 |
|
|
0 commit comments