1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
22 "github.com/ghodss/yaml"
23 check "gopkg.in/check.v1"
26 type TestableUnixVolume struct {
31 func NewTestableUnixVolume(t TB, serialize bool, readonly bool) *TestableUnixVolume {
32 d, err := ioutil.TempDir("", "volume_test")
36 var locker sync.Locker
38 locker = &sync.Mutex{}
40 return &TestableUnixVolume{
41 UnixVolume: UnixVolume{
50 // PutRaw writes a Keep block directly into a UnixVolume, even if
51 // the volume is readonly.
52 func (v *TestableUnixVolume) PutRaw(locator string, data []byte) {
53 defer func(orig bool) {
57 err := v.Put(context.Background(), locator, data)
63 func (v *TestableUnixVolume) TouchWithDate(locator string, lastPut time.Time) {
64 err := syscall.Utime(v.blockPath(locator), &syscall.Utimbuf{lastPut.Unix(), lastPut.Unix()})
70 func (v *TestableUnixVolume) Teardown() {
71 if err := os.RemoveAll(v.Root); err != nil {
76 // serialize = false; readonly = false
77 func TestUnixVolumeWithGenericTests(t *testing.T) {
78 DoGenericVolumeTests(t, func(t TB) TestableVolume {
79 return NewTestableUnixVolume(t, false, false)
83 // serialize = false; readonly = true
84 func TestUnixVolumeWithGenericTestsReadOnly(t *testing.T) {
85 DoGenericVolumeTests(t, func(t TB) TestableVolume {
86 return NewTestableUnixVolume(t, false, true)
90 // serialize = true; readonly = false
91 func TestUnixVolumeWithGenericTestsSerialized(t *testing.T) {
92 DoGenericVolumeTests(t, func(t TB) TestableVolume {
93 return NewTestableUnixVolume(t, true, false)
97 // serialize = false; readonly = false
98 func TestUnixVolumeHandlersWithGenericVolumeTests(t *testing.T) {
99 DoHandlersWithGenericVolumeTests(t, func(t TB) (*RRVolumeManager, []TestableVolume) {
100 vols := make([]Volume, 2)
101 testableUnixVols := make([]TestableVolume, 2)
103 for i := range vols {
104 v := NewTestableUnixVolume(t, false, false)
106 testableUnixVols[i] = v
109 return MakeRRVolumeManager(vols), testableUnixVols
113 func TestReplicationDefault1(t *testing.T) {
118 if err := v.Start(); err != nil {
121 if got := v.Replication(); got != 1 {
122 t.Errorf("Replication() returned %d, expected 1 if no config given", got)
126 func TestGetNotFound(t *testing.T) {
127 v := NewTestableUnixVolume(t, false, false)
129 v.Put(context.Background(), TestHash, TestBlock)
131 buf := make([]byte, BlockSize)
132 n, err := v.Get(context.Background(), TestHash2, buf)
134 case os.IsNotExist(err):
137 t.Errorf("Read should have failed, returned %+q", buf[:n])
139 t.Errorf("Read expected ErrNotExist, got: %s", err)
143 func TestPut(t *testing.T) {
144 v := NewTestableUnixVolume(t, false, false)
147 err := v.Put(context.Background(), TestHash, TestBlock)
151 p := fmt.Sprintf("%s/%s/%s", v.Root, TestHash[:3], TestHash)
152 if buf, err := ioutil.ReadFile(p); err != nil {
154 } else if bytes.Compare(buf, TestBlock) != 0 {
155 t.Errorf("Write should have stored %s, did store %s",
156 string(TestBlock), string(buf))
160 func TestPutBadVolume(t *testing.T) {
161 v := NewTestableUnixVolume(t, false, false)
164 os.Chmod(v.Root, 000)
165 err := v.Put(context.Background(), TestHash, TestBlock)
167 t.Error("Write should have failed")
171 func TestUnixVolumeReadonly(t *testing.T) {
172 v := NewTestableUnixVolume(t, false, true)
175 v.PutRaw(TestHash, TestBlock)
177 buf := make([]byte, BlockSize)
178 _, err := v.Get(context.Background(), TestHash, buf)
180 t.Errorf("got err %v, expected nil", err)
183 err = v.Put(context.Background(), TestHash, TestBlock)
184 if err != MethodDisabledError {
185 t.Errorf("got err %v, expected MethodDisabledError", err)
188 err = v.Touch(TestHash)
189 if err != MethodDisabledError {
190 t.Errorf("got err %v, expected MethodDisabledError", err)
193 err = v.Trash(TestHash)
194 if err != MethodDisabledError {
195 t.Errorf("got err %v, expected MethodDisabledError", err)
199 func TestIsFull(t *testing.T) {
200 v := NewTestableUnixVolume(t, false, false)
203 fullPath := v.Root + "/full"
204 now := fmt.Sprintf("%d", time.Now().Unix())
205 os.Symlink(now, fullPath)
207 t.Errorf("%s: claims not to be full", v)
211 // Test with an expired /full link.
212 expired := fmt.Sprintf("%d", time.Now().Unix()-3605)
213 os.Symlink(expired, fullPath)
215 t.Errorf("%s: should no longer be full", v)
219 func TestNodeStatus(t *testing.T) {
220 v := NewTestableUnixVolume(t, false, false)
223 // Get node status and make a basic sanity check.
224 volinfo := v.Status()
225 if volinfo.MountPoint != v.Root {
226 t.Errorf("GetNodeStatus mount_point %s, expected %s", volinfo.MountPoint, v.Root)
228 if volinfo.DeviceNum == 0 {
229 t.Errorf("uninitialized device_num in %v", volinfo)
231 if volinfo.BytesFree == 0 {
232 t.Errorf("uninitialized bytes_free in %v", volinfo)
234 if volinfo.BytesUsed == 0 {
235 t.Errorf("uninitialized bytes_used in %v", volinfo)
239 func TestUnixVolumeGetFuncWorkerError(t *testing.T) {
240 v := NewTestableUnixVolume(t, false, false)
243 v.Put(context.Background(), TestHash, TestBlock)
244 mockErr := errors.New("Mock error")
245 err := v.getFunc(context.Background(), v.blockPath(TestHash), func(rdr io.Reader) error {
249 t.Errorf("Got %v, expected %v", err, mockErr)
253 func TestUnixVolumeGetFuncFileError(t *testing.T) {
254 v := NewTestableUnixVolume(t, false, false)
258 err := v.getFunc(context.Background(), v.blockPath(TestHash), func(rdr io.Reader) error {
263 t.Errorf("Expected error opening non-existent file")
266 t.Errorf("Worker func should not have been called")
270 func TestUnixVolumeGetFuncWorkerWaitsOnMutex(t *testing.T) {
271 v := NewTestableUnixVolume(t, false, false)
274 v.Put(context.Background(), TestHash, TestBlock)
276 mtx := NewMockMutex()
279 funcCalled := make(chan struct{})
280 go v.getFunc(context.Background(), v.blockPath(TestHash), func(rdr io.Reader) error {
281 funcCalled <- struct{}{}
285 case mtx.AllowLock <- struct{}{}:
287 t.Fatal("Function was called before mutex was acquired")
288 case <-time.After(5 * time.Second):
289 t.Fatal("Timed out before mutex was acquired")
293 case mtx.AllowUnlock <- struct{}{}:
294 t.Fatal("Mutex was released before function was called")
295 case <-time.After(5 * time.Second):
296 t.Fatal("Timed out waiting for funcCalled")
299 case mtx.AllowUnlock <- struct{}{}:
300 case <-time.After(5 * time.Second):
301 t.Fatal("Timed out waiting for getFunc() to release mutex")
305 func TestUnixVolumeCompare(t *testing.T) {
306 v := NewTestableUnixVolume(t, false, false)
309 v.Put(context.Background(), TestHash, TestBlock)
310 err := v.Compare(context.Background(), TestHash, TestBlock)
312 t.Errorf("Got err %q, expected nil", err)
315 err = v.Compare(context.Background(), TestHash, []byte("baddata"))
316 if err != CollisionError {
317 t.Errorf("Got err %q, expected %q", err, CollisionError)
320 v.Put(context.Background(), TestHash, []byte("baddata"))
321 err = v.Compare(context.Background(), TestHash, TestBlock)
322 if err != DiskHashError {
323 t.Errorf("Got err %q, expected %q", err, DiskHashError)
326 p := fmt.Sprintf("%s/%s/%s", v.Root, TestHash[:3], TestHash)
328 err = v.Compare(context.Background(), TestHash, TestBlock)
329 if err == nil || strings.Index(err.Error(), "permission denied") < 0 {
330 t.Errorf("Got err %q, expected %q", err, "permission denied")
334 func TestUnixVolumeContextCancelPut(t *testing.T) {
335 v := NewTestableUnixVolume(t, true, false)
338 ctx, cancel := context.WithCancel(context.Background())
340 time.Sleep(50 * time.Millisecond)
342 time.Sleep(50 * time.Millisecond)
345 err := v.Put(ctx, TestHash, TestBlock)
346 if err != context.Canceled {
347 t.Errorf("Put() returned %s -- expected short read / canceled", err)
351 func TestUnixVolumeContextCancelGet(t *testing.T) {
352 v := NewTestableUnixVolume(t, false, false)
354 bpath := v.blockPath(TestHash)
355 v.PutRaw(TestHash, TestBlock)
357 err := syscall.Mkfifo(bpath, 0600)
359 t.Fatalf("Mkfifo %s: %s", bpath, err)
361 defer os.Remove(bpath)
362 ctx, cancel := context.WithCancel(context.Background())
364 time.Sleep(50 * time.Millisecond)
367 buf := make([]byte, len(TestBlock))
368 n, err := v.Get(ctx, TestHash, buf)
369 if n == len(TestBlock) || err != context.Canceled {
370 t.Errorf("Get() returned %d, %s -- expected short read / canceled", n, err)
374 var _ = check.Suite(&UnixVolumeSuite{})
376 type UnixVolumeSuite struct {
377 volume *TestableUnixVolume
380 func (s *UnixVolumeSuite) TearDownTest(c *check.C) {
386 func (s *UnixVolumeSuite) TestStats(c *check.C) {
387 s.volume = NewTestableUnixVolume(c, false, false)
388 stats := func() string {
389 buf, err := json.Marshal(s.volume.InternalStats())
390 c.Check(err, check.IsNil)
394 c.Check(stats(), check.Matches, `.*"StatOps":0,.*`)
395 c.Check(stats(), check.Matches, `.*"Errors":0,.*`)
397 loc := "acbd18db4cc2f85cedef654fccc4a4d8"
398 _, err := s.volume.Get(context.Background(), loc, make([]byte, 3))
399 c.Check(err, check.NotNil)
400 c.Check(stats(), check.Matches, `.*"StatOps":[^0],.*`)
401 c.Check(stats(), check.Matches, `.*"Errors":[^0],.*`)
402 c.Check(stats(), check.Matches, `.*"\*os\.PathError":[^0].*`)
403 c.Check(stats(), check.Matches, `.*"InBytes":0,.*`)
404 c.Check(stats(), check.Matches, `.*"OpenOps":0,.*`)
405 c.Check(stats(), check.Matches, `.*"CreateOps":0,.*`)
407 err = s.volume.Put(context.Background(), loc, []byte("foo"))
408 c.Check(err, check.IsNil)
409 c.Check(stats(), check.Matches, `.*"OutBytes":3,.*`)
410 c.Check(stats(), check.Matches, `.*"CreateOps":1,.*`)
411 c.Check(stats(), check.Matches, `.*"OpenOps":0,.*`)
412 c.Check(stats(), check.Matches, `.*"UtimesOps":0,.*`)
414 err = s.volume.Touch(loc)
415 c.Check(err, check.IsNil)
416 c.Check(stats(), check.Matches, `.*"FlockOps":1,.*`)
417 c.Check(stats(), check.Matches, `.*"OpenOps":1,.*`)
418 c.Check(stats(), check.Matches, `.*"UtimesOps":1,.*`)
420 _, err = s.volume.Get(context.Background(), loc, make([]byte, 3))
421 c.Check(err, check.IsNil)
422 err = s.volume.Compare(context.Background(), loc, []byte("foo"))
423 c.Check(err, check.IsNil)
424 c.Check(stats(), check.Matches, `.*"InBytes":6,.*`)
425 c.Check(stats(), check.Matches, `.*"OpenOps":3,.*`)
427 err = s.volume.Trash(loc)
428 c.Check(err, check.IsNil)
429 c.Check(stats(), check.Matches, `.*"FlockOps":2,.*`)
432 func (s *UnixVolumeSuite) TestConfig(c *check.C) {
434 err := yaml.Unmarshal([]byte(`
437 StorageClasses: ["class_a", "class_b"]
440 c.Check(err, check.IsNil)
441 c.Check(cfg.Volumes[0].GetStorageClasses(), check.DeepEquals, []string{"class_a", "class_b"})