github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 }