~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/provider/common/availabilityzones.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
}