/
eligible_instances.go
190 lines (157 loc) · 5.64 KB
/
eligible_instances.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// Copyright 2016 Netflix, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package term
import (
"strings"
"github.com/Netflix/chaosmonkey"
"github.com/Netflix/chaosmonkey/deploy"
"github.com/Netflix/chaosmonkey/grp"
)
// EligibleInstances returns a list of instances that belong to group that are eligible for termination
// It does not include any instances that match the list of exceptions
func EligibleInstances(group grp.InstanceGroup, cfg chaosmonkey.AppConfig, app *deploy.App) []*deploy.Instance {
if !cfg.Enabled {
return nil
}
/*
Pipeline: emit -> filterGroup -> filterWhitelist -> filterException -> filterCanaries -> toInstances
emit: generates all of the asgs for this app
filterGroup: filters out asgs that don't match the group
filterWhitelist: filters out asgs that don't match whitelist (deprecated, will be removed in the future)
filterExceptions: filters out asgs based on exception list
filterCanaries: filters out asgs that are part of canary deploys to avoid interfering with canary analysis
toInstances: converts ASGs to instances
*/
egchan := make(chan *deploy.ASG) // (emit -> filterGroup) channel
gwchan := make(chan *deploy.ASG) // (filterGroup -> filterWhitelist) channel
gechan := make(chan *deploy.ASG) // (filterGroup -> filterException) channel
ecchan := make(chan *deploy.ASG) // (filterExceptions -> filterCanaries) channel
cichan := make(chan *deploy.ASG) // (filterCanaries -> toInstance) channel
tichan := make(chan *deploy.Instance) // (toInstance -> <result> ) channel
go emit(app, egchan)
go filterGroup(egchan, gwchan, group)
go filterWhitelist(gwchan, gechan, cfg.Whitelist)
go filterExceptions(gechan, ecchan, cfg.Exceptions)
go filterCanaries(ecchan, cichan)
go toInstances(cichan, tichan)
result := []*deploy.Instance{}
for instance := range tichan {
result = append(result, instance)
}
return result
}
// emit takes all of the ASGs associated with an App and
// pushes them one at a time over a channel
func emit(app *deploy.App, dst chan<- *deploy.ASG) {
defer close(dst)
for _, account := range app.Accounts() {
for _, cluster := range account.Clusters() {
for _, asg := range cluster.ASGs() {
dst <- asg
}
}
}
}
// filterWhitelist receives ASGs from src and pushes them through dst if they
// match at least one element in the whitelist. If there's no whitelist,
// they all get through
func filterWhitelist(src <-chan *deploy.ASG, dst chan<- *deploy.ASG, pwl *[]chaosmonkey.Exception) {
defer close(dst)
for asg := range src {
if isWhitelisted(pwl, asg) {
dst <- asg
}
}
}
// isWhitelisted returns true if instances from the ASG
// match any of the elements of the whitelist
//
// If the whitelist pointer is null, it always returns true
func isWhitelisted(pwl *[]chaosmonkey.Exception, asg *deploy.ASG) bool {
return (pwl == nil) || isException(*pwl, asg)
}
// filterExceptions receives ASGs from src and pushes them through dst, unless
// there's an exception that matches, in which case it does not push that ASG
// through
func filterExceptions(src <-chan *deploy.ASG, dst chan<- *deploy.ASG, exs []chaosmonkey.Exception) {
defer close(dst)
for asg := range src {
if isException(exs, asg) {
continue
}
dst <- asg
}
}
// filterCanaries receives ASGs from src and pushes them through dst,
// unless the ASG is involved in canarying (name ends with -baseline or -canary)
func filterCanaries(src <-chan *deploy.ASG, dst chan<- *deploy.ASG) {
defer close(dst)
for asg := range src {
if isCanary(asg) {
continue
}
dst <- asg
}
}
// Returns true if asg is part of a canary deployment
func isCanary(asg *deploy.ASG) bool {
cluster := asg.ClusterName()
// If cluster name ends with an element of the blacklist, it's a canary
//
// TODO: Specify these in a configuration file instead of hard-coding
blacklist := []string{"-canary", "-baseline", "-citrus", "-citrusproxy"}
for _, suffix := range blacklist {
if strings.HasSuffix(cluster, suffix) {
return true
}
}
return false
}
// filterGroup receives ASGs from src, and sends ASGs
// to dst if the IntsanceGroup contains the ASG
func filterGroup(src <-chan *deploy.ASG, dst chan<- *deploy.ASG, group grp.InstanceGroup) {
defer close(dst)
for asg := range src {
if contains(group, asg) {
dst <- asg
}
}
}
// matches return true if the group contains the asg
func contains(group grp.InstanceGroup, asg *deploy.ASG) bool {
return grp.Contains(group, asg.AppName(), asg.AccountName(), asg.RegionName(), asg.StackName(), asg.ClusterName())
}
// toInstances reads ASGs from src, extracts the Instances and writes them to dst
func toInstances(src <-chan *deploy.ASG, dst chan<- *deploy.Instance) {
defer close(dst)
for asg := range src {
for _, instance := range asg.Instances() {
dst <- instance
}
}
}
// isException returns true if instances from the ASG match
// any of the exceptions
func isException(exs []chaosmonkey.Exception, asg *deploy.ASG) bool {
for _, ex := range exs {
account := asg.AccountName()
stack := asg.StackName()
detail := asg.DetailName()
region := asg.RegionName()
if ex.Matches(account, stack, detail, region) {
return true
}
}
return false
}