Skip to content

Commit 933e32a

Browse files
authored
Merge pull request #9029 from hashicorp/b-tgs-updates
consul/connect: trigger update as necessary on connect changes
2 parents c195f93 + 37cd0ec commit 933e32a

File tree

6 files changed

+340
-2
lines changed

6 files changed

+340
-2
lines changed

nomad/structs/services.go

+57-2
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,7 @@ func (c *ConsulConnect) Copy() *ConsulConnect {
696696
}
697697
}
698698

699-
// Equals returns true if the structs are recursively equal.
699+
// Equals returns true if the connect blocks are deeply equal.
700700
func (c *ConsulConnect) Equals(o *ConsulConnect) bool {
701701
if c == nil || o == nil {
702702
return c == o
@@ -710,7 +710,9 @@ func (c *ConsulConnect) Equals(o *ConsulConnect) bool {
710710
return false
711711
}
712712

713-
// todo(shoenig) task has never been compared, should it be?
713+
if !c.SidecarTask.Equals(o.SidecarTask) {
714+
return false
715+
}
714716

715717
if !c.Gateway.Equals(o.Gateway) {
716718
return false
@@ -864,6 +866,59 @@ type SidecarTask struct {
864866
KillSignal string
865867
}
866868

869+
func (t *SidecarTask) Equals(o *SidecarTask) bool {
870+
if t == nil || o == nil {
871+
return t == o
872+
}
873+
874+
if t.Name != o.Name {
875+
return false
876+
}
877+
878+
if t.Driver != o.Driver {
879+
return false
880+
}
881+
882+
if t.User != o.User {
883+
return false
884+
}
885+
886+
// config compare
887+
if !opaqueMapsEqual(t.Config, o.Config) {
888+
return false
889+
}
890+
891+
if !helper.CompareMapStringString(t.Env, o.Env) {
892+
return false
893+
}
894+
895+
if !t.Resources.Equals(o.Resources) {
896+
return false
897+
}
898+
899+
if !helper.CompareMapStringString(t.Meta, o.Meta) {
900+
return false
901+
}
902+
903+
if !helper.CompareTimePtrs(t.KillTimeout, o.KillTimeout) {
904+
return false
905+
}
906+
907+
if !t.LogConfig.Equals(o.LogConfig) {
908+
return false
909+
}
910+
911+
if !helper.CompareTimePtrs(t.ShutdownDelay, o.ShutdownDelay) {
912+
return false
913+
}
914+
915+
if t.KillSignal != o.KillSignal {
916+
return false
917+
}
918+
919+
return true
920+
}
921+
867922
func (t *SidecarTask) Copy() *SidecarTask {
868923
if t == nil {
869924
return nil

nomad/structs/services_test.go

+79
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,85 @@ func TestSidecarTask_MergeIntoTask(t *testing.T) {
349349
require.Exactly(t, expected, task)
350350
}
351351

352+
func TestSidecarTask_Equals(t *testing.T) {
353+
t.Parallel()
354+
355+
original := &SidecarTask{
356+
Name: "sidecar-task-1",
357+
Driver: "docker",
358+
User: "nobody",
359+
Config: map[string]interface{}{"foo": 1},
360+
Env: map[string]string{"color": "blue"},
361+
Resources: &Resources{MemoryMB: 300},
362+
Meta: map[string]string{"index": "1"},
363+
KillTimeout: helper.TimeToPtr(2 * time.Second),
364+
LogConfig: &LogConfig{
365+
MaxFiles: 2,
366+
MaxFileSizeMB: 300,
367+
},
368+
ShutdownDelay: helper.TimeToPtr(10 * time.Second),
369+
KillSignal: "SIGTERM",
370+
}
371+
372+
t.Run("unmodified", func(t *testing.T) {
373+
duplicate := original.Copy()
374+
require.True(t, duplicate.Equals(original))
375+
})
376+
377+
type st = SidecarTask
378+
type tweaker = func(task *st)
379+
380+
try := func(t *testing.T, tweak tweaker) {
381+
modified := original.Copy()
382+
tweak(modified)
383+
require.NotEqual(t, original, modified)
384+
}
385+
386+
t.Run("mod name", func(t *testing.T) {
387+
try(t, func(s *st) { s.Name = "sidecar-task-2" })
388+
})
389+
390+
t.Run("mod driver", func(t *testing.T) {
391+
try(t, func(s *st) { s.Driver = "exec" })
392+
})
393+
394+
t.Run("mod user", func(t *testing.T) {
395+
try(t, func(s *st) { s.User = "root" })
396+
})
397+
398+
t.Run("mod config", func(t *testing.T) {
399+
try(t, func(s *st) { s.Config = map[string]interface{}{"foo": 2} })
400+
})
401+
402+
t.Run("mod env", func(t *testing.T) {
403+
try(t, func(s *st) { s.Env = map[string]string{"color": "red"} })
404+
})
405+
406+
t.Run("mod resources", func(t *testing.T) {
407+
try(t, func(s *st) { s.Resources = &Resources{MemoryMB: 200} })
408+
})
409+
410+
t.Run("mod meta", func(t *testing.T) {
411+
try(t, func(s *st) { s.Meta = map[string]string{"index": "2"} })
412+
})
413+
414+
t.Run("mod kill timeout", func(t *testing.T) {
415+
try(t, func(s *st) { s.KillTimeout = helper.TimeToPtr(3 * time.Second) })
416+
})
417+
418+
t.Run("mod log config", func(t *testing.T) {
419+
try(t, func(s *st) { s.LogConfig = &LogConfig{MaxFiles: 3} })
420+
})
421+
422+
t.Run("mod shutdown delay", func(t *testing.T) {
423+
try(t, func(s *st) { s.ShutdownDelay = helper.TimeToPtr(20 * time.Second) })
424+
})
425+
426+
t.Run("mod kill signal", func(t *testing.T) {
427+
try(t, func(s *st) { s.KillSignal = "SIGHUP" })
428+
})
429+
}
430+
352431
func TestConsulUpstream_upstreamEquals(t *testing.T) {
353432
t.Parallel()
354433

nomad/structs/structs.go

+16
Original file line numberDiff line numberDiff line change
@@ -6231,6 +6231,22 @@ type LogConfig struct {
62316231
MaxFileSizeMB int
62326232
}
62336233

6234+
func (l *LogConfig) Equals(o *LogConfig) bool {
6235+
if l == nil || o == nil {
6236+
return l == o
6237+
}
6238+
6239+
if l.MaxFiles != o.MaxFiles {
6240+
return false
6241+
}
6242+
6243+
if l.MaxFileSizeMB != o.MaxFileSizeMB {
6244+
return false
6245+
}
6246+
6247+
return true
6248+
}
6249+
62346250
func (l *LogConfig) Copy() *LogConfig {
62356251
if l == nil {
62366252
return nil

nomad/structs/structs_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -2123,6 +2123,38 @@ func TestTask_Validate_LogConfig(t *testing.T) {
21232123
}
21242124
}
21252125

2126+
func TestLogConfig_Equals(t *testing.T) {
2127+
t.Run("both nil", func(t *testing.T) {
2128+
a := (*LogConfig)(nil)
2129+
b := (*LogConfig)(nil)
2130+
require.True(t, a.Equals(b))
2131+
})
2132+
2133+
t.Run("one nil", func(t *testing.T) {
2134+
a := new(LogConfig)
2135+
b := (*LogConfig)(nil)
2136+
require.False(t, a.Equals(b))
2137+
})
2138+
2139+
t.Run("max files", func(t *testing.T) {
2140+
a := &LogConfig{MaxFiles: 1, MaxFileSizeMB: 200}
2141+
b := &LogConfig{MaxFiles: 2, MaxFileSizeMB: 200}
2142+
require.False(t, a.Equals(b))
2143+
})
2144+
2145+
t.Run("max file size", func(t *testing.T) {
2146+
a := &LogConfig{MaxFiles: 1, MaxFileSizeMB: 100}
2147+
b := &LogConfig{MaxFiles: 1, MaxFileSizeMB: 200}
2148+
require.False(t, a.Equals(b))
2149+
})
2150+
2151+
t.Run("same", func(t *testing.T) {
2152+
a := &LogConfig{MaxFiles: 1, MaxFileSizeMB: 200}
2153+
b := &LogConfig{MaxFiles: 1, MaxFileSizeMB: 200}
2154+
require.True(t, a.Equals(b))
2155+
})
2156+
}
2157+
21262158
func TestTask_Validate_CSIPluginConfig(t *testing.T) {
21272159
table := []struct {
21282160
name string

scheduler/util.go

+77
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,11 @@ func tasksUpdated(jobA, jobB *structs.Job, taskGroup string) bool {
377377
return true
378378
}
379379

380+
// Check connect service(s) updated
381+
if connectServiceUpdated(a.Services, b.Services) {
382+
return true
383+
}
384+
380385
// Check each task
381386
for _, at := range a.Tasks {
382387
bt := b.LookupTask(at.Name)
@@ -429,6 +434,78 @@ func tasksUpdated(jobA, jobB *structs.Job, taskGroup string) bool {
429434
return false
430435
}
431436

437+
// connectServiceUpdated returns true if any services with a connect stanza have
438+
// been changed in such a way that requires a destructive update.
439+
//
440+
// Ordinary services can be updated in-place by updating the service definition
441+
// in Consul. Connect service changes mostly require destroying the task.
442+
func connectServiceUpdated(servicesA, servicesB []*structs.Service) bool {
443+
for _, serviceA := range servicesA {
444+
if serviceA.Connect != nil {
445+
for _, serviceB := range servicesB {
446+
if serviceA.Name == serviceB.Name {
447+
if connectUpdated(serviceA.Connect, serviceB.Connect) {
448+
return true
449+
}
450+
// Part of the Connect plumbing is derived from port label,
451+
// if that changes we need to destroy the task.
452+
if serviceA.PortLabel != serviceB.PortLabel {
453+
return true
454+
}
455+
break
456+
}
457+
}
458+
}
459+
}
460+
return false
461+
}
462+
463+
// connectUpdated returns true if the connect block has been updated in a manner
464+
// that will require a destructive update.
465+
//
466+
// Fields that can be updated through consul-sync do not need a destructive
467+
// update.
468+
func connectUpdated(connectA, connectB *structs.ConsulConnect) bool {
469+
if connectA == nil || connectB == nil {
470+
return connectA == connectB
471+
}
472+
473+
if connectA.Native != connectB.Native {
474+
return true
475+
}
476+
477+
if !connectA.Gateway.Equals(connectB.Gateway) {
478+
return true
479+
}
480+
481+
if !connectA.SidecarTask.Equals(connectB.SidecarTask) {
482+
return true
483+
}
484+
485+
// not everything in sidecar_service needs task destruction
486+
if connectSidecarServiceUpdated(connectA.SidecarService, connectB.SidecarService) {
487+
return true
488+
}
489+
490+
return false
491+
}
492+
493+
func connectSidecarServiceUpdated(ssA, ssB *structs.ConsulSidecarService) bool {
494+
if ssA == nil || ssB == nil {
495+
return ssA == ssB
496+
}
497+
498+
if ssA.Port != ssB.Port {
499+
return true
500+
}
501+
502+
// sidecar_service.tags handled in-place (registration)
503+
504+
// sidecar_service.proxy handled in-place (registration + xDS)
505+
506+
return false
507+
}
508+
432509
func networkUpdated(netA, netB []*structs.NetworkResource) bool {
433510
if len(netA) != len(netB) {
434511
return true

0 commit comments

Comments
 (0)