github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/fleetmanager/hypervisors/moveIpAddresses.go (about)

     1  package hypervisors
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"time"
     7  
     8  	"github.com/Cloud-Foundations/Dominator/lib/constants"
     9  	"github.com/Cloud-Foundations/Dominator/lib/errors"
    10  	"github.com/Cloud-Foundations/Dominator/lib/net/util"
    11  	"github.com/Cloud-Foundations/Dominator/lib/srpc"
    12  	hyper_proto "github.com/Cloud-Foundations/Dominator/proto/hypervisor"
    13  )
    14  
    15  func (m *Manager) addIp(hypervisorIpAddress, ip net.IP) error {
    16  	client, err := srpc.DialHTTP("tcp",
    17  		fmt.Sprintf("%s:%d",
    18  			hypervisorIpAddress, constants.HypervisorPortNumber),
    19  		time.Second*15)
    20  	if err != nil {
    21  		return err
    22  	}
    23  	defer client.Close()
    24  	request := hyper_proto.ChangeAddressPoolRequest{
    25  		AddressesToAdd: []hyper_proto.Address{{
    26  			IpAddress: ip,
    27  			MacAddress: fmt.Sprintf("52:54:%02x:%02x:%02x:%02x",
    28  				ip[0], ip[1], ip[2], ip[3]),
    29  		}},
    30  	}
    31  	var reply hyper_proto.ChangeAddressPoolResponse
    32  	err = client.RequestReply("Hypervisor.ChangeAddressPool", request, &reply)
    33  	if err != nil {
    34  		return err
    35  	}
    36  	return errors.New(reply.Error)
    37  }
    38  
    39  func (m *Manager) getHealthyHypervisorAddr(hostname string) (net.IP, error) {
    40  	hypervisor, err := m.getLockedHypervisor(hostname, false)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  	defer hypervisor.mutex.RUnlock()
    45  	if hypervisor.healthStatus == "marginal" ||
    46  		hypervisor.healthStatus == "at risk" {
    47  		return nil, errors.New("cannot move IPs to unhealthy hypervisor")
    48  	}
    49  	if len(hypervisor.machine.HostIpAddress) < 1 {
    50  		return nil, fmt.Errorf("IP address for: %s not known", hostname)
    51  	}
    52  	return hypervisor.machine.HostIpAddress, nil
    53  }
    54  
    55  func (m *Manager) markIPsForMigration(ipAddresses []net.IP) error {
    56  	m.mutex.Lock()
    57  	defer m.mutex.Unlock()
    58  	if num := len(m.migratingIPs); num > 0 {
    59  		return fmt.Errorf("%d other migrations in progress: %v",
    60  			num, m.migratingIPs)
    61  	}
    62  	for _, ip := range ipAddresses {
    63  		if len(ip) > 0 {
    64  			m.migratingIPs[ip.String()] = struct{}{}
    65  		}
    66  	}
    67  	return nil
    68  }
    69  
    70  func (m *Manager) moveIpAddresses(hostname string, ipAddresses []net.IP) error {
    71  	if !*manageHypervisors {
    72  		return errors.New("this is a read-only Fleet Manager")
    73  	}
    74  	if len(ipAddresses) < 1 {
    75  		return nil
    76  	}
    77  	sourceHypervisorIPs := make([]net.IP, len(ipAddresses))
    78  	for index, ip := range ipAddresses {
    79  		ip = util.ShrinkIP(ip)
    80  		ipAddresses[index] = ip
    81  		sourceHypervisorIp, err := m.storer.GetHypervisorForIp(ip)
    82  		if err != nil {
    83  			return err
    84  		}
    85  		sourceHypervisorIPs[index] = sourceHypervisorIp
    86  	}
    87  	hypervisorIpAddress, err := m.getHealthyHypervisorAddr(hostname)
    88  	if err != nil {
    89  		return err
    90  	}
    91  	if err := m.markIPsForMigration(ipAddresses); err != nil {
    92  		return err
    93  	}
    94  	defer m.unmarkIPsForMigration(ipAddresses)
    95  	// Move IPs.
    96  	for index, ip := range ipAddresses {
    97  		err := m.moveIpAddress(hypervisorIpAddress, sourceHypervisorIPs[index],
    98  			ip)
    99  		if err != nil {
   100  			return err
   101  		}
   102  	}
   103  	// Wait for IPs to have moved.
   104  	// TODO(rgooch): Change this to watch for the registration events.
   105  	stopTime := time.Now().Add(time.Second * 10)
   106  	for ; time.Until(stopTime) >= 0; time.Sleep(time.Millisecond * 10) {
   107  		allInPlace := true
   108  		for _, ip := range ipAddresses {
   109  			newHyperIp, err := m.storer.GetHypervisorForIp(ip)
   110  			if err != nil {
   111  				return err
   112  			}
   113  			if newHyperIp == nil || !newHyperIp.Equal(hypervisorIpAddress) {
   114  				allInPlace = false
   115  				break // Not yet registered with the destination Hypervisor.
   116  			}
   117  		}
   118  		if allInPlace {
   119  			return nil
   120  		}
   121  	}
   122  	return errors.New("timed out waiting for addresses to move")
   123  }
   124  
   125  func (m *Manager) moveIpAddress(destinationHypervisorIpAddress,
   126  	sourceHypervisorIpAddress, ipToMove net.IP) error {
   127  	if sourceHypervisorIpAddress != nil {
   128  		if sourceHypervisorIpAddress.Equal(destinationHypervisorIpAddress) {
   129  			return nil // IP address is already registered to dest Hypervisor.
   130  		}
   131  		err := m.removeIpAndWait(sourceHypervisorIpAddress, ipToMove)
   132  		if err != nil {
   133  			return err
   134  		}
   135  	}
   136  	return m.addIp(destinationHypervisorIpAddress, ipToMove)
   137  }
   138  
   139  func (m *Manager) removeIpAndWait(hypervisorIpAddress, ipToMove net.IP) error {
   140  	client, err := srpc.DialHTTP("tcp",
   141  		fmt.Sprintf("%s:%d",
   142  			hypervisorIpAddress, constants.HypervisorPortNumber),
   143  		time.Second*15)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	defer client.Close()
   148  	request := hyper_proto.ChangeAddressPoolRequest{
   149  		AddressesToRemove: []hyper_proto.Address{{IpAddress: ipToMove}},
   150  	}
   151  	var reply hyper_proto.ChangeAddressPoolResponse
   152  	err = client.RequestReply("Hypervisor.ChangeAddressPool", request, &reply)
   153  	if err != nil {
   154  		return err
   155  	}
   156  	if err := errors.New(reply.Error); err != nil {
   157  		return fmt.Errorf("error unregistering %s from %s: %s",
   158  			ipToMove, hypervisorIpAddress, err)
   159  	}
   160  	// TODO(rgooch): Change this to watch for the deregistration event.
   161  	stopTime := time.Now().Add(time.Second * 10)
   162  	for ; time.Until(stopTime) >= 0; time.Sleep(time.Millisecond * 10) {
   163  		newHyperIp, err := m.storer.GetHypervisorForIp(ipToMove)
   164  		if err != nil {
   165  			return err
   166  		}
   167  		if newHyperIp == nil {
   168  			return nil // No longer registered with a Hypervisor.
   169  		}
   170  	}
   171  	return fmt.Errorf(
   172  		"timed out waiting for %s to become unregistered from %s",
   173  		ipToMove, hypervisorIpAddress)
   174  }
   175  
   176  func (m *Manager) unmarkIPsForMigration(ipAddresses []net.IP) {
   177  	m.mutex.Lock()
   178  	defer m.mutex.Unlock()
   179  	for _, ip := range ipAddresses {
   180  		if len(ip) > 0 {
   181  			delete(m.migratingIPs, ip.String())
   182  		}
   183  	}
   184  }