1// Copyright 2015 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package wal
16
17import (
18	"fmt"
19	"io"
20	"io/ioutil"
21	"os"
22	"testing"
23
24	"github.com/coreos/etcd/raft/raftpb"
25	"github.com/coreos/etcd/wal/walpb"
26)
27
28type corruptFunc func(string, int64) error
29
30// TestRepairTruncate ensures a truncated file can be repaired
31func TestRepairTruncate(t *testing.T) {
32	corruptf := func(p string, offset int64) error {
33		f, err := openLast(p)
34		if err != nil {
35			return err
36		}
37		defer f.Close()
38		return f.Truncate(offset - 4)
39	}
40
41	testRepair(t, makeEnts(10), corruptf, 9)
42}
43
44func testRepair(t *testing.T, ents [][]raftpb.Entry, corrupt corruptFunc, expectedEnts int) {
45	p, err := ioutil.TempDir(os.TempDir(), "waltest")
46	if err != nil {
47		t.Fatal(err)
48	}
49	defer os.RemoveAll(p)
50	// create WAL
51	w, err := Create(p, nil)
52	defer func() {
53		if err = w.Close(); err != nil {
54			t.Fatal(err)
55		}
56	}()
57	if err != nil {
58		t.Fatal(err)
59	}
60
61	for _, es := range ents {
62		if err = w.Save(raftpb.HardState{}, es); err != nil {
63			t.Fatal(err)
64		}
65	}
66
67	offset, err := w.tail().Seek(0, io.SeekCurrent)
68	if err != nil {
69		t.Fatal(err)
70	}
71	w.Close()
72
73	err = corrupt(p, offset)
74	if err != nil {
75		t.Fatal(err)
76	}
77
78	// verify we broke the wal
79	w, err = Open(p, walpb.Snapshot{})
80	if err != nil {
81		t.Fatal(err)
82	}
83	_, _, _, err = w.ReadAll()
84	if err != io.ErrUnexpectedEOF {
85		t.Fatalf("err = %v, want error %v", err, io.ErrUnexpectedEOF)
86	}
87	w.Close()
88
89	// repair the wal
90	if ok := Repair(p); !ok {
91		t.Fatalf("fix = %t, want %t", ok, true)
92	}
93
94	// read it back
95	w, err = Open(p, walpb.Snapshot{})
96	if err != nil {
97		t.Fatal(err)
98	}
99	_, _, walEnts, err := w.ReadAll()
100	if err != nil {
101		t.Fatal(err)
102	}
103	if len(walEnts) != expectedEnts {
104		t.Fatalf("len(ents) = %d, want %d", len(walEnts), expectedEnts)
105	}
106
107	// write some more entries to repaired log
108	for i := 1; i <= 10; i++ {
109		es := []raftpb.Entry{{Index: uint64(expectedEnts + i)}}
110		if err = w.Save(raftpb.HardState{}, es); err != nil {
111			t.Fatal(err)
112		}
113	}
114	w.Close()
115
116	// read back entries following repair, ensure it's all there
117	w, err = Open(p, walpb.Snapshot{})
118	if err != nil {
119		t.Fatal(err)
120	}
121	_, _, walEnts, err = w.ReadAll()
122	if err != nil {
123		t.Fatal(err)
124	}
125	if len(walEnts) != expectedEnts+10 {
126		t.Fatalf("len(ents) = %d, want %d", len(walEnts), expectedEnts+10)
127	}
128}
129
130func makeEnts(ents int) (ret [][]raftpb.Entry) {
131	for i := 1; i <= ents; i++ {
132		ret = append(ret, []raftpb.Entry{{Index: uint64(i)}})
133	}
134	return ret
135}
136
137// TestRepairWriteTearLast repairs the WAL in case the last record is a torn write
138// that straddled two sectors.
139func TestRepairWriteTearLast(t *testing.T) {
140	corruptf := func(p string, offset int64) error {
141		f, err := openLast(p)
142		if err != nil {
143			return err
144		}
145		defer f.Close()
146		// 512 bytes perfectly aligns the last record, so use 1024
147		if offset < 1024 {
148			return fmt.Errorf("got offset %d, expected >1024", offset)
149		}
150		if terr := f.Truncate(1024); terr != nil {
151			return terr
152		}
153		if terr := f.Truncate(offset); terr != nil {
154			return terr
155		}
156		return nil
157	}
158	testRepair(t, makeEnts(50), corruptf, 40)
159}
160
161// TestRepairWriteTearMiddle repairs the WAL when there is write tearing
162// in the middle of a record.
163func TestRepairWriteTearMiddle(t *testing.T) {
164	corruptf := func(p string, offset int64) error {
165		f, err := openLast(p)
166		if err != nil {
167			return err
168		}
169		defer f.Close()
170		// corrupt middle of 2nd record
171		_, werr := f.WriteAt(make([]byte, 512), 4096+512)
172		return werr
173	}
174	ents := makeEnts(5)
175	// 4096 bytes of data so a middle sector is easy to corrupt
176	dat := make([]byte, 4096)
177	for i := range dat {
178		dat[i] = byte(i)
179	}
180	for i := range ents {
181		ents[i][0].Data = dat
182	}
183	testRepair(t, ents, corruptf, 1)
184}
185