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 | // Copyright 2012-2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package relation
// NextHook returns the next hook that should be executed in the relation
// characterised by the supplied local and remote state; or an error if the
// states do not refer to the same relation; or ErrRelationUpToDate if no
// hooks need to be executed.
func NextHook(local *State, remote *RemoteState) (hook.Info, error) {
// Check sanity.
if local.RelationId != remote.RelationId {
template := "mismatched relation state ids (local: %d, remote: %d)"
return hook.Info{}, errors.Errorf(template, local.RelationId, remote.RelationId)
}
relationId := local.RelationId
// If there's a guaranteed next hook, return that.
if local.ChangedPending != "" {
unitName := local.ChangedPending
return hook.Info{
Kind: hooks.RelationChanged,
RelationId: relationId,
RemoteUnit: unitName,
ChangeVersion: remote.Members[unitName],
}, nil
}
// Get the union of all relevant units, and sort them, so we produce events
// in a consistent order (largely for the convenience of the tests).
allUnitNames := set.NewStrings()
for unitName := range local.Members {
allUnitNames.Add(unitName)
}
for unitName := range remote.Members {
allUnitNames.Add(unitName)
}
sortedUnitNames := allUnitNames.SortedValues()
// If there are any locally known units that are no longer reflected in
// remote state, depart them.
for _, unitName := range sortedUnitNames {
changeVersion, found := local.Members[unitName]
if !found {
continue
}
if _, found := remote.Members[unitName]; !found {
return hook.Info{
Kind: hooks.RelationDeparted,
RelationId: relationId,
RemoteUnit: unitName,
ChangeVersion: changeVersion,
}, nil
}
}
// If the relation's meant to be broken, break it.
if remote.Broken {
return hook.Info{
Kind: hooks.RelationBroken,
RelationId: relationId,
}
}
// If there are any remote units not locally known, join them.
for _, unitName := range sortedUnitNames {
changeVersion, found := remote.Members[unitName]
if !found {
continue
}
if _, found := local.Members[unitName]; !found {
return hook.Info{
Kind: hooks.RelationJoined,
RelationId: relationId,
RemoteUnit: unitName,
ChangeVersion: changeVersion,
}
}
}
// Finally scan for remote units whose latest version is not reflected
// in local state.
for _, unitName := range sortedUnitNames {
remoteChangeVersion, found := remote.Members[unitName]
if !found {
continue
}
localChangeVersion, found := local.Members[unitName]
if !found {
continue
}
if remoteChangeVersion > localChangeVersion {
return hook.Info{
Kind: hooks.RelationChanged,
RelationId: relationId,
RemoteUnit: unitName,
ChangeVersion: remoteChangeVersion,
}
}
}
// Nothing left to do for this relation.
return hook.Info{}, ErrRelationUpToDate
}
|