github.com/mongey/nomad@v0.5.2/command/agent/consul/chaos_test.go (about) 1 // +build chaos 2 3 package consul 4 5 import ( 6 "fmt" 7 "io/ioutil" 8 "sort" 9 "strings" 10 "sync" 11 "testing" 12 "time" 13 14 "github.com/hashicorp/consul/testutil" 15 "github.com/hashicorp/nomad/nomad/structs" 16 "github.com/hashicorp/nomad/nomad/structs/config" 17 ) 18 19 func TestSyncerChaos(t *testing.T) { 20 // Create an embedded Consul server 21 testconsul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) { 22 // If -v wasn't specified squelch consul logging 23 if !testing.Verbose() { 24 c.Stdout = ioutil.Discard 25 c.Stderr = ioutil.Discard 26 } 27 }) 28 defer testconsul.Stop() 29 30 // Configure Syncer to talk to the test server 31 cconf := config.DefaultConsulConfig() 32 cconf.Addr = testconsul.HTTPAddr 33 34 clientSyncer, err := NewSyncer(cconf, nil, logger) 35 if err != nil { 36 t.Fatalf("Error creating Syncer: %v", err) 37 } 38 defer clientSyncer.Shutdown() 39 40 execSyncer, err := NewSyncer(cconf, nil, logger) 41 if err != nil { 42 t.Fatalf("Error creating Syncer: %v", err) 43 } 44 defer execSyncer.Shutdown() 45 46 clientService := &structs.Service{Name: "nomad-client"} 47 services := map[ServiceKey]*structs.Service{ 48 GenerateServiceKey(clientService): clientService, 49 } 50 if err := clientSyncer.SetServices("client", services); err != nil { 51 t.Fatalf("error setting client service: %v", err) 52 } 53 54 const execn = 100 55 const reapern = 2 56 errors := make(chan error, 100) 57 wg := sync.WaitGroup{} 58 59 // Start goroutines to concurrently SetServices 60 for i := 0; i < execn; i++ { 61 wg.Add(1) 62 go func(i int) { 63 defer wg.Done() 64 domain := ServiceDomain(fmt.Sprintf("exec-%d", i)) 65 services := map[ServiceKey]*structs.Service{} 66 for ii := 0; ii < 10; ii++ { 67 s := &structs.Service{Name: fmt.Sprintf("exec-%d-%d", i, ii)} 68 services[GenerateServiceKey(s)] = s 69 if err := execSyncer.SetServices(domain, services); err != nil { 70 select { 71 case errors <- err: 72 default: 73 } 74 return 75 } 76 time.Sleep(1) 77 } 78 }(i) 79 } 80 81 // SyncServices runs a timer started by Syncer.Run which we don't use 82 // in this test, so run SyncServices concurrently 83 wg.Add(1) 84 go func() { 85 defer wg.Done() 86 for i := 0; i < execn; i++ { 87 if err := execSyncer.SyncServices(); err != nil { 88 select { 89 case errors <- err: 90 default: 91 } 92 return 93 } 94 time.Sleep(100) 95 } 96 }() 97 98 wg.Add(1) 99 go func() { 100 defer wg.Done() 101 if err := clientSyncer.ReapUnmatched([]ServiceDomain{"nomad-client"}); err != nil { 102 select { 103 case errors <- err: 104 default: 105 } 106 return 107 } 108 }() 109 110 // Reap all but exec-0-* 111 wg.Add(1) 112 go func() { 113 defer wg.Done() 114 for i := 0; i < execn; i++ { 115 if err := execSyncer.ReapUnmatched([]ServiceDomain{"exec-0", ServiceDomain(fmt.Sprintf("exec-%d", i))}); err != nil { 116 select { 117 case errors <- err: 118 default: 119 } 120 } 121 time.Sleep(100) 122 } 123 }() 124 125 go func() { 126 wg.Wait() 127 close(errors) 128 }() 129 130 for err := range errors { 131 if err != nil { 132 t.Errorf("error setting service from executor goroutine: %v", err) 133 } 134 } 135 136 // Do a final ReapUnmatched to get consul back into a deterministic state 137 if err := execSyncer.ReapUnmatched([]ServiceDomain{"exec-0"}); err != nil { 138 t.Fatalf("error doing final reap: %v", err) 139 } 140 141 // flattenedServices should be fully populated as ReapUnmatched doesn't 142 // touch Syncer's internal state 143 expected := map[string]struct{}{} 144 for i := 0; i < execn; i++ { 145 for ii := 0; ii < 10; ii++ { 146 expected[fmt.Sprintf("exec-%d-%d", i, ii)] = struct{}{} 147 } 148 } 149 150 for _, s := range execSyncer.flattenedServices() { 151 _, ok := expected[s.Name] 152 if !ok { 153 t.Errorf("%s unexpected", s.Name) 154 } 155 delete(expected, s.Name) 156 } 157 if len(expected) > 0 { 158 left := []string{} 159 for s := range expected { 160 left = append(left, s) 161 } 162 sort.Strings(left) 163 t.Errorf("Couldn't find %d names in flattened services:\n%s", len(expected), strings.Join(left, "\n")) 164 } 165 166 // All but exec-0 and possibly some of exec-99 should have been reaped 167 { 168 services, err := execSyncer.client.Agent().Services() 169 if err != nil { 170 t.Fatalf("Error getting services: %v", err) 171 } 172 expected := []int{} 173 for k, service := range services { 174 if service.Service == "consul" { 175 continue 176 } 177 i := -1 178 ii := -1 179 fmt.Sscanf(service.Service, "exec-%d-%d", &i, &ii) 180 switch { 181 case i == -1 || ii == -1: 182 t.Errorf("invalid service: %s -> %s", k, service.Service) 183 case i != 0 || ii > 9: 184 t.Errorf("unexpected service: %s -> %s", k, service.Service) 185 default: 186 expected = append(expected, ii) 187 } 188 } 189 if len(expected) != 10 { 190 t.Errorf("expected 0-9 but found: %#q", expected) 191 } 192 } 193 }