carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package hostactors
2
3import (
4 "context"
5 "errors"
6 "sort"
7 "strings"
8
9 "github.com/carlosframework/platform/internal/release"
10)
11
12// Box and group roles (region HA spec 2 §7, A11-1). A box's role says
13// whether placement may choose it: the default role may be chosen, and
14// legacy and platform never are, for a create, a relocation target or a
15// move target. The box keeps the instances it has (H25).
16//
17// The region's group entry carries a role too. Every box automatic
18// enrollment admits from the group takes the group's role in the same
19// roster write, and changing the group's role changes the role of every
20// box enrolled from it, so a replacement launched after the group was
21// excluded is never placeable, even for a moment.
22//
23// The roster stores the default role as "" so a roster written before
24// roles existed reads as all-default.
25const (
26 RoleDefault = ""
27 RoleLegacy = "legacy"
28 RolePlatform = "platform"
29)
30
31// ErrRole is a role name a box or group cannot take.
32var ErrRole = errors.New("hostactors: not a role this box or group can take")
33
34// ParseBoxRole reads an operator's box role: default, legacy or platform.
35func ParseBoxRole(s string) (string, error) {
36 switch strings.TrimSpace(s) {
37 case "default":
38 return RoleDefault, nil
39 case RoleLegacy:
40 return RoleLegacy, nil
41 case RolePlatform:
42 return RolePlatform, nil
43 }
44 return "", ErrRole
45}
46
47// ParseGroupRole reads an operator's group role: default or legacy. A
48// group is never the platform box.
49func ParseGroupRole(s string) (string, error) {
50 switch strings.TrimSpace(s) {
51 case "default":
52 return RoleDefault, nil
53 case RoleLegacy:
54 return RoleLegacy, nil
55 }
56 return "", ErrRole
57}
58
59// RoleName is the operator's word for a stored role.
60func RoleName(role string) string {
61 if role == RoleDefault {
62 return "default"
63 }
64 return role
65}
66
67func validBoxRole(role string) bool {
68 return role == RoleDefault || role == RoleLegacy || role == RolePlatform
69}
70
71func validGroupRole(role string) bool { return role == RoleDefault || role == RoleLegacy }
72
73// SetBoxRole sets one box's role in a fresh roster write over o. The box
74// must be enrolled and not retired. Setting the role it already has
75// changes nothing and returns o.
76func (d Directory) SetBoxRole(ctx context.Context, sg release.Signer, o Observed, ref, role string) (Observed, error) {
77 if !validBoxRole(role) {
78 return Observed{}, ErrRole
79 }
80 b, ok := o.Roster.Boxes[ref]
81 if !ok || b.State == "retired" {
82 return Observed{}, ErrRoster
83 }
84 if b.Role == role {
85 return o, nil
86 }
87 r := Clone(o.Roster)
88 b = r.Boxes[ref]
89 b.Role = role
90 r.Boxes[ref] = b
91 return d.Replace(ctx, sg, o, r)
92}
93
94// GroupMembers returns, sorted, the boxes enrolled from region's group
95// that are not retired: the boxes a group role change applies to.
96func GroupMembers(r Roster, region string) []string {
97 g, ok := r.Regions[region]
98 if !ok || g.GroupName == "" {
99 return nil
100 }
101 var out []string
102 for ref, b := range r.Boxes {
103 if b.Region == region && b.Group == g.GroupName && b.State != "retired" {
104 out = append(out, ref)
105 }
106 }
107 sort.Strings(out)
108 return out
109}
110
111// SetGroupRole sets region's group role and, in the same roster write, the
112// role of every box enrolled from that group (GroupMembers). The region
113// must have a group. It returns the roster and the boxes it set.
114func (d Directory) SetGroupRole(ctx context.Context, sg release.Signer, o Observed, region, role string) (Observed, []string, error) {
115 if !validGroupRole(role) {
116 return Observed{}, nil, ErrRole
117 }
118 g, ok := o.Roster.Regions[region]
119 if !ok || g.GroupName == "" {
120 return Observed{}, nil, ErrRoster
121 }
122 members := GroupMembers(o.Roster, region)
123 changed := g.Role != role
124 for _, ref := range members {
125 changed = changed || o.Roster.Boxes[ref].Role != role
126 }
127 if !changed {
128 return o, members, nil
129 }
130 r := Clone(o.Roster)
131 g.Role = role
132 r.Regions[region] = g
133 for _, ref := range members {
134 b := r.Boxes[ref]
135 b.Role = role
136 r.Boxes[ref] = b
137 }
138 next, err := d.Replace(ctx, sg, o, r)
139 if err != nil {
140 return Observed{}, nil, err
141 }
142 return next, members, nil
143}
144