forked from juju/juju
-
Notifications
You must be signed in to change notification settings - Fork 0
/
integrity.go
358 lines (306 loc) · 11.2 KB
/
integrity.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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
// Copyright 2020 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package spaces
import (
"fmt"
"strings"
"github.com/juju/collections/set"
"github.com/juju/errors"
"github.com/DavinZhang/juju/core/network"
)
// unitNetwork represents a group of units and the subnets
// to which they are *all* connected.
type unitNetwork struct {
unitNames set.Strings
// subnets are those that all the units are connected to.
// Not that these are populated from the *current* network topology.
subnets network.SubnetInfos
}
// hasSameConnectivity returns true if the input set of subnets
// matches the unitNetwork subnets exactly, based on ID.
func (n *unitNetwork) hasSameConnectivity(subnets network.SubnetInfos) bool {
return n.subnets.EqualTo(subnets)
}
// remainsConnectedTo returns true if the unitNetwork has a subnet in common
// with the input future representation of the target space.
// Note that we compare subnet IDs and not space IDs, because the subnets are
// populated from the existing topology, whereas the input space comes from
// the hypothetical new topology where one or more subnets have moved there.
func (n *unitNetwork) remainsConnectedTo(space network.SpaceInfo) bool {
spaceSubs := space.Subnets
for _, sub := range n.subnets {
if spaceSubs.ContainsID(sub.ID) {
return true
}
}
return false
}
// affectedNetworks groups unique unit networks by application name.
// It facilitates checking whether the connectedness of application units
// is able to honour changing space topology based on application
// constraints and endpoint bindings.
type affectedNetworks struct {
// subnets identifies the subnets that are being moved.
subnets network.IDSet
// newSpace is the name of the space that the subnets are being moved to.
newSpace string
// spaces is a the target space topology.
spaces network.SpaceInfos
// changingNetworks is all unit subnet connectivity grouped by application
// for any that may be affected by moving the subnets above.
changingNetworks map[string][]unitNetwork
// unchangedNetworks is all unit subnet connectivity grouped by application
// for those that are unaffected by moving subnets.
// These are included in order to determine whether application endpoint
// bindings can be massaged to satisfy the mutating space topology.
unchangedNetworks map[string][]unitNetwork
// force originates as a CLI option.
// When true, violations of constraints/bindings integrity are logged as
// warnings instead of being returned as errors.
force bool
}
// newAffectedNetworks returns a new affectedNetworks reference for
// verification of the movement of the input subnets to the input space.
// The input space topology is manipulated to represent the topology that
// would result from the move.
func newAffectedNetworks(
movingSubnets network.IDSet, spaceName string, currentTopology network.SpaceInfos, force bool,
) (*affectedNetworks, error) {
// We need to indicate that any moving fan underlays include
// their overlays as being affected by a move.
movingOverlays, err := currentTopology.FanOverlaysFor(movingSubnets)
if err != nil {
return nil, errors.Trace(err)
}
for _, overlay := range movingOverlays {
movingSubnets.Add(overlay.ID)
}
// Now get the topology as would result from moving all of these subnets.
newTopology, err := currentTopology.MoveSubnets(movingSubnets, spaceName)
if err != nil {
return nil, errors.Trace(err)
}
return &affectedNetworks{
subnets: movingSubnets,
newSpace: spaceName,
spaces: newTopology,
changingNetworks: make(map[string][]unitNetwork),
unchangedNetworks: make(map[string][]unitNetwork),
force: force,
}, nil
}
// processMachines iterates over the input machines,
// looking at the subnets they are connected to.
// Any machines connected to a moving subnet have their unit networks
// included for for later verification.
func (n *affectedNetworks) processMachines(machines []Machine) error {
for _, machine := range machines {
addresses, err := machine.AllAddresses()
if err != nil {
return errors.Trace(err)
}
var includesMover bool
var machineSubnets network.SubnetInfos
for _, address := range addresses {
// These are not going to have subnets, so just ignore them.
if address.ConfigMethod() == network.ConfigLoopback {
continue
}
sub, err := n.addressSubnet(address)
if err != nil {
return errors.Trace(err)
}
machineSubnets = append(machineSubnets, sub)
if n.subnets.Contains(sub.ID) {
includesMover = true
}
}
if err = n.includeMachine(machine, machineSubnets, includesMover); err != nil {
return errors.Trace(err)
}
}
return nil
}
func (n *affectedNetworks) addressSubnet(addr Address) (network.SubnetInfo, error) {
allSubs, err := n.spaces.AllSubnetInfos()
if err != nil {
return network.SubnetInfo{}, errors.Trace(err)
}
subs, err := allSubs.GetByCIDR(addr.SubnetCIDR())
if err != nil {
return network.SubnetInfo{}, errors.Trace(err)
}
// TODO (manadart 2020-05-07): This is done on the basis of CIDR still
// uniquely identifying a subnet.
// It will have to change for multi-network enablement.
if len(subs) > 0 {
return subs[0], nil
}
// If the address CIDR was not located in our network topology,
// it *may* be due to the fact that fan addresses are indicated as being
// part of their *overlay* (such as 252.0.0.0/8), rather than the
// zone-specific segments of the overlay (such as 252.32.0.0/12)
// as seen in AWS.
// Try to locate a subnet based on the address itself.
subs, err = allSubs.GetByAddress(addr.Value())
if err != nil {
return network.SubnetInfo{}, errors.Trace(err)
}
if len(subs) > 0 {
return subs[0], nil
}
return network.SubnetInfo{}, errors.NotFoundf("subnet for machine address %q", addr.Value())
}
// includeMachine ensures that the units on the machine and their collection
// of subnet connectedness are included as networks to be validated.
// The collection they are placed into depends on whether they are connected to
// a moving subnet, indicated by the netChange argument.
func (n *affectedNetworks) includeMachine(machine Machine, subnets network.SubnetInfos, netChange bool) error {
units, err := machine.Units()
if err != nil {
return errors.Trace(err)
}
collection := n.unchangedNetworks
if netChange {
collection = n.changingNetworks
}
for _, unit := range units {
appName := unit.ApplicationName()
unitNets, ok := collection[appName]
if !ok {
collection[appName] = []unitNetwork{}
}
var present bool
for _, unitNet := range unitNets {
if unitNet.hasSameConnectivity(subnets) {
unitNet.unitNames.Add(unit.Name())
present = true
}
}
if !present {
collection[appName] = append(unitNets, unitNetwork{
unitNames: set.NewStrings(unit.Name()),
subnets: subnets,
})
}
}
return nil
}
// ensureConstraintIntegrity checks that moving subnets to the new space does
// not violate any application space constraints.
func (n *affectedNetworks) ensureConstraintIntegrity(cons map[string]set.Strings) error {
for appName, spaces := range cons {
if _, ok := n.changingNetworks[appName]; !ok {
// The constraint is for an application not affected by the move.
continue
}
if err := n.ensureNegativeConstraintIntegrity(appName, spaces); err != nil {
return errors.Trace(err)
}
if err := n.ensurePositiveConstraintIntegrity(appName, spaces); err != nil {
return errors.Trace(err)
}
}
return nil
}
// ensureNegativeConstraintIntegrity checks that the input application does not
// have a negative space constraint for the proposed destination space.
func (n *affectedNetworks) ensureNegativeConstraintIntegrity(appName string, spaceConstraints set.Strings) error {
if spaceConstraints.Contains("^" + n.newSpace) {
msg := fmt.Sprintf("moving subnet(s) to space %q violates space constraints "+
"for application %q: %s", n.newSpace, appName, strings.Join(spaceConstraints.SortedValues(), ", "))
if !n.force {
return errors.New(msg)
}
logger.Warningf(msg)
}
return nil
}
// ensurePositiveConstraintIntegrity checks that for each positive space
// constraint, comparing the input application's unit subnet connectivity to
// the target topology determines the constraint to be satisfied.
func (n *affectedNetworks) ensurePositiveConstraintIntegrity(appName string, spaceConstraints set.Strings) error {
unitNets := n.changingNetworks[appName]
for _, spaceName := range spaceConstraints.Values() {
if strings.HasPrefix(spaceName, "^") {
continue
}
conSpace := n.spaces.GetByName(spaceName)
if conSpace == nil {
return errors.NotFoundf("space with name %q", spaceName)
}
for _, unitNet := range unitNets {
if unitNet.remainsConnectedTo(*conSpace) {
continue
}
msg := fmt.Sprintf(
"moving subnet(s) to space %q violates space constraints for application %q: %s\n\t"+
"units not connected to the space: %s",
n.newSpace,
appName,
strings.Join(spaceConstraints.SortedValues(), ", "),
strings.Join(unitNet.unitNames.SortedValues(), ", "),
)
if !n.force {
return errors.New(msg)
}
logger.Warningf(msg)
}
}
return nil
}
// ensureBindingsIntegrity checks that moving subnets to the new space does
// not result in inconsistent application endpoint bindings.
// Consistency is considered maintained if:
// 1. Bound spaces remain unchanged by subnet relocation.
// 2. We successfully change affected bindings to a new space that
// preserves consistency across all units of an application.
func (n *affectedNetworks) ensureBindingsIntegrity(allBindings map[string]Bindings) error {
for appName, bindings := range allBindings {
if err := n.ensureApplicationBindingsIntegrity(appName, bindings); err != nil {
return errors.Trace(err)
}
}
return nil
}
func (n *affectedNetworks) ensureApplicationBindingsIntegrity(appName string, appBindings Bindings) error {
unitNets, ok := n.changingNetworks[appName]
if !ok {
return nil
}
for endpoint, boundSpaceID := range appBindings.Map() {
for _, unitNet := range unitNets {
boundSpace := n.spaces.GetByID(boundSpaceID)
if boundSpace == nil {
return errors.NotFoundf("space with ID %q", boundSpaceID)
}
// TODO (manadart 2020-05-05): There is some optimisation that
// could be done here to prevent re-checking endpoints bound to
// spaces that we have already checked, but at this time it is
// eschewed for clarity.
// The comparisons are all done using the in-memory topology,
// without going back to the DB, so it is not a huge issue.
if unitNet.remainsConnectedTo(*boundSpace) {
continue
}
// TODO (manadart 2020-05-05): At this point,
// use n.unchangedNetworks in combination with n.changingNetworks
// to see if we can maintain integrity by changing the binding to
// the target space. If we can, just make the change and log it.
msg := fmt.Sprintf(
"moving subnet(s) to space %q violates endpoint binding %s:%s for application %q\n\t"+
"units not connected to the space: %s",
n.newSpace,
endpoint,
boundSpace.Name,
appName,
strings.Join(unitNet.unitNames.SortedValues(), ", "),
)
if !n.force {
return errors.New(msg)
}
logger.Warningf(msg)
}
}
return nil
}