1
// Copyright 2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
9
"github.com/juju/juju/environs"
10
"github.com/juju/juju/instance"
13
// AvailabilityZone describes a provider availability zone.
14
type AvailabilityZone interface {
15
// Name returns the name of the availability zone.
18
// Available reports whether the availability zone is currently available.
22
// ZonedEnviron is an environs.Environ that has support for
23
// availability zones.
24
type ZonedEnviron interface {
27
// AvailabilityZones returns all availability zones in the environment.
28
AvailabilityZones() ([]AvailabilityZone, error)
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)
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.
42
// Instances is a set of instances within the availability zone.
43
Instances []instance.Id
46
type byPopulationThenName []AvailabilityZoneInstances
48
func (b byPopulationThenName) Len() int {
52
func (b byPopulationThenName) Less(i, j int) bool {
54
case len(b[i].Instances) < len(b[j].Instances):
56
case len(b[i].Instances) == len(b[j].Instances):
57
return b[i].ZoneName < b[j].ZoneName
62
func (b byPopulationThenName) Swap(i, j int) {
63
b[i], b[j] = b[j], b[i]
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
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) {
75
instances, err := env.AllInstances()
79
group = make([]instance.Id, len(instances))
80
for i, inst := range instances {
84
instanceZones, err := env.InstanceAvailabilityZoneNames(group)
86
case nil, environs.ErrPartialInstances:
87
case environs.ErrNoInstances:
93
// Get the list of all "available" availability zones,
94
// and then initialise a tally for each one.
95
zones, err := env.AvailabilityZones()
99
instancesByZoneName := make(map[string][]instance.Id)
100
for _, zone := range zones {
101
if !zone.Available() {
105
instancesByZoneName[name] = nil
107
if len(instancesByZoneName) == 0 {
111
for i, id := range group {
112
zone := instanceZones[i]
116
if _, ok := instancesByZoneName[zone]; !ok {
117
// zone is not available
120
instancesByZoneName[zone] = append(instancesByZoneName[zone], id)
123
zoneInstances := make([]AvailabilityZoneInstances, 0, len(instancesByZoneName))
124
for zoneName, instances := range instancesByZoneName {
125
zoneInstances = append(zoneInstances, AvailabilityZoneInstances{
127
Instances: instances,
130
sort.Sort(byPopulationThenName(zoneInstances))
131
return zoneInstances, nil
134
var internalAvailabilityZoneAllocations = AvailabilityZoneAllocations
136
// DistributeInstances is a common function for implement the
137
// state.InstanceDistributor policy based on availability zone
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 {
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) {
153
for _, id := range zoneInstances[i].Instances {
154
allEligible = append(allEligible, string(id))
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)