28faf989ce956c667e51fd00fc19954e18e91794
[arvados.git] / services / datamanager / datamanager_test.go
1 package main
2
3 import (
4         "encoding/json"
5         "fmt"
6         "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
7         "git.curoverse.com/arvados.git/sdk/go/arvadostest"
8         "git.curoverse.com/arvados.git/sdk/go/keepclient"
9         "git.curoverse.com/arvados.git/services/datamanager/collection"
10         "git.curoverse.com/arvados.git/services/datamanager/summary"
11         "io/ioutil"
12         "net/http"
13         "os"
14         "os/exec"
15         "regexp"
16         "strings"
17         "testing"
18         "time"
19 )
20
21 var arv arvadosclient.ArvadosClient
22 var keepClient *keepclient.KeepClient
23 var keepServers []string
24
25 func SetupDataManagerTest(t *testing.T) {
26         os.Setenv("ARVADOS_API_HOST_INSECURE", "true")
27
28         // start api and keep servers
29         arvadostest.ResetEnv()
30         arvadostest.StartAPI()
31         arvadostest.StartKeep(2, false)
32
33         var err error
34         arv, err = arvadosclient.MakeArvadosClient()
35         if err != nil {
36                 t.Fatalf("Error making arvados client: %s", err)
37         }
38         arv.ApiToken = arvadostest.DataManagerToken
39
40         // keep client
41         keepClient = &keepclient.KeepClient{
42                 Arvados:       &arv,
43                 Want_replicas: 2,
44                 Using_proxy:   true,
45                 Client:        &http.Client{},
46         }
47
48         // discover keep services
49         if err = keepClient.DiscoverKeepServers(); err != nil {
50                 t.Fatalf("Error discovering keep services: %s", err)
51         }
52         keepServers = []string{}
53         for _, host := range keepClient.LocalRoots() {
54                 keepServers = append(keepServers, host)
55         }
56 }
57
58 func TearDownDataManagerTest(t *testing.T) {
59         arvadostest.StopKeep(2)
60         arvadostest.StopAPI()
61         summary.WriteDataTo = ""
62         collection.HeapProfileFilename = ""
63 }
64
65 func putBlock(t *testing.T, data string) string {
66         locator, _, err := keepClient.PutB([]byte(data))
67         if err != nil {
68                 t.Fatalf("Error putting test data for %s %s %v", data, locator, err)
69         }
70         if locator == "" {
71                 t.Fatalf("No locator found after putting test data")
72         }
73
74         splits := strings.Split(locator, "+")
75         return splits[0] + "+" + splits[1]
76 }
77
78 func getBlock(t *testing.T, locator string, data string) {
79         reader, blocklen, _, err := keepClient.Get(locator)
80         if err != nil {
81                 t.Fatalf("Error getting test data in setup for %s %s %v", data, locator, err)
82         }
83         if reader == nil {
84                 t.Fatalf("No reader found after putting test data")
85         }
86         if blocklen != int64(len(data)) {
87                 t.Fatalf("blocklen %d did not match data len %d", blocklen, len(data))
88         }
89
90         all, err := ioutil.ReadAll(reader)
91         if string(all) != data {
92                 t.Fatalf("Data read %s did not match expected data %s", string(all), data)
93         }
94 }
95
96 // Create a collection using arv-put
97 func createCollection(t *testing.T, data string) string {
98         tempfile, err := ioutil.TempFile(os.TempDir(), "temp-test-file")
99         defer os.Remove(tempfile.Name())
100
101         _, err = tempfile.Write([]byte(data))
102         if err != nil {
103                 t.Fatalf("Error writing to tempfile %v", err)
104         }
105
106         // arv-put
107         output, err := exec.Command("arv-put", "--use-filename", "test.txt", tempfile.Name()).Output()
108         if err != nil {
109                 t.Fatalf("Error running arv-put %s", err)
110         }
111
112         uuid := string(output[0:27]) // trim terminating char
113         return uuid
114 }
115
116 // Get collection locator
117 var locatorMatcher = regexp.MustCompile(`^([0-9a-f]{32})\+(\d*)(.*)$`)
118
119 func getFirstLocatorFromCollection(t *testing.T, uuid string) string {
120         manifest := getCollection(t, uuid)["manifest_text"].(string)
121
122         locator := strings.Split(manifest, " ")[1]
123         match := locatorMatcher.FindStringSubmatch(locator)
124         if match == nil {
125                 t.Fatalf("No locator found in collection manifest %s", manifest)
126         }
127
128         return match[1] + "+" + match[2]
129 }
130
131 func switchToken(t string) func() {
132         orig := arv.ApiToken
133         restore := func() {
134                 arv.ApiToken = orig
135         }
136         arv.ApiToken = t
137         return restore
138 }
139
140 func getCollection(t *testing.T, uuid string) Dict {
141         defer switchToken(arvadostest.AdminToken)()
142
143         getback := make(Dict)
144         err := arv.Get("collections", uuid, nil, &getback)
145         if err != nil {
146                 t.Fatalf("Error getting collection %s", err)
147         }
148         if getback["uuid"] != uuid {
149                 t.Fatalf("Get collection uuid did not match original: $s, result: $s", uuid, getback["uuid"])
150         }
151
152         return getback
153 }
154
155 func updateCollection(t *testing.T, uuid string, paramName string, paramValue string) {
156         defer switchToken(arvadostest.AdminToken)()
157
158         err := arv.Update("collections", uuid, arvadosclient.Dict{
159                 "collection": arvadosclient.Dict{
160                         paramName: paramValue,
161                 },
162         }, &arvadosclient.Dict{})
163
164         if err != nil {
165                 t.Fatalf("Error updating collection %s", err)
166         }
167 }
168
169 type Dict map[string]interface{}
170
171 func deleteCollection(t *testing.T, uuid string) {
172         defer switchToken(arvadostest.AdminToken)()
173
174         getback := make(Dict)
175         err := arv.Delete("collections", uuid, nil, &getback)
176         if err != nil {
177                 t.Fatalf("Error deleting collection %s", err)
178         }
179         if getback["uuid"] != uuid {
180                 t.Fatalf("Delete collection uuid did not match original: $s, result: $s", uuid, getback["uuid"])
181         }
182 }
183
184 func dataManagerSingleRun(t *testing.T) {
185         err := singlerun(arv)
186         if err != nil {
187                 t.Fatalf("Error during singlerun %s", err)
188         }
189 }
190
191 func getBlockIndexesForServer(t *testing.T, i int) []string {
192         var indexes []string
193
194         path := keepServers[i] + "/index"
195         client := http.Client{}
196         req, err := http.NewRequest("GET", path, nil)
197         req.Header.Add("Authorization", "OAuth2 "+arvadostest.DataManagerToken)
198         req.Header.Add("Content-Type", "application/octet-stream")
199         resp, err := client.Do(req)
200         defer resp.Body.Close()
201
202         if err != nil {
203                 t.Fatalf("Error during %s %s", path, err)
204         }
205
206         body, err := ioutil.ReadAll(resp.Body)
207         if err != nil {
208                 t.Fatalf("Error reading response from %s %s", path, err)
209         }
210
211         lines := strings.Split(string(body), "\n")
212         for _, line := range lines {
213                 indexes = append(indexes, strings.Split(line, " ")...)
214         }
215
216         return indexes
217 }
218
219 func getBlockIndexes(t *testing.T) [][]string {
220         var indexes [][]string
221
222         for i := 0; i < len(keepServers); i++ {
223                 indexes = append(indexes, getBlockIndexesForServer(t, i))
224         }
225         return indexes
226 }
227
228 func verifyBlocks(t *testing.T, notExpected []string, expected []string, minReplication int) {
229         blocks := getBlockIndexes(t)
230
231         for _, block := range notExpected {
232                 for _, idx := range blocks {
233                         if valueInArray(block, idx) {
234                                 t.Fatalf("Found unexpected block %s", block)
235                         }
236                 }
237         }
238
239         for _, block := range expected {
240                 nFound := 0
241                 for _, idx := range blocks {
242                         if valueInArray(block, idx) {
243                                 nFound++
244                         }
245                 }
246                 if nFound < minReplication {
247                         t.Fatalf("Found %d replicas of block %s, expected >= %d", nFound, block, minReplication)
248                 }
249         }
250 }
251
252 func valueInArray(value string, list []string) bool {
253         for _, v := range list {
254                 if value == v {
255                         return true
256                 }
257         }
258         return false
259 }
260
261 // Test env uses two keep volumes. The volume names can be found by reading the files
262 // ARVADOS_HOME/tmp/keep0.volume and ARVADOS_HOME/tmp/keep1.volume
263 //
264 // The keep volumes are of the dir structure: volumeN/subdir/locator
265 func backdateBlocks(t *testing.T, oldUnusedBlockLocators []string) {
266         // First get rid of any size hints in the locators
267         var trimmedBlockLocators []string
268         for _, block := range oldUnusedBlockLocators {
269                 trimmedBlockLocators = append(trimmedBlockLocators, strings.Split(block, "+")[0])
270         }
271
272         // Get the working dir so that we can read keep{n}.volume files
273         wd, err := os.Getwd()
274         if err != nil {
275                 t.Fatalf("Error getting working dir %s", err)
276         }
277
278         // Now cycle through the two keep volumes
279         oldTime := time.Now().AddDate(0, -2, 0)
280         for i := 0; i < 2; i++ {
281                 filename := fmt.Sprintf("%s/../../tmp/keep%d.volume", wd, i)
282                 volumeDir, err := ioutil.ReadFile(filename)
283                 if err != nil {
284                         t.Fatalf("Error reading keep volume file %s %s", filename, err)
285                 }
286
287                 // Read the keep volume dir structure
288                 volumeContents, err := ioutil.ReadDir(string(volumeDir))
289                 if err != nil {
290                         t.Fatalf("Error reading keep dir %s %s", string(volumeDir), err)
291                 }
292
293                 // Read each subdir for each of the keep volume dir
294                 for _, subdir := range volumeContents {
295                         subdirName := fmt.Sprintf("%s/%s", volumeDir, subdir.Name())
296                         subdirContents, err := ioutil.ReadDir(string(subdirName))
297                         if err != nil {
298                                 t.Fatalf("Error reading keep dir %s %s", string(subdirName), err)
299                         }
300
301                         // Now we got to the files. The files are names are the block locators
302                         for _, fileInfo := range subdirContents {
303                                 blockName := fileInfo.Name()
304                                 myname := fmt.Sprintf("%s/%s", subdirName, blockName)
305                                 if valueInArray(blockName, trimmedBlockLocators) {
306                                         err = os.Chtimes(myname, oldTime, oldTime)
307                                 }
308                         }
309                 }
310         }
311 }
312
313 func getStatus(t *testing.T, path string) interface{} {
314         client := http.Client{}
315         req, err := http.NewRequest("GET", path, nil)
316         req.Header.Add("Authorization", "OAuth2 "+arvadostest.DataManagerToken)
317         req.Header.Add("Content-Type", "application/octet-stream")
318         resp, err := client.Do(req)
319         if err != nil {
320                 t.Fatalf("Error during %s %s", path, err)
321         }
322         defer resp.Body.Close()
323
324         var s interface{}
325         json.NewDecoder(resp.Body).Decode(&s)
326
327         return s
328 }
329
330 // Wait until PullQueue and TrashQueue are empty on all keepServers.
331 func waitUntilQueuesFinishWork(t *testing.T) {
332         for _, ks := range keepServers {
333                 for done := false; !done; {
334                         time.Sleep(100 * time.Millisecond)
335                         s := getStatus(t, ks+"/status.json")
336                         for _, qName := range []string{"PullQueue", "TrashQueue"} {
337                                 qStatus := s.(map[string]interface{})[qName].(map[string]interface{})
338                                 if qStatus["Queued"].(float64)+qStatus["InProgress"].(float64) == 0 {
339                                         done = true
340                                 }
341                         }
342                 }
343         }
344 }
345
346 // Create some blocks and backdate some of them.
347 // Also create some collections and delete some of them.
348 // Verify block indexes.
349 func TestPutAndGetBlocks(t *testing.T) {
350         defer TearDownDataManagerTest(t)
351         SetupDataManagerTest(t)
352
353         // Put some blocks which will be backdated later on
354         // The first one will also be used in a collection and hence should not be deleted when datamanager runs.
355         // The rest will be old and unreferenced and hence should be deleted when datamanager runs.
356         var oldUnusedBlockLocators []string
357         oldUnusedBlockData := "this block will have older mtime"
358         for i := 0; i < 5; i++ {
359                 oldUnusedBlockLocators = append(oldUnusedBlockLocators, putBlock(t, fmt.Sprintf("%s%d", oldUnusedBlockData, i)))
360         }
361         for i := 0; i < 5; i++ {
362                 getBlock(t, oldUnusedBlockLocators[i], fmt.Sprintf("%s%d", oldUnusedBlockData, i))
363         }
364
365         // The rest will be old and unreferenced and hence should be deleted when datamanager runs.
366         oldUsedBlockData := "this collection block will have older mtime"
367         oldUsedBlockLocator := putBlock(t, oldUsedBlockData)
368         getBlock(t, oldUsedBlockLocator, oldUsedBlockData)
369
370         // Put some more blocks which will not be backdated; hence they are still new, but not in any collection.
371         // Hence, even though unreferenced, these should not be deleted when datamanager runs.
372         var newBlockLocators []string
373         newBlockData := "this block is newer"
374         for i := 0; i < 5; i++ {
375                 newBlockLocators = append(newBlockLocators, putBlock(t, fmt.Sprintf("%s%d", newBlockData, i)))
376         }
377         for i := 0; i < 5; i++ {
378                 getBlock(t, newBlockLocators[i], fmt.Sprintf("%s%d", newBlockData, i))
379         }
380
381         // Create a collection that would be deleted later on
382         toBeDeletedCollectionUUID := createCollection(t, "some data for collection creation")
383         toBeDeletedCollectionLocator := getFirstLocatorFromCollection(t, toBeDeletedCollectionUUID)
384
385         // Create another collection that has the same data as the one of the old blocks
386         oldUsedBlockCollectionUUID := createCollection(t, oldUsedBlockData)
387         oldUsedBlockCollectionLocator := getFirstLocatorFromCollection(t, oldUsedBlockCollectionUUID)
388         if oldUsedBlockCollectionLocator != oldUsedBlockLocator {
389                 t.Fatalf("Locator of the collection with the same data as old block is different %s", oldUsedBlockCollectionLocator)
390         }
391
392         // Create another collection whose replication level will be changed
393         replicationCollectionUUID := createCollection(t, "replication level on this collection will be reduced")
394         replicationCollectionLocator := getFirstLocatorFromCollection(t, replicationCollectionUUID)
395
396         // Create two collections with same data; one will be deleted later on
397         dataForTwoCollections := "one of these collections will be deleted"
398         oneOfTwoWithSameDataUUID := createCollection(t, dataForTwoCollections)
399         oneOfTwoWithSameDataLocator := getFirstLocatorFromCollection(t, oneOfTwoWithSameDataUUID)
400         secondOfTwoWithSameDataUUID := createCollection(t, dataForTwoCollections)
401         secondOfTwoWithSameDataLocator := getFirstLocatorFromCollection(t, secondOfTwoWithSameDataUUID)
402         if oneOfTwoWithSameDataLocator != secondOfTwoWithSameDataLocator {
403                 t.Fatalf("Locators for both these collections expected to be same: %s %s", oneOfTwoWithSameDataLocator, secondOfTwoWithSameDataLocator)
404         }
405
406         // create collection with empty manifest text
407         emptyBlockLocator := putBlock(t, "")
408         emptyCollection := createCollection(t, "")
409
410         // Verify blocks before doing any backdating / deleting.
411         var expected []string
412         expected = append(expected, oldUnusedBlockLocators...)
413         expected = append(expected, newBlockLocators...)
414         expected = append(expected, toBeDeletedCollectionLocator)
415         expected = append(expected, replicationCollectionLocator)
416         expected = append(expected, oneOfTwoWithSameDataLocator)
417         expected = append(expected, secondOfTwoWithSameDataLocator)
418         expected = append(expected, emptyBlockLocator)
419
420         verifyBlocks(t, nil, expected, 2)
421
422         // Run datamanager in singlerun mode
423         dataManagerSingleRun(t)
424         waitUntilQueuesFinishWork(t)
425
426         verifyBlocks(t, nil, expected, 2)
427
428         // Backdate the to-be old blocks and delete the collections
429         backdateBlocks(t, oldUnusedBlockLocators)
430         deleteCollection(t, toBeDeletedCollectionUUID)
431         deleteCollection(t, secondOfTwoWithSameDataUUID)
432         backdateBlocks(t, []string{emptyBlockLocator})
433         deleteCollection(t, emptyCollection)
434
435         // Run data manager again
436         dataManagerSingleRun(t)
437         waitUntilQueuesFinishWork(t)
438
439         // Get block indexes and verify that all backdated blocks except the first one used in collection are not included.
440         expected = expected[:0]
441         expected = append(expected, oldUsedBlockLocator)
442         expected = append(expected, newBlockLocators...)
443         expected = append(expected, toBeDeletedCollectionLocator)
444         expected = append(expected, oneOfTwoWithSameDataLocator)
445         expected = append(expected, secondOfTwoWithSameDataLocator)
446         expected = append(expected, emptyBlockLocator) // even when unreferenced, this remains
447
448         verifyBlocks(t, oldUnusedBlockLocators, expected, 2)
449
450         // Reduce desired replication on replicationCollectionUUID
451         // collection, and verify that Data Manager does not reduce
452         // actual replication any further than that. (It might not
453         // reduce actual replication at all; that's OK for this test.)
454
455         // Reduce desired replication level.
456         updateCollection(t, replicationCollectionUUID, "replication_desired", "1")
457         collection := getCollection(t, replicationCollectionUUID)
458         if collection["replication_desired"].(interface{}) != float64(1) {
459                 t.Fatalf("After update replication_desired is not 1; instead it is %v", collection["replication_desired"])
460         }
461
462         // Verify data is currently overreplicated.
463         verifyBlocks(t, nil, []string{replicationCollectionLocator}, 2)
464
465         // Run data manager again
466         dataManagerSingleRun(t)
467         waitUntilQueuesFinishWork(t)
468
469         // Verify data is not underreplicated.
470         verifyBlocks(t, nil, []string{replicationCollectionLocator}, 1)
471
472         // Verify *other* collections' data is not underreplicated.
473         verifyBlocks(t, oldUnusedBlockLocators, expected, 2)
474 }
475
476 func TestDatamanagerSingleRunRepeatedly(t *testing.T) {
477         defer TearDownDataManagerTest(t)
478         SetupDataManagerTest(t)
479
480         for i := 0; i < 10; i++ {
481                 err := singlerun(arv)
482                 if err != nil {
483                         t.Fatalf("Got an error during datamanager singlerun: %v", err)
484                 }
485         }
486 }
487
488 func TestGetStatusRepeatedly(t *testing.T) {
489         defer TearDownDataManagerTest(t)
490         SetupDataManagerTest(t)
491
492         for i := 0; i < 10; i++ {
493                 for j := 0; j < 2; j++ {
494                         s := getStatus(t, keepServers[j]+"/status.json")
495
496                         var pullQueueStatus interface{}
497                         pullQueueStatus = s.(map[string]interface{})["PullQueue"]
498                         var trashQueueStatus interface{}
499                         trashQueueStatus = s.(map[string]interface{})["TrashQueue"]
500
501                         if pullQueueStatus.(map[string]interface{})["Queued"] == nil ||
502                                 pullQueueStatus.(map[string]interface{})["InProgress"] == nil ||
503                                 trashQueueStatus.(map[string]interface{})["Queued"] == nil ||
504                                 trashQueueStatus.(map[string]interface{})["InProgress"] == nil {
505                                 t.Fatalf("PullQueue and TrashQueue status not found")
506                         }
507
508                         time.Sleep(100 * time.Millisecond)
509                 }
510         }
511 }
512
513 func TestRunDatamanagerWithBogusServer(t *testing.T) {
514         defer TearDownDataManagerTest(t)
515         SetupDataManagerTest(t)
516
517         arv.ApiServer = "bogus-server"
518
519         err := singlerun(arv)
520         if err == nil {
521                 t.Fatalf("Expected error during singlerun with bogus server")
522         }
523 }
524
525 func TestRunDatamanagerAsNonAdminUser(t *testing.T) {
526         defer TearDownDataManagerTest(t)
527         SetupDataManagerTest(t)
528
529         arv.ApiToken = arvadostest.ActiveToken
530
531         err := singlerun(arv)
532         if err == nil {
533                 t.Fatalf("Expected error during singlerun as non-admin user")
534         }
535 }
536
537 func TestPutAndGetBlocks_NoErrorDuringSingleRun(t *testing.T) {
538         testOldBlocksNotDeletedOnDataManagerError(t, "", "", false, false)
539 }
540
541 func TestPutAndGetBlocks_ErrorDuringGetCollectionsBadWriteTo(t *testing.T) {
542         testOldBlocksNotDeletedOnDataManagerError(t, "/badwritetofile", "", true, true)
543 }
544
545 func TestPutAndGetBlocks_ErrorDuringGetCollectionsBadHeapProfileFilename(t *testing.T) {
546         testOldBlocksNotDeletedOnDataManagerError(t, "", "/badheapprofilefile", true, true)
547 }
548
549 // Create some blocks and backdate some of them.
550 // Run datamanager while producing an error condition.
551 // Verify that the blocks are hence not deleted.
552 func testOldBlocksNotDeletedOnDataManagerError(t *testing.T, writeDataTo string, heapProfileFile string, expectError bool, expectOldBlocks bool) {
553         defer TearDownDataManagerTest(t)
554         SetupDataManagerTest(t)
555
556         // Put some blocks and backdate them.
557         var oldUnusedBlockLocators []string
558         oldUnusedBlockData := "this block will have older mtime"
559         for i := 0; i < 5; i++ {
560                 oldUnusedBlockLocators = append(oldUnusedBlockLocators, putBlock(t, fmt.Sprintf("%s%d", oldUnusedBlockData, i)))
561         }
562         backdateBlocks(t, oldUnusedBlockLocators)
563
564         // Run data manager
565         summary.WriteDataTo = writeDataTo
566         collection.HeapProfileFilename = heapProfileFile
567
568         err := singlerun(arv)
569         if !expectError {
570                 if err != nil {
571                         t.Fatalf("Got an error during datamanager singlerun: %v", err)
572                 }
573         } else {
574                 if err == nil {
575                         t.Fatalf("Expected error during datamanager singlerun")
576                 }
577         }
578         waitUntilQueuesFinishWork(t)
579
580         // Get block indexes and verify that all backdated blocks are not/deleted as expected
581         if expectOldBlocks {
582                 verifyBlocks(t, nil, oldUnusedBlockLocators, 2)
583         } else {
584                 verifyBlocks(t, oldUnusedBlockLocators, nil, 2)
585         }
586 }
587
588 // Create a collection with multiple streams and blocks
589 func createMultiStreamBlockCollection(t *testing.T, data string, numStreams, numBlocks int) (string, []string) {
590         defer switchToken(arvadostest.AdminToken)()
591
592         manifest := ""
593         locators := make(map[string]bool)
594         for s := 0; s < numStreams; s++ {
595                 manifest += fmt.Sprintf("./stream%d ", s)
596                 for b := 0; b < numBlocks; b++ {
597                         locator, _, err := keepClient.PutB([]byte(fmt.Sprintf("%s in stream %d and block %d", data, s, b)))
598                         if err != nil {
599                                 t.Fatalf("Error creating block %d in stream %d: %v", b, s, err)
600                         }
601                         locators[strings.Split(locator, "+A")[0]] = true
602                         manifest += locator + " "
603                 }
604                 manifest += "0:1:dummyfile.txt\n"
605         }
606
607         collection := make(Dict)
608         err := arv.Create("collections",
609                 arvadosclient.Dict{"collection": arvadosclient.Dict{"manifest_text": manifest}},
610                 &collection)
611
612         if err != nil {
613                 t.Fatalf("Error creating collection %v", err)
614         }
615
616         var locs []string
617         for k, _ := range locators {
618                 locs = append(locs, k)
619         }
620
621         return collection["uuid"].(string), locs
622 }
623
624 // Create collection with multiple streams and blocks; backdate the blocks and but do not delete the collection.
625 // Also, create stray block and backdate it.
626 // After datamanager run: expect blocks from the collection, but not the stray block.
627 func TestManifestWithMultipleStreamsAndBlocks(t *testing.T) {
628         defer TearDownDataManagerTest(t)
629         SetupDataManagerTest(t)
630
631         // create collection whose blocks will be backdated
632         collectionWithOldBlocks, oldBlocks := createMultiStreamBlockCollection(t, "old block", 100, 10)
633         if collectionWithOldBlocks == "" {
634                 t.Fatalf("Failed to create collection with 1000 blocks")
635         }
636         if len(oldBlocks) != 1000 {
637                 t.Fatalf("Not all blocks are created: expected %v, found %v", 1000, len(oldBlocks))
638         }
639
640         // create a stray block that will be backdated
641         strayOldBlock := putBlock(t, "this stray block is old")
642
643         expected := []string{strayOldBlock}
644         expected = append(expected, oldBlocks...)
645         verifyBlocks(t, nil, expected, 2)
646
647         // Backdate old blocks; but the collection still references these blocks
648         backdateBlocks(t, oldBlocks)
649
650         // also backdate the stray old block
651         backdateBlocks(t, []string{strayOldBlock})
652
653         // run datamanager
654         dataManagerSingleRun(t)
655
656         // verify that strayOldBlock is not to be found, but the collections blocks are still there
657         verifyBlocks(t, []string{strayOldBlock}, oldBlocks, 2)
658 }