github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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  // AvailabilityZoneInstances describes an availability zone and
    37  // a set of instances in that zone.
    38  type AvailabilityZoneInstances struct {
    39  	// ZoneName is the name of the availability zone.
    40  	ZoneName string
    41  
    42  	// Instances is a set of instances within the availability zone.
    43  	Instances []instance.Id
    44  }
    45  
    46  type byPopulationThenName []AvailabilityZoneInstances
    47  
    48  func (b byPopulationThenName) Len() int {
    49  	return len(b)
    50  }
    51  
    52  func (b byPopulationThenName) Less(i, j int) bool {
    53  	switch {
    54  	case len(b[i].Instances) < len(b[j].Instances):
    55  		return true
    56  	case len(b[i].Instances) == len(b[j].Instances):
    57  		return b[i].ZoneName < b[j].ZoneName
    58  	}
    59  	return false
    60  }
    61  
    62  func (b byPopulationThenName) Swap(i, j int) {
    63  	b[i], b[j] = b[j], b[i]
    64  }
    65  
    66  // AvailabilityZoneAllocations returns the availability zones and their
    67  // instance allocations from the specified group, in ascending order of
    68  // population. Availability zones with the same population size are
    69  // ordered by name.
    70  //
    71  // If the specified group is empty, then it will behave as if the result of
    72  // AllInstances were provided.
    73  func AvailabilityZoneAllocations(env ZonedEnviron, group []instance.Id) ([]AvailabilityZoneInstances, error) {
    74  	if len(group) == 0 {
    75  		instances, err := env.AllInstances()
    76  		if err != nil {
    77  			return nil, err
    78  		}
    79  		group = make([]instance.Id, len(instances))
    80  		for i, inst := range instances {
    81  			group[i] = inst.Id()
    82  		}
    83  	}
    84  	instanceZones, err := env.InstanceAvailabilityZoneNames(group)
    85  	switch err {
    86  	case nil, environs.ErrPartialInstances:
    87  	case environs.ErrNoInstances:
    88  		group = nil
    89  	default:
    90  		return nil, err
    91  	}
    92  
    93  	// Get the list of all "available" availability zones,
    94  	// and then initialise a tally for each one.
    95  	zones, err := env.AvailabilityZones()
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	instancesByZoneName := make(map[string][]instance.Id)
   100  	for _, zone := range zones {
   101  		if !zone.Available() {
   102  			continue
   103  		}
   104  		name := zone.Name()
   105  		instancesByZoneName[name] = nil
   106  	}
   107  	if len(instancesByZoneName) == 0 {
   108  		return nil, nil
   109  	}
   110  
   111  	for i, id := range group {
   112  		zone := instanceZones[i]
   113  		if zone == "" {
   114  			continue
   115  		}
   116  		if _, ok := instancesByZoneName[zone]; !ok {
   117  			// zone is not available
   118  			continue
   119  		}
   120  		instancesByZoneName[zone] = append(instancesByZoneName[zone], id)
   121  	}
   122  
   123  	zoneInstances := make([]AvailabilityZoneInstances, 0, len(instancesByZoneName))
   124  	for zoneName, instances := range instancesByZoneName {
   125  		zoneInstances = append(zoneInstances, AvailabilityZoneInstances{
   126  			ZoneName:  zoneName,
   127  			Instances: instances,
   128  		})
   129  	}
   130  	sort.Sort(byPopulationThenName(zoneInstances))
   131  	return zoneInstances, nil
   132  }
   133  
   134  var internalAvailabilityZoneAllocations = AvailabilityZoneAllocations
   135  
   136  // DistributeInstances is a common function for implement the
   137  // state.InstanceDistributor policy based on availability zone
   138  // spread.
   139  func DistributeInstances(env ZonedEnviron, candidates, group []instance.Id) ([]instance.Id, error) {
   140  	// Determine the best availability zones for the group.
   141  	zoneInstances, err := internalAvailabilityZoneAllocations(env, group)
   142  	if err != nil || len(zoneInstances) == 0 {
   143  		return nil, err
   144  	}
   145  
   146  	// Determine which of the candidates are eligible based on whether
   147  	// they are allocated in one of the best availability zones.
   148  	var allEligible []string
   149  	for i := range zoneInstances {
   150  		if i > 0 && len(zoneInstances[i].Instances) > len(zoneInstances[i-1].Instances) {
   151  			break
   152  		}
   153  		for _, id := range zoneInstances[i].Instances {
   154  			allEligible = append(allEligible, string(id))
   155  		}
   156  	}
   157  	sort.Strings(allEligible)
   158  	eligible := make([]instance.Id, 0, len(candidates))
   159  	for _, candidate := range candidates {
   160  		n := sort.SearchStrings(allEligible, string(candidate))
   161  		if n >= 0 && n < len(allEligible) {
   162  			eligible = append(eligible, candidate)
   163  		}
   164  	}
   165  	return eligible, nil
   166  }