github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/nomad/deploymentwatcher/testutil_test.go (about) 1 package deploymentwatcher 2 3 import ( 4 "log" 5 "os" 6 "reflect" 7 "strings" 8 "sync" 9 "testing" 10 11 memdb "github.com/hashicorp/go-memdb" 12 "github.com/hashicorp/nomad/nomad/state" 13 "github.com/hashicorp/nomad/nomad/structs" 14 mocker "github.com/stretchr/testify/mock" 15 ) 16 17 func testLogger() *log.Logger { 18 return log.New(os.Stderr, "", log.LstdFlags|log.Lmicroseconds) 19 } 20 21 type mockBackend struct { 22 mocker.Mock 23 index uint64 24 state *state.StateStore 25 l sync.Mutex 26 } 27 28 func newMockBackend(t *testing.T) *mockBackend { 29 state, err := state.NewStateStore(os.Stderr) 30 if err != nil { 31 t.Fatalf("err: %v", err) 32 } 33 if state == nil { 34 t.Fatalf("missing state") 35 } 36 return &mockBackend{ 37 index: 10000, 38 state: state, 39 } 40 } 41 42 func (m *mockBackend) nextIndex() uint64 { 43 m.l.Lock() 44 defer m.l.Unlock() 45 i := m.index 46 m.index++ 47 return i 48 } 49 50 func (m *mockBackend) UpsertEvals(evals []*structs.Evaluation) (uint64, error) { 51 m.Called(evals) 52 i := m.nextIndex() 53 return i, m.state.UpsertEvals(i, evals) 54 } 55 56 // matchUpsertEvals is used to match an upsert request 57 func matchUpsertEvals(deploymentIDs []string) func(evals []*structs.Evaluation) bool { 58 return func(evals []*structs.Evaluation) bool { 59 if len(evals) != len(deploymentIDs) { 60 return false 61 } 62 63 dmap := make(map[string]struct{}, len(deploymentIDs)) 64 for _, d := range deploymentIDs { 65 dmap[d] = struct{}{} 66 } 67 68 for _, e := range evals { 69 if _, ok := dmap[e.DeploymentID]; !ok { 70 return false 71 } 72 73 delete(dmap, e.DeploymentID) 74 } 75 76 return true 77 } 78 } 79 80 func (m *mockBackend) UpsertJob(job *structs.Job) (uint64, error) { 81 m.Called(job) 82 i := m.nextIndex() 83 return i, m.state.UpsertJob(i, job) 84 } 85 86 func (m *mockBackend) UpdateDeploymentStatus(u *structs.DeploymentStatusUpdateRequest) (uint64, error) { 87 m.Called(u) 88 i := m.nextIndex() 89 return i, m.state.UpdateDeploymentStatus(i, u) 90 } 91 92 // matchDeploymentStatusUpdateConfig is used to configure the matching 93 // function 94 type matchDeploymentStatusUpdateConfig struct { 95 // DeploymentID is the expected ID 96 DeploymentID string 97 98 // Status is the desired status 99 Status string 100 101 // StatusDescription is the desired status description 102 StatusDescription string 103 104 // JobVersion marks whether we expect a roll back job at the given version 105 JobVersion *uint64 106 107 // Eval marks whether we expect an evaluation. 108 Eval bool 109 } 110 111 // matchDeploymentStatusUpdateRequest is used to match an update request 112 func matchDeploymentStatusUpdateRequest(c *matchDeploymentStatusUpdateConfig) func(args *structs.DeploymentStatusUpdateRequest) bool { 113 return func(args *structs.DeploymentStatusUpdateRequest) bool { 114 if args.DeploymentUpdate.DeploymentID != c.DeploymentID { 115 testLogger().Printf("deployment ids dont match") 116 return false 117 } 118 119 if args.DeploymentUpdate.Status != c.Status && args.DeploymentUpdate.StatusDescription != c.StatusDescription { 120 testLogger().Printf("status's dont match") 121 return false 122 } 123 124 if c.Eval && args.Eval == nil || !c.Eval && args.Eval != nil { 125 testLogger().Printf("evals dont match") 126 return false 127 } 128 129 if c.JobVersion != nil { 130 if args.Job == nil { 131 return false 132 } else if args.Job.Version != *c.JobVersion { 133 return false 134 } 135 } else if c.JobVersion == nil && args.Job != nil { 136 return false 137 } 138 139 return true 140 } 141 } 142 143 func (m *mockBackend) UpdateDeploymentPromotion(req *structs.ApplyDeploymentPromoteRequest) (uint64, error) { 144 m.Called(req) 145 i := m.nextIndex() 146 return i, m.state.UpdateDeploymentPromotion(i, req) 147 } 148 149 // matchDeploymentPromoteRequestConfig is used to configure the matching 150 // function 151 type matchDeploymentPromoteRequestConfig struct { 152 // Promotion holds the expected promote request 153 Promotion *structs.DeploymentPromoteRequest 154 155 // Eval marks whether we expect an evaluation. 156 Eval bool 157 } 158 159 // matchDeploymentPromoteRequest is used to match a promote request 160 func matchDeploymentPromoteRequest(c *matchDeploymentPromoteRequestConfig) func(args *structs.ApplyDeploymentPromoteRequest) bool { 161 return func(args *structs.ApplyDeploymentPromoteRequest) bool { 162 if !reflect.DeepEqual(*c.Promotion, args.DeploymentPromoteRequest) { 163 return false 164 } 165 166 if c.Eval && args.Eval == nil || !c.Eval && args.Eval != nil { 167 return false 168 } 169 170 return true 171 } 172 } 173 func (m *mockBackend) UpdateDeploymentAllocHealth(req *structs.ApplyDeploymentAllocHealthRequest) (uint64, error) { 174 m.Called(req) 175 i := m.nextIndex() 176 return i, m.state.UpdateDeploymentAllocHealth(i, req) 177 } 178 179 // matchDeploymentAllocHealthRequestConfig is used to configure the matching 180 // function 181 type matchDeploymentAllocHealthRequestConfig struct { 182 // DeploymentID is the expected ID 183 DeploymentID string 184 185 // Healthy and Unhealthy contain the expected allocation IDs that are having 186 // their health set 187 Healthy, Unhealthy []string 188 189 // DeploymentUpdate holds the expected values of status and description. We 190 // don't check for exact match but string contains 191 DeploymentUpdate *structs.DeploymentStatusUpdate 192 193 // JobVersion marks whether we expect a roll back job at the given version 194 JobVersion *uint64 195 196 // Eval marks whether we expect an evaluation. 197 Eval bool 198 } 199 200 // matchDeploymentAllocHealthRequest is used to match an update request 201 func matchDeploymentAllocHealthRequest(c *matchDeploymentAllocHealthRequestConfig) func(args *structs.ApplyDeploymentAllocHealthRequest) bool { 202 return func(args *structs.ApplyDeploymentAllocHealthRequest) bool { 203 if args.DeploymentID != c.DeploymentID { 204 return false 205 } 206 207 if len(c.Healthy) != len(args.HealthyAllocationIDs) { 208 return false 209 } 210 if len(c.Unhealthy) != len(args.UnhealthyAllocationIDs) { 211 return false 212 } 213 214 hmap, umap := make(map[string]struct{}, len(c.Healthy)), make(map[string]struct{}, len(c.Unhealthy)) 215 for _, h := range c.Healthy { 216 hmap[h] = struct{}{} 217 } 218 for _, u := range c.Unhealthy { 219 umap[u] = struct{}{} 220 } 221 222 for _, h := range args.HealthyAllocationIDs { 223 if _, ok := hmap[h]; !ok { 224 return false 225 } 226 } 227 for _, u := range args.UnhealthyAllocationIDs { 228 if _, ok := umap[u]; !ok { 229 return false 230 } 231 } 232 233 if c.DeploymentUpdate != nil { 234 if args.DeploymentUpdate == nil { 235 return false 236 } 237 238 if !strings.Contains(args.DeploymentUpdate.Status, c.DeploymentUpdate.Status) { 239 return false 240 } 241 if !strings.Contains(args.DeploymentUpdate.StatusDescription, c.DeploymentUpdate.StatusDescription) { 242 return false 243 } 244 } else if args.DeploymentUpdate != nil { 245 return false 246 } 247 248 if c.Eval && args.Eval == nil || !c.Eval && args.Eval != nil { 249 return false 250 } 251 252 if (c.JobVersion != nil && (args.Job == nil || args.Job.Version != *c.JobVersion)) || c.JobVersion == nil && args.Job != nil { 253 return false 254 } 255 256 return true 257 } 258 } 259 260 func (m *mockBackend) Evaluations(args *structs.JobSpecificRequest, reply *structs.JobEvaluationsResponse) error { 261 rargs := m.Called(args, reply) 262 return rargs.Error(0) 263 } 264 265 func (m *mockBackend) evaluationsFromState(in mocker.Arguments) { 266 args, reply := in.Get(0).(*structs.JobSpecificRequest), in.Get(1).(*structs.JobEvaluationsResponse) 267 ws := memdb.NewWatchSet() 268 evals, _ := m.state.EvalsByJob(ws, args.JobID) 269 reply.Evaluations = evals 270 reply.Index, _ = m.state.Index("evals") 271 } 272 273 func (m *mockBackend) Allocations(args *structs.DeploymentSpecificRequest, reply *structs.AllocListResponse) error { 274 rargs := m.Called(args, reply) 275 return rargs.Error(0) 276 } 277 278 func (m *mockBackend) allocationsFromState(in mocker.Arguments) { 279 args, reply := in.Get(0).(*structs.DeploymentSpecificRequest), in.Get(1).(*structs.AllocListResponse) 280 ws := memdb.NewWatchSet() 281 allocs, _ := m.state.AllocsByDeployment(ws, args.DeploymentID) 282 283 var stubs []*structs.AllocListStub 284 for _, a := range allocs { 285 stubs = append(stubs, a.Stub()) 286 } 287 288 reply.Allocations = stubs 289 reply.Index, _ = m.state.Index("allocs") 290 } 291 292 func (m *mockBackend) List(args *structs.DeploymentListRequest, reply *structs.DeploymentListResponse) error { 293 rargs := m.Called(args, reply) 294 return rargs.Error(0) 295 } 296 297 func (m *mockBackend) listFromState(in mocker.Arguments) { 298 reply := in.Get(1).(*structs.DeploymentListResponse) 299 ws := memdb.NewWatchSet() 300 iter, _ := m.state.Deployments(ws) 301 302 var deploys []*structs.Deployment 303 for { 304 raw := iter.Next() 305 if raw == nil { 306 break 307 } 308 309 deploys = append(deploys, raw.(*structs.Deployment)) 310 } 311 312 reply.Deployments = deploys 313 reply.Index, _ = m.state.Index("deployment") 314 } 315 316 func (m *mockBackend) GetDeployment(args *structs.DeploymentSpecificRequest, reply *structs.SingleDeploymentResponse) error { 317 rargs := m.Called(args, reply) 318 return rargs.Error(0) 319 } 320 321 func (m *mockBackend) GetJobVersions(args *structs.JobVersionsRequest, reply *structs.JobVersionsResponse) error { 322 rargs := m.Called(args, reply) 323 return rargs.Error(0) 324 } 325 326 func (m *mockBackend) getJobVersionsFromState(in mocker.Arguments) { 327 args, reply := in.Get(0).(*structs.JobVersionsRequest), in.Get(1).(*structs.JobVersionsResponse) 328 ws := memdb.NewWatchSet() 329 versions, _ := m.state.JobVersionsByID(ws, args.JobID) 330 reply.Versions = versions 331 reply.Index, _ = m.state.Index("jobs") 332 } 333 334 func (m *mockBackend) GetJob(args *structs.JobSpecificRequest, reply *structs.SingleJobResponse) error { 335 rargs := m.Called(args, reply) 336 return rargs.Error(0) 337 } 338 339 func (m *mockBackend) getJobFromState(in mocker.Arguments) { 340 args, reply := in.Get(0).(*structs.JobSpecificRequest), in.Get(1).(*structs.SingleJobResponse) 341 ws := memdb.NewWatchSet() 342 job, _ := m.state.JobByID(ws, args.JobID) 343 reply.Job = job 344 reply.Index, _ = m.state.Index("jobs") 345 } 346 347 // matchDeploymentSpecificRequest is used to match that a deployment specific 348 // request is for the passed deployment id 349 func matchDeploymentSpecificRequest(dID string) func(args *structs.DeploymentSpecificRequest) bool { 350 return func(args *structs.DeploymentSpecificRequest) bool { 351 return args.DeploymentID == dID 352 } 353 } 354 355 // matchJobSpecificRequest is used to match that a job specific 356 // request is for the passed job id 357 func matchJobSpecificRequest(jID string) func(args *structs.JobSpecificRequest) bool { 358 return func(args *structs.JobSpecificRequest) bool { 359 return args.JobID == jID 360 } 361 } 362 363 // matchJobVersionsRequest is used to match that a job version 364 // request is for the passed job id 365 func matchJobVersionsRequest(jID string) func(args *structs.JobVersionsRequest) bool { 366 return func(args *structs.JobVersionsRequest) bool { 367 return args.JobID == jID 368 } 369 }