cluster.fsinfo_test.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. package main
  2. /*
  3. Tests for the cluster side of two features that cross into the core:
  4. - which node performs nightly maintenance of a file system handler
  5. - how a cluster file record is turned into the rows and locations the
  6. File Manager properties dialog shows
  7. */
  8. import (
  9. "strings"
  10. "testing"
  11. "imuslab.com/arozos/mod/cluster/metadata"
  12. fs "imuslab.com/arozos/mod/filesystem"
  13. "imuslab.com/arozos/mod/filesystem/arozfs"
  14. "imuslab.com/arozos/mod/time/nightly"
  15. )
  16. func TestNightlyFshOption(t *testing.T) {
  17. testcases := []struct {
  18. name string
  19. fsh *fs.FileSystemHandler
  20. masterOnly bool
  21. }{
  22. {"cluster drive", &fs.FileSystemHandler{Filesystem: "cluster"}, true},
  23. {"local disk", &fs.FileSystemHandler{Filesystem: "ext4"}, false},
  24. {"network drive", &fs.FileSystemHandler{Filesystem: "webdav"}, false},
  25. {"no handler", nil, false},
  26. }
  27. for _, tc := range testcases {
  28. t.Run(tc.name, func(t *testing.T) {
  29. if got := nightlyFshOption(tc.fsh).MasterNodeOnly; got != tc.masterOnly {
  30. t.Errorf("Expected MasterNodeOnly to be %v, got %v", tc.masterOnly, got)
  31. }
  32. })
  33. }
  34. }
  35. // Every node sees the same files on cluster:/, so a nightly pass that runs
  36. // on all of them deletes the same expired trash and version history once per
  37. // node. Only the master node does that work; the local drives of each node
  38. // are still maintained by that node.
  39. func TestNightlyShouldMaintainFsh(t *testing.T) {
  40. previous := nightlyManager
  41. defer func() { nightlyManager = previous }()
  42. nightlyManager = nightly.NewNightlyTaskManager(3)
  43. clusterDrive := &fs.FileSystemHandler{Filesystem: "cluster"}
  44. localDrive := &fs.FileSystemHandler{Filesystem: "ext4"}
  45. nightlyManager.SetMasterNodeResolver(func() bool { return false })
  46. if nightlyShouldMaintainFsh(clusterDrive) {
  47. t.Error("Expected cluster:/ maintenance to be left to the master node")
  48. }
  49. if !nightlyShouldMaintainFsh(localDrive) {
  50. t.Error("Expected a local drive to be maintained by its own host")
  51. }
  52. nightlyManager.SetMasterNodeResolver(func() bool { return true })
  53. if !nightlyShouldMaintainFsh(clusterDrive) {
  54. t.Error("Expected the master node to maintain cluster:/")
  55. }
  56. }
  57. func TestClusterIsMasterNodeWithoutCluster(t *testing.T) {
  58. previous := clusterManager
  59. defer func() { clusterManager = previous }()
  60. clusterManager = nil
  61. //A host with no cluster is the only node there is
  62. if !clusterIsMasterNode() {
  63. t.Error("Expected a host outside a cluster to count as the master node")
  64. }
  65. }
  66. // Copy ordering: the first question anyone opening the tab has is whether
  67. // the file is on the host they are using, and the second is which copies can
  68. // be read at all.
  69. func TestClusterOrderedLocations(t *testing.T) {
  70. rec := &metadata.FileRecord{Locations: []metadata.Location{
  71. {VolumeID: "v-far", NodeID: "node-c", State: metadata.LocFailed},
  72. {VolumeID: "v-ok", NodeID: "node-b", State: metadata.LocVerified},
  73. {VolumeID: "v-here", NodeID: "node-a", State: metadata.LocCommitted},
  74. {VolumeID: "v-stale", NodeID: "node-d", State: metadata.LocStale},
  75. }}
  76. got := []string{}
  77. for _, loc := range clusterOrderedLocations(rec, "node-a") {
  78. got = append(got, loc.VolumeID)
  79. }
  80. wanted := "v-here,v-ok,v-far,v-stale"
  81. if strings.Join(got, ",") != wanted {
  82. t.Errorf("Expected the copies in the order %q, got %q", wanted, strings.Join(got, ","))
  83. }
  84. if len(got) != len(rec.Locations) {
  85. t.Errorf("Expected all %d copies to be listed, got %d", len(rec.Locations), len(got))
  86. }
  87. }
  88. func TestClusterReplicaPolicyField(t *testing.T) {
  89. testcases := []struct {
  90. replicas int
  91. value string
  92. arg string
  93. }{
  94. {0, "{0} copy", "1"}, //a folder with no policy keeps one copy
  95. {1, "{0} copy", "1"},
  96. {3, "{0} copies", "3"},
  97. }
  98. for _, tc := range testcases {
  99. field := clusterReplicaPolicyField(metadata.Policy{Replicas: tc.replicas})
  100. if field.Value != tc.value || len(field.Args) != 1 || field.Args[0] != tc.arg {
  101. t.Errorf("Policy of %d replicas gave %q %v, wanted %q [%s]",
  102. tc.replicas, field.Value, field.Args, tc.value, tc.arg)
  103. }
  104. }
  105. }
  106. func TestClusterCopyCountField(t *testing.T) {
  107. //Nothing to point out while every copy can be read
  108. full := clusterCopyCountField(2, 2)
  109. if full.Value != "{0}" || full.Args[0] != "2" {
  110. t.Errorf("Expected a plain count, got %q %v", full.Value, full.Args)
  111. }
  112. //A copy that cannot be read is the whole point of the row
  113. partial := clusterCopyCountField(1, 3)
  114. if partial.Value != "{0} of {1} readable" || partial.Args[0] != "1" || partial.Args[1] != "3" {
  115. t.Errorf("Expected a readable count, got %q %v", partial.Value, partial.Args)
  116. }
  117. }
  118. // A node name is operator set and defaults to the same "My ArOZ" on every
  119. // host, so a path is only an answer when it names the node by its UUID:
  120. // <node uuid>:<drive uuid>/<path on that drive>.
  121. func TestClusterVolumePathText(t *testing.T) {
  122. vol := metadata.Volume{NodeID: "node-a-test", FshUUID: "user", Subpath: "/cluster"}
  123. if got := clusterVolumePathText(vol, "/docs/a.txt"); got != "node-a-test:user/cluster/docs/a.txt" {
  124. t.Errorf("Expected the path on the holding node, got %q", got)
  125. }
  126. if got := clusterVolumeRootText(vol); got != "node-a-test:user/cluster" {
  127. t.Errorf("Expected the volume root, got %q", got)
  128. }
  129. //A volume contributed at the root of its drive
  130. root := metadata.Volume{NodeID: "node-b-test", FshUUID: "s1", Subpath: ""}
  131. if got := clusterVolumePathText(root, "/a.txt"); got != "node-b-test:s1/a.txt" {
  132. t.Errorf("Expected a root volume path, got %q", got)
  133. }
  134. if got := clusterVolumeRootText(root); got != "node-b-test:s1/" {
  135. t.Errorf("Expected the root of a whole drive, got %q", got)
  136. }
  137. //Two nodes of the same name contributing the same folder stay apart
  138. twin := metadata.Volume{NodeID: "node-c-test", FshUUID: "user", Subpath: "/cluster"}
  139. if clusterVolumePathText(vol, "/docs/a.txt") == clusterVolumePathText(twin, "/docs/a.txt") {
  140. t.Error("Expected the same folder on two nodes to give two different paths")
  141. }
  142. }
  143. func TestClusterChecksumText(t *testing.T) {
  144. long := strings.Repeat("a", 64)
  145. if got := clusterChecksumText(long); len(got) != 27 || !strings.HasSuffix(got, "...") {
  146. t.Errorf("Expected a shortened hash, got %q", got)
  147. }
  148. if got := clusterChecksumText("abc"); got != "abc" {
  149. t.Errorf("Expected a short hash to be left alone, got %q", got)
  150. }
  151. if got := clusterChecksumText(""); got != "" {
  152. t.Errorf("Expected no text for a file with no hash, got %q", got)
  153. }
  154. }
  155. func TestClusterCopyStatusCoversEveryState(t *testing.T) {
  156. //A state with no entry would show up as Unknown in the dialog
  157. states := []string{metadata.LocPending, metadata.LocWriting, metadata.LocCommitted,
  158. metadata.LocVerified, metadata.LocStale, metadata.LocFailed}
  159. for _, state := range states {
  160. words, ok := clusterCopyStatus[state]
  161. if !ok {
  162. t.Errorf("No wording for the copy state %q", state)
  163. continue
  164. }
  165. switch words[1] {
  166. case arozfs.StorageStateOK, arozfs.StorageStatePending,
  167. arozfs.StorageStateStale, arozfs.StorageStateError:
  168. default:
  169. t.Errorf("Copy state %q maps to the unexpected display state %q", state, words[1])
  170. }
  171. }
  172. }