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