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