// TestExpiration ensures that expired claims get reclaimed properly. func TestExpiration(t *testing.T) { t.Parallel() coord, conf := setupEtcd(t) claims := make(chan int, 10) hf := metafora.HandlerFunc(metafora.SimpleHandler(func(_ metafora.Task, stop <-chan bool) bool { claims <- 1 <-stop return true })) consumer, err := metafora.NewConsumer(coord, hf, metafora.DumbBalancer) if err != nil { t.Fatalf("Error creating consumer: %+v", err) } client, _ := testutil.NewEtcdClient(t) _, err = client.Create(path.Join(conf.Namespace, TasksPath, "abc", OwnerMarker), `{"node":"--"}`, 1) if err != nil { t.Fatalf("Error creating fake claim: %v", err) } defer consumer.Shutdown() go consumer.Run() // Wait for claim to expire and coordinator to pick up task select { case <-claims: // Task claimed! case <-time.After(5 * time.Second): t.Fatal("Task not claimed long after it should have been.") } tasks := consumer.Tasks() if len(tasks) != 1 { t.Fatalf("Expected 1 task to be claimed but found: %v", tasks) } }
// TestNodeRefresher ensures the node refresher properly updates the TTL on the // node directory in etcd and shuts down the entire consumer on error. func TestNodeRefresher(t *testing.T) { t.Parallel() _, conf := setupEtcd(t) // Use a custom node path ttl conf.NodeTTL = 3 coord, err := NewEtcdCoordinator(conf) if err != nil { t.Fatalf("Error creating coordinator: %v", err) } hf := metafora.HandlerFunc(nil) // we won't be handling any tasks consumer, err := metafora.NewConsumer(coord, hf, metafora.DumbBalancer) if err != nil { t.Fatalf("Error creating consumer: %+v", err) } client, _ := testutil.NewEtcdClient(t) defer consumer.Shutdown() runDone := make(chan struct{}) go func() { consumer.Run() close(runDone) }() nodePath := path.Join(conf.Namespace, NodesPath, conf.Name) ttl := int64(-1) deadline := time.Now().Add(3 * time.Second) for time.Now().Before(deadline) { resp, _ := client.Get(nodePath, false, false) if resp != nil && resp.Node.Dir { ttl = resp.Node.TTL break } time.Sleep(250 * time.Millisecond) } if ttl == -1 { t.Fatalf("Node path %s not found.", nodePath) } if ttl < 1 || ttl > 3 { t.Fatalf("Expected TTL to be between 1 and 3, found: %d", ttl) } // Let it refresh once to make sure that works time.Sleep(time.Duration(ttl) * time.Second) ttl = -1 deadline = time.Now().Add(3 * time.Second) for time.Now().Before(deadline) { resp, _ := client.Get(nodePath, false, false) if resp != nil && resp.Node.Dir { ttl = resp.Node.TTL break } time.Sleep(250 * time.Millisecond) } if ttl == -1 { t.Fatalf("Node path %s not found.", nodePath) } // Now remove the node out from underneath the refresher to cause it to fail if _, err := client.Delete(nodePath, true); err != nil { t.Fatalf("Unexpected error deleting %s: %+v", nodePath, err) } select { case <-runDone: // success! run exited case <-time.After(5 * time.Second): t.Fatal("Consumer didn't exit even though node directory disappeared!") } }