github.com/hernad/nomad@v1.6.112/nomad/deploymentwatcher/testutil_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package deploymentwatcher 5 6 import ( 7 "reflect" 8 "strings" 9 "sync" 10 "testing" 11 12 "github.com/hernad/nomad/nomad/state" 13 "github.com/hernad/nomad/nomad/structs" 14 mocker "github.com/stretchr/testify/mock" 15 ) 16 17 type mockBackend struct { 18 mocker.Mock 19 index uint64 20 state *state.StateStore 21 l sync.Mutex 22 } 23 24 func newMockBackend(t *testing.T) *mockBackend { 25 m := &mockBackend{ 26 index: 10000, 27 state: state.TestStateStore(t), 28 } 29 m.Test(t) 30 return m 31 } 32 33 func (m *mockBackend) nextIndex() uint64 { 34 m.l.Lock() 35 defer m.l.Unlock() 36 i := m.index 37 m.index++ 38 return i 39 } 40 41 func (m *mockBackend) UpdateAllocDesiredTransition(u *structs.AllocUpdateDesiredTransitionRequest) (uint64, error) { 42 m.Called(u) 43 i := m.nextIndex() 44 return i, m.state.UpdateAllocsDesiredTransitions(structs.MsgTypeTestSetup, i, u.Allocs, u.Evals) 45 } 46 47 // matchUpdateAllocDesiredTransitions is used to match an upsert request 48 func matchUpdateAllocDesiredTransitions(deploymentIDs []string) func(update *structs.AllocUpdateDesiredTransitionRequest) bool { 49 return func(update *structs.AllocUpdateDesiredTransitionRequest) bool { 50 if len(update.Evals) != len(deploymentIDs) { 51 return false 52 } 53 54 dmap := make(map[string]struct{}, len(deploymentIDs)) 55 for _, d := range deploymentIDs { 56 dmap[d] = struct{}{} 57 } 58 59 for _, e := range update.Evals { 60 if _, ok := dmap[e.DeploymentID]; !ok { 61 return false 62 } 63 64 delete(dmap, e.DeploymentID) 65 } 66 67 return true 68 } 69 } 70 71 // matchUpdateAllocDesiredTransitionReschedule is used to match allocs that have their DesiredTransition set to Reschedule 72 func matchUpdateAllocDesiredTransitionReschedule(allocIDs []string) func(update *structs.AllocUpdateDesiredTransitionRequest) bool { 73 return func(update *structs.AllocUpdateDesiredTransitionRequest) bool { 74 amap := make(map[string]struct{}, len(allocIDs)) 75 for _, d := range allocIDs { 76 amap[d] = struct{}{} 77 } 78 79 for allocID, dt := range update.Allocs { 80 if _, ok := amap[allocID]; !ok { 81 return false 82 } 83 if !*dt.Reschedule { 84 return false 85 } 86 } 87 88 return true 89 } 90 } 91 92 func (m *mockBackend) UpsertJob(job *structs.Job) (uint64, error) { 93 m.Called(job) 94 i := m.nextIndex() 95 return i, m.state.UpsertJob(structs.MsgTypeTestSetup, i, nil, job) 96 } 97 98 func (m *mockBackend) UpdateDeploymentStatus(u *structs.DeploymentStatusUpdateRequest) (uint64, error) { 99 m.Called(u) 100 i := m.nextIndex() 101 return i, m.state.UpdateDeploymentStatus(structs.MsgTypeTestSetup, i, u) 102 } 103 104 // matchDeploymentStatusUpdateConfig is used to configure the matching 105 // function 106 type matchDeploymentStatusUpdateConfig struct { 107 // DeploymentID is the expected ID 108 DeploymentID string 109 110 // Status is the desired status 111 Status string 112 113 // StatusDescription is the desired status description 114 StatusDescription string 115 116 // JobVersion marks whether we expect a roll back job at the given version 117 JobVersion *uint64 118 119 // Eval marks whether we expect an evaluation. 120 Eval bool 121 } 122 123 // matchDeploymentStatusUpdateRequest is used to match an update request 124 func matchDeploymentStatusUpdateRequest(c *matchDeploymentStatusUpdateConfig) func(args *structs.DeploymentStatusUpdateRequest) bool { 125 return func(args *structs.DeploymentStatusUpdateRequest) bool { 126 if args.DeploymentUpdate.DeploymentID != c.DeploymentID { 127 return false 128 } 129 130 if args.DeploymentUpdate.Status != c.Status && args.DeploymentUpdate.StatusDescription != c.StatusDescription { 131 return false 132 } 133 134 if c.Eval && args.Eval == nil || !c.Eval && args.Eval != nil { 135 return false 136 } 137 138 if c.JobVersion != nil { 139 if args.Job == nil { 140 return false 141 } else if args.Job.Version != *c.JobVersion { 142 return false 143 } 144 } else if c.JobVersion == nil && args.Job != nil { 145 return false 146 } 147 148 return true 149 } 150 } 151 152 func (m *mockBackend) UpdateDeploymentPromotion(req *structs.ApplyDeploymentPromoteRequest) (uint64, error) { 153 m.Called(req) 154 i := m.nextIndex() 155 return i, m.state.UpdateDeploymentPromotion(structs.MsgTypeTestSetup, i, req) 156 } 157 158 // matchDeploymentPromoteRequestConfig is used to configure the matching 159 // function 160 type matchDeploymentPromoteRequestConfig struct { 161 // Promotion holds the expected promote request 162 Promotion *structs.DeploymentPromoteRequest 163 164 // Eval marks whether we expect an evaluation. 165 Eval bool 166 } 167 168 // matchDeploymentPromoteRequest is used to match a promote request 169 func matchDeploymentPromoteRequest(c *matchDeploymentPromoteRequestConfig) func(args *structs.ApplyDeploymentPromoteRequest) bool { 170 return func(args *structs.ApplyDeploymentPromoteRequest) bool { 171 if !reflect.DeepEqual(*c.Promotion, args.DeploymentPromoteRequest) { 172 return false 173 } 174 175 if c.Eval && args.Eval == nil || !c.Eval && args.Eval != nil { 176 return false 177 } 178 179 return true 180 } 181 } 182 func (m *mockBackend) UpdateDeploymentAllocHealth(req *structs.ApplyDeploymentAllocHealthRequest) (uint64, error) { 183 m.Called(req) 184 i := m.nextIndex() 185 return i, m.state.UpdateDeploymentAllocHealth(structs.MsgTypeTestSetup, i, req) 186 } 187 188 // matchDeploymentAllocHealthRequestConfig is used to configure the matching 189 // function 190 type matchDeploymentAllocHealthRequestConfig struct { 191 // DeploymentID is the expected ID 192 DeploymentID string 193 194 // Healthy and Unhealthy contain the expected allocation IDs that are having 195 // their health set 196 Healthy, Unhealthy []string 197 198 // DeploymentUpdate holds the expected values of status and description. We 199 // don't check for exact match but string contains 200 DeploymentUpdate *structs.DeploymentStatusUpdate 201 202 // JobVersion marks whether we expect a roll back job at the given version 203 JobVersion *uint64 204 205 // Eval marks whether we expect an evaluation. 206 Eval bool 207 } 208 209 // matchDeploymentAllocHealthRequest is used to match an update request 210 func matchDeploymentAllocHealthRequest(c *matchDeploymentAllocHealthRequestConfig) func(args *structs.ApplyDeploymentAllocHealthRequest) bool { 211 return func(args *structs.ApplyDeploymentAllocHealthRequest) bool { 212 if args.DeploymentID != c.DeploymentID { 213 return false 214 } 215 216 // Require a timestamp 217 if args.Timestamp.IsZero() { 218 return false 219 } 220 221 if len(c.Healthy) != len(args.HealthyAllocationIDs) { 222 return false 223 } 224 if len(c.Unhealthy) != len(args.UnhealthyAllocationIDs) { 225 return false 226 } 227 228 hmap, umap := make(map[string]struct{}, len(c.Healthy)), make(map[string]struct{}, len(c.Unhealthy)) 229 for _, h := range c.Healthy { 230 hmap[h] = struct{}{} 231 } 232 for _, u := range c.Unhealthy { 233 umap[u] = struct{}{} 234 } 235 236 for _, h := range args.HealthyAllocationIDs { 237 if _, ok := hmap[h]; !ok { 238 return false 239 } 240 } 241 for _, u := range args.UnhealthyAllocationIDs { 242 if _, ok := umap[u]; !ok { 243 return false 244 } 245 } 246 247 if c.DeploymentUpdate != nil { 248 if args.DeploymentUpdate == nil { 249 return false 250 } 251 252 if !strings.Contains(args.DeploymentUpdate.Status, c.DeploymentUpdate.Status) { 253 return false 254 } 255 if !strings.Contains(args.DeploymentUpdate.StatusDescription, c.DeploymentUpdate.StatusDescription) { 256 return false 257 } 258 } else if args.DeploymentUpdate != nil { 259 return false 260 } 261 262 if c.Eval && args.Eval == nil || !c.Eval && args.Eval != nil { 263 return false 264 } 265 266 if (c.JobVersion != nil && (args.Job == nil || args.Job.Version != *c.JobVersion)) || c.JobVersion == nil && args.Job != nil { 267 return false 268 } 269 270 return true 271 } 272 }