github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/provider/common/availabilityzones.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package common
     5  
     6  import (
     7  	"sort"
     8  
     9  	"github.com/juju/juju/environs"
    10  	"github.com/juju/juju/instance"
    11  )
    12  
    13  // AvailabilityZone describes a provider availability zone.
    14  type AvailabilityZone interface {
    15  	// Name returns the name of the availability zone.
    16  	Name() string
    17  
    18  	// Available reports whether the availability zone is currently available.
    19  	Available() bool
    20  }
    21  
    22  // ZonedEnviron is an environs.Environ that has support for
    23  // availability zones.
    24  type ZonedEnviron interface {
    25  	environs.Environ
    26  
    27  	// AvailabilityZones returns all availability zones in the environment.
    28  	AvailabilityZones() ([]AvailabilityZone, error)
    29  
    30  	// InstanceAvailabilityZoneNames returns the names of the availability
    31  	// zones for the specified instances. The error returned follows the same
    32  	// rules as Environ.Instances.
    33  	InstanceAvailabilityZoneNames(ids []instance.Id) ([]string, error)
    34  }
    35  
    36  // BestAvailabilityZoneAllocations returns the availability zones with the
    37  // fewest instances from the specified group, along with the instances from
    38  // the group currently allocated to those zones.
    39  //
    40  // If the specified group is empty, then it will behave as if the result of
    41  // AllInstances were provided.
    42  func BestAvailabilityZoneAllocations(env ZonedEnviron, group []instance.Id) (map[string][]instance.Id, error) {
    43  	if len(group) == 0 {
    44  		instances, err := env.AllInstances()
    45  		if err != nil {
    46  			return nil, err
    47  		}
    48  		group = make([]instance.Id, len(instances))
    49  		for i, inst := range instances {
    50  			group[i] = inst.Id()
    51  		}
    52  	}
    53  	instanceZones, err := env.InstanceAvailabilityZoneNames(group)
    54  	if err != nil && err != environs.ErrPartialInstances {
    55  		return nil, err
    56  	}
    57  
    58  	// Get the list of all "available" availability zones,
    59  	// and then initialise a tally for each one.
    60  	zones, err := env.AvailabilityZones()
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	if len(zones) == 0 {
    65  		return nil, nil
    66  	}
    67  	zoneTally := make(map[string]int, len(zones))
    68  	for _, zone := range zones {
    69  		if !zone.Available() {
    70  			continue
    71  		}
    72  		zoneTally[zone.Name()] = 0
    73  	}
    74  
    75  	// Tally the zones with provisioned instances and return
    76  	// the zones with equal fewest instances from the group.
    77  	zoneInstances := make(map[string][]instance.Id)
    78  	for zone := range zoneTally {
    79  		// Initialise each zone's instance slice to nil,
    80  		// in case there are zones without instances.
    81  		zoneInstances[zone] = nil
    82  	}
    83  	for i, id := range group {
    84  		zone := instanceZones[i]
    85  		if zone == "" {
    86  			continue
    87  		}
    88  		if _, ok := zoneTally[zone]; !ok {
    89  			// zone is not available
    90  			continue
    91  		}
    92  		zoneTally[zone] += 1
    93  		zoneInstances[zone] = append(zoneInstances[zone], id)
    94  	}
    95  	minZoneTally := -1
    96  	for _, tally := range zoneTally {
    97  		if minZoneTally == -1 || tally < minZoneTally {
    98  			minZoneTally = tally
    99  		}
   100  	}
   101  	for zone, tally := range zoneTally {
   102  		if tally > minZoneTally {
   103  			delete(zoneInstances, zone)
   104  		}
   105  	}
   106  	return zoneInstances, nil
   107  }
   108  
   109  var internalBestAvailabilityZoneAllocations = BestAvailabilityZoneAllocations
   110  
   111  // DistributeInstances is a common function for implement the
   112  // state.InstanceDistributor policy based on availability zone
   113  // spread.
   114  func DistributeInstances(env ZonedEnviron, candidates, group []instance.Id) ([]instance.Id, error) {
   115  	// Determine the best availability zones for the group.
   116  	bestAvailabilityZones, err := internalBestAvailabilityZoneAllocations(env, group)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	// Determine which of the candidates are eligible based on whether
   122  	// they are allocated in one of the best availability zones.
   123  	var allEligible []string
   124  	for _, ids := range bestAvailabilityZones {
   125  		for _, id := range ids {
   126  			allEligible = append(allEligible, string(id))
   127  		}
   128  	}
   129  	sort.Strings(allEligible)
   130  	eligible := make([]instance.Id, 0, len(candidates))
   131  	for _, candidate := range candidates {
   132  		n := sort.SearchStrings(allEligible, string(candidate))
   133  		if n >= 0 && n < len(allEligible) {
   134  			eligible = append(eligible, candidate)
   135  		}
   136  	}
   137  	return eligible, nil
   138  }