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 }