github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/command/agent/consul/service_client_test.go (about) 1 package consul 2 3 import ( 4 "reflect" 5 "testing" 6 "time" 7 8 "github.com/hashicorp/consul/api" 9 "github.com/hashicorp/nomad/helper/testlog" 10 "github.com/hashicorp/nomad/helper/uuid" 11 "github.com/hashicorp/nomad/nomad/structs" 12 "github.com/stretchr/testify/require" 13 ) 14 15 var ( 16 // the service as known by nomad 17 wanted = api.AgentServiceRegistration{ 18 Kind: "", 19 ID: "aca4c175-1778-5ef4-0220-2ab434147d35", 20 Name: "myservice", 21 Tags: []string{"a", "b"}, 22 Port: 9000, 23 Address: "1.1.1.1", 24 EnableTagOverride: true, 25 Meta: map[string]string{"foo": "1"}, 26 Connect: &api.AgentServiceConnect{ 27 Native: false, 28 SidecarService: &api.AgentServiceRegistration{ 29 Kind: "connect-proxy", 30 ID: "_nomad-task-8e8413af-b5bb-aa67-2c24-c146c45f1ec9-group-mygroup-myservice-9001-sidecar-proxy", 31 Name: "name-sidecar-proxy", 32 Tags: []string{"x", "y", "z"}, 33 }, 34 }, 35 } 36 37 // the service (and + connect proxy) as known by consul 38 existing = &api.AgentService{ 39 Kind: "", 40 ID: "aca4c175-1778-5ef4-0220-2ab434147d35", 41 Service: "myservice", 42 Tags: []string{"a", "b"}, 43 Port: 9000, 44 Address: "1.1.1.1", 45 EnableTagOverride: true, 46 Meta: map[string]string{"foo": "1"}, 47 } 48 49 sidecar = &api.AgentService{ 50 Kind: "connect-proxy", 51 ID: "_nomad-task-8e8413af-b5bb-aa67-2c24-c146c45f1ec9-group-mygroup-myservice-9001-sidecar-proxy", 52 Service: "myservice-sidecar-proxy", 53 Tags: []string{"x", "y", "z"}, 54 } 55 ) 56 57 func TestSyncLogic_agentServiceUpdateRequired(t *testing.T) { 58 t.Parallel() 59 60 // By default wanted and existing match. Each test should modify wanted in 61 // 1 way, and / or configure the type of sync operation that is being 62 // considered, then evaluate the result of the update-required algebra. 63 64 type asr = api.AgentServiceRegistration 65 type tweaker func(w asr) *asr // create a conveniently modifiable copy 66 67 try := func( 68 t *testing.T, 69 exp bool, 70 reason syncReason, 71 tweak tweaker) { 72 result := agentServiceUpdateRequired(reason, tweak(wanted), existing, sidecar) 73 require.Equal(t, exp, result) 74 } 75 76 t.Run("matching", func(t *testing.T) { 77 try(t, false, syncNewOps, func(w asr) *asr { 78 return &w 79 }) 80 }) 81 82 t.Run("different kind", func(t *testing.T) { 83 try(t, true, syncNewOps, func(w asr) *asr { 84 w.Kind = "other" 85 return &w 86 }) 87 }) 88 89 t.Run("different id", func(t *testing.T) { 90 try(t, true, syncNewOps, func(w asr) *asr { 91 w.ID = "_other" 92 return &w 93 }) 94 }) 95 96 t.Run("different port", func(t *testing.T) { 97 try(t, true, syncNewOps, func(w asr) *asr { 98 w.Port = 9001 99 return &w 100 }) 101 }) 102 103 t.Run("different address", func(t *testing.T) { 104 try(t, true, syncNewOps, func(w asr) *asr { 105 w.Address = "2.2.2.2" 106 return &w 107 }) 108 }) 109 110 t.Run("different name", func(t *testing.T) { 111 try(t, true, syncNewOps, func(w asr) *asr { 112 w.Name = "bob" 113 return &w 114 }) 115 }) 116 117 t.Run("different enable_tag_override", func(t *testing.T) { 118 try(t, true, syncNewOps, func(w asr) *asr { 119 w.EnableTagOverride = false 120 return &w 121 }) 122 }) 123 124 t.Run("different meta", func(t *testing.T) { 125 try(t, true, syncNewOps, func(w asr) *asr { 126 w.Meta = map[string]string{"foo": "2"} 127 return &w 128 }) 129 }) 130 131 t.Run("different tags syncNewOps eto=true", func(t *testing.T) { 132 // sync is required even though eto=true, because NewOps indicates the 133 // service definition in nomad has changed (e.g. job run a modified job) 134 try(t, true, syncNewOps, func(w asr) *asr { 135 w.Tags = []string{"other", "tags"} 136 return &w 137 }) 138 }) 139 140 t.Run("different tags syncPeriodic eto=true", func(t *testing.T) { 141 // sync is not required since eto=true and this is a periodic sync 142 // with consul - in which case we keep Consul's definition of the tags 143 try(t, false, syncPeriodic, func(w asr) *asr { 144 w.Tags = []string{"other", "tags"} 145 return &w 146 }) 147 }) 148 149 t.Run("different sidecar tags on syncPeriodic eto=true", func(t *testing.T) { 150 try(t, false, syncPeriodic, func(w asr) *asr { 151 // like the parent service, the sidecar's tags do not get enforced 152 // if ETO is true and this is a periodic sync 153 w.Connect.SidecarService.Tags = []string{"other", "tags"} 154 return &w 155 }) 156 }) 157 158 t.Run("different sidecar tags on syncNewOps eto=true", func(t *testing.T) { 159 try(t, true, syncNewOps, func(w asr) *asr { 160 // like the parent service, the sidecar's tags always get enforced 161 // regardless of ETO if this is a sync due to applied operations 162 w.Connect.SidecarService.Tags = []string{"other", "tags"} 163 return &w 164 }) 165 }) 166 167 // for remaining tests, EnableTagOverride = false 168 wanted.EnableTagOverride = false 169 existing.EnableTagOverride = false 170 171 t.Run("different tags syncPeriodic eto=false", func(t *testing.T) { 172 // sync is required because eto=false and the tags do not match 173 try(t, true, syncPeriodic, func(w asr) *asr { 174 w.Tags = []string{"other", "tags"} 175 return &w 176 }) 177 }) 178 179 t.Run("different tags syncNewOps eto=false", func(t *testing.T) { 180 // sync is required because eto=false and the tags do not match 181 try(t, true, syncNewOps, func(w asr) *asr { 182 w.Tags = []string{"other", "tags"} 183 return &w 184 }) 185 }) 186 187 t.Run("different sidecar tags on syncPeriodic eto=false", func(t *testing.T) { 188 // like the parent service, sync is required because eto=false and the 189 // sidecar's tags do not match 190 try(t, true, syncPeriodic, func(w asr) *asr { 191 w.Connect.SidecarService.Tags = []string{"other", "tags"} 192 return &w 193 }) 194 }) 195 196 t.Run("different sidecar tags syncNewOps eto=false", func(t *testing.T) { 197 // like the parent service, sync is required because eto=false and the 198 // sidecar's tags do not match 199 try(t, true, syncNewOps, func(w asr) *asr { 200 w.Connect.SidecarService.Tags = []string{"other", "tags"} 201 return &w 202 }) 203 }) 204 } 205 206 func TestSyncLogic_tagsDifferent(t *testing.T) { 207 t.Run("nil nil", func(t *testing.T) { 208 require.False(t, tagsDifferent(nil, nil)) 209 }) 210 211 t.Run("empty nil", func(t *testing.T) { 212 // where reflect.DeepEqual does not work 213 require.False(t, tagsDifferent([]string{}, nil)) 214 }) 215 216 t.Run("empty empty", func(t *testing.T) { 217 require.False(t, tagsDifferent([]string{}, []string{})) 218 }) 219 220 t.Run("set empty", func(t *testing.T) { 221 require.True(t, tagsDifferent([]string{"A"}, []string{})) 222 }) 223 224 t.Run("set nil", func(t *testing.T) { 225 require.True(t, tagsDifferent([]string{"A"}, nil)) 226 }) 227 228 t.Run("different content", func(t *testing.T) { 229 require.True(t, tagsDifferent([]string{"A"}, []string{"B"})) 230 }) 231 232 t.Run("different lengths", func(t *testing.T) { 233 require.True(t, tagsDifferent([]string{"A"}, []string{"A", "B"})) 234 }) 235 } 236 237 func TestSyncLogic_sidecarTagsDifferent(t *testing.T) { 238 type tc struct { 239 parent, wanted, sidecar []string 240 expect bool 241 } 242 243 try := func(t *testing.T, test tc) { 244 result := sidecarTagsDifferent(test.parent, test.wanted, test.sidecar) 245 require.Equal(t, test.expect, result) 246 } 247 248 try(t, tc{parent: nil, wanted: nil, sidecar: nil, expect: false}) 249 250 // wanted is nil, compare sidecar to parent 251 try(t, tc{parent: []string{"foo"}, wanted: nil, sidecar: nil, expect: true}) 252 try(t, tc{parent: []string{"foo"}, wanted: nil, sidecar: []string{"foo"}, expect: false}) 253 try(t, tc{parent: []string{"foo"}, wanted: nil, sidecar: []string{"bar"}, expect: true}) 254 try(t, tc{parent: nil, wanted: nil, sidecar: []string{"foo"}, expect: true}) 255 256 // wanted is non-nil, compare sidecar to wanted 257 try(t, tc{parent: nil, wanted: []string{"foo"}, sidecar: nil, expect: true}) 258 try(t, tc{parent: nil, wanted: []string{"foo"}, sidecar: []string{"foo"}, expect: false}) 259 try(t, tc{parent: nil, wanted: []string{"foo"}, sidecar: []string{"bar"}, expect: true}) 260 try(t, tc{parent: []string{"foo"}, wanted: []string{"foo"}, sidecar: []string{"bar"}, expect: true}) 261 } 262 263 func TestSyncLogic_maybeTweakTags(t *testing.T) { 264 t.Parallel() 265 266 differentPointers := func(a, b []string) bool { 267 return &(a) != &(b) 268 } 269 270 try := func(inConsul, inConsulSC []string, eto bool) { 271 wanted := &api.AgentServiceRegistration{ 272 Tags: []string{"original"}, 273 Connect: &api.AgentServiceConnect{ 274 SidecarService: &api.AgentServiceRegistration{ 275 Tags: []string{"original-sidecar"}, 276 }, 277 }, 278 EnableTagOverride: eto, 279 } 280 281 existing := &api.AgentService{Tags: inConsul} 282 sidecar := &api.AgentService{Tags: inConsulSC} 283 284 maybeTweakTags(wanted, existing, sidecar) 285 286 switch eto { 287 case false: 288 require.Equal(t, []string{"original"}, wanted.Tags) 289 require.Equal(t, []string{"original-sidecar"}, wanted.Connect.SidecarService.Tags) 290 require.True(t, differentPointers(wanted.Tags, wanted.Connect.SidecarService.Tags)) 291 case true: 292 require.Equal(t, inConsul, wanted.Tags) 293 require.Equal(t, inConsulSC, wanted.Connect.SidecarService.Tags) 294 require.True(t, differentPointers(wanted.Tags, wanted.Connect.SidecarService.Tags)) 295 } 296 } 297 298 try([]string{"original"}, []string{"original-sidecar"}, true) 299 try([]string{"original"}, []string{"original-sidecar"}, false) 300 try([]string{"modified"}, []string{"original-sidecar"}, true) 301 try([]string{"modified"}, []string{"original-sidecar"}, false) 302 try([]string{"original"}, []string{"modified-sidecar"}, true) 303 try([]string{"original"}, []string{"modified-sidecar"}, false) 304 try([]string{"modified"}, []string{"modified-sidecar"}, true) 305 try([]string{"modified"}, []string{"modified-sidecar"}, false) 306 } 307 308 func TestSyncLogic_maybeTweakTags_emptySC(t *testing.T) { 309 t.Parallel() 310 311 // Check the edge cases where the connect service is deleted on the nomad 312 // side (i.e. are we checking multiple nil pointers). 313 314 try := func(asr *api.AgentServiceRegistration) { 315 maybeTweakTags(asr, existing, sidecar) 316 require.False(t, reflect.DeepEqual([]string{"original"}, asr.Tags)) 317 } 318 319 try(&api.AgentServiceRegistration{ 320 Tags: []string{"original"}, 321 EnableTagOverride: true, 322 Connect: nil, // ooh danger! 323 }) 324 325 try(&api.AgentServiceRegistration{ 326 Tags: []string{"original"}, 327 EnableTagOverride: true, 328 Connect: &api.AgentServiceConnect{ 329 SidecarService: nil, // ooh danger! 330 }, 331 }) 332 } 333 334 // TestServiceRegistration_CheckOnUpdate tests that a ServiceRegistrations 335 // CheckOnUpdate is populated and updated properly 336 func TestServiceRegistration_CheckOnUpdate(t *testing.T) { 337 t.Parallel() 338 339 mock := NewMockAgent() 340 logger := testlog.HCLogger(t) 341 sc := NewServiceClient(mock, logger, true) 342 343 allocID := uuid.Generate() 344 ws := &WorkloadServices{ 345 AllocID: allocID, 346 Task: "taskname", 347 Restarter: &restartRecorder{}, 348 Services: []*structs.Service{ 349 { 350 Name: "taskname-service", 351 PortLabel: "x", 352 Tags: []string{"tag1", "tag2"}, 353 Meta: map[string]string{"meta1": "foo"}, 354 Checks: []*structs.ServiceCheck{ 355 { 356 357 Name: "c1", 358 Type: "tcp", 359 Interval: time.Second, 360 Timeout: time.Second, 361 PortLabel: "x", 362 OnUpdate: structs.OnUpdateIgnoreWarn, 363 }, 364 }, 365 }, 366 }, 367 Networks: []*structs.NetworkResource{ 368 { 369 DynamicPorts: []structs.Port{ 370 {Label: "x", Value: xPort}, 371 {Label: "y", Value: yPort}, 372 }, 373 }, 374 }, 375 } 376 377 require.NoError(t, sc.RegisterWorkload(ws)) 378 379 require.NotNil(t, sc.allocRegistrations[allocID]) 380 381 allocReg := sc.allocRegistrations[allocID] 382 serviceReg := allocReg.Tasks["taskname"] 383 require.NotNil(t, serviceReg) 384 385 // Ensure that CheckOnUpdate was set correctly 386 require.Len(t, serviceReg.Services, 1) 387 for _, sreg := range serviceReg.Services { 388 require.NotEmpty(t, sreg.CheckOnUpdate) 389 for _, onupdate := range sreg.CheckOnUpdate { 390 require.Equal(t, structs.OnUpdateIgnoreWarn, onupdate) 391 } 392 } 393 394 // Update 395 wsUpdate := new(WorkloadServices) 396 *wsUpdate = *ws 397 wsUpdate.Services[0].Checks[0].OnUpdate = structs.OnUpdateRequireHealthy 398 399 require.NoError(t, sc.UpdateWorkload(ws, wsUpdate)) 400 401 require.NotNil(t, sc.allocRegistrations[allocID]) 402 403 allocReg = sc.allocRegistrations[allocID] 404 serviceReg = allocReg.Tasks["taskname"] 405 require.NotNil(t, serviceReg) 406 407 // Ensure that CheckOnUpdate was updated correctly 408 require.Len(t, serviceReg.Services, 1) 409 for _, sreg := range serviceReg.Services { 410 require.NotEmpty(t, sreg.CheckOnUpdate) 411 for _, onupdate := range sreg.CheckOnUpdate { 412 require.Equal(t, structs.OnUpdateRequireHealthy, onupdate) 413 } 414 } 415 }