Dragonfly2
307 строк · 7.7 Кб
1/*
2* Copyright 2020 The Dragonfly Authors
3*
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7*
8* http://www.apache.org/licenses/LICENSE-2.0
9*
10* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
15*/
16
17package peer
18
19import (
20"context"
21"crypto/md5"
22"encoding/hex"
23"errors"
24"fmt"
25"io"
26"math"
27"net/http"
28"net/http/httptest"
29"net/url"
30"os"
31"testing"
32"time"
33
34"github.com/go-http-utils/headers"
35testifyassert "github.com/stretchr/testify/assert"
36"github.com/stretchr/testify/require"
37"google.golang.org/grpc/status"
38
39commonv1 "d7y.io/api/v2/pkg/apis/common/v1"
40
41"d7y.io/dragonfly/v2/client/daemon/test"
42logger "d7y.io/dragonfly/v2/internal/dflog"
43nethttp "d7y.io/dragonfly/v2/pkg/net/http"
44"d7y.io/dragonfly/v2/pkg/source"
45"d7y.io/dragonfly/v2/pkg/source/clients/httpprotocol"
46)
47
48func TestPieceDownloader_isConnectionError(t *testing.T) {
49tests := []struct {
50name string
51err error
52expect bool
53}{
54{
55name: "connection error",
56err: &pieceDownloadError{
57connectionError: true,
58},
59expect: true,
60},
61{
62name: "connection ok",
63err: &pieceDownloadError{
64connectionError: false,
65},
66expect: false,
67},
68{
69name: "no error",
70err: nil,
71expect: false,
72},
73}
74
75for _, tc := range tests {
76t.Run(tc.name, func(t *testing.T) {
77assert := testifyassert.New(t)
78assert.Equal(tc.expect, isConnectionError(tc.err))
79})
80}
81}
82
83func TestPieceDownloader_isPieceNotFound(t *testing.T) {
84tests := []struct {
85name string
86err error
87expect bool
88}{
89{
90name: "piece not found",
91err: &pieceDownloadError{
92statusCode: http.StatusNotFound,
93},
94expect: true,
95},
96{
97name: "piece found",
98err: &pieceDownloadError{},
99expect: false,
100},
101{
102name: "no error",
103err: nil,
104expect: false,
105},
106}
107
108for _, tc := range tests {
109t.Run(tc.name, func(t *testing.T) {
110assert := testifyassert.New(t)
111assert.Equal(tc.expect, isPieceNotFound(tc.err))
112})
113}
114}
115
116func TestPieceDownloader_isBackSourceError(t *testing.T) {
117tests := []struct {
118name string
119err error
120expect bool
121}{
122{
123name: "back source error",
124err: &backSourceError{},
125expect: true,
126},
127{
128name: "unexpected status code error",
129err: &source.UnexpectedStatusCodeError{},
130expect: true,
131},
132{
133name: "no error",
134err: nil,
135expect: false,
136},
137}
138
139for _, tc := range tests {
140t.Run(tc.name, func(t *testing.T) {
141assert := testifyassert.New(t)
142assert.Equal(tc.expect, isBackSourceError(tc.err))
143})
144}
145}
146
147func TestPieceDownloader_Error(t *testing.T) {
148tests := []struct {
149name string
150err error
151expect func(t *testing.T, errMsg string)
152}{
153{
154name: "no request URL",
155err: &pieceDownloadError{
156connectionError: true,
157target: "",
158err: errors.New("http: nil Request.URL"),
159},
160expect: func(t *testing.T, errMsg string) {
161assert := testifyassert.New(t)
162assert.Equal("connect with with error: http: nil Request.URL", errMsg)
163},
164},
165{
166name: "request URL not found",
167err: &pieceDownloadError{
168connectionError: false,
169target: "http://www.x.yy",
170status: "404 Not Found",
171},
172expect: func(t *testing.T, errMsg string) {
173assert := testifyassert.New(t)
174assert.Equal("download http://www.x.yy with error status: 404 Not Found", errMsg)
175},
176},
177{
178name: "resource not found",
179err: &backSourceError{
180st: status.New(5, "Resource not found."),
181},
182expect: func(t *testing.T, errMsg string) {
183assert := testifyassert.New(t)
184assert.Equal("rpc error: code = NotFound desc = Resource not found.", errMsg)
185},
186},
187}
188
189for _, tc := range tests {
190t.Run(tc.name, func(t *testing.T) {
191errMsg := tc.err.Error()
192tc.expect(t, errMsg)
193})
194}
195}
196
197func TestPieceDownloader_DownloadPiece(t *testing.T) {
198assert := testifyassert.New(t)
199source.UnRegister("http")
200require.Nil(t, source.Register("http", httpprotocol.NewHTTPSourceClient(), httpprotocol.Adapter))
201defer source.UnRegister("http")
202testData, err := os.ReadFile(test.File)
203assert.Nil(err, "load test file")
204pieceDownloadTimeout := 30 * time.Second
205
206tests := []struct {
207handleFunc func(w http.ResponseWriter, r *http.Request)
208taskID string
209pieceRange string
210rangeStart uint64
211rangeSize uint32
212targetPieceData []byte
213}{
214{
215handleFunc: func(w http.ResponseWriter, r *http.Request) {
216assert.Equal("/download/tas/task-0", r.URL.Path)
217data := []byte("test test ")
218w.Header().Set(headers.ContentLength, fmt.Sprintf("%d", len(data)))
219if _, err := w.Write(data); err != nil {
220t.Error(err)
221}
222},
223taskID: "task-0",
224pieceRange: "bytes=0-9",
225rangeStart: 0,
226rangeSize: 10,
227targetPieceData: []byte("test test "),
228},
229{
230handleFunc: func(w http.ResponseWriter, r *http.Request) {
231assert.Equal("/download/tas/task-1", r.URL.Path)
232rg := nethttp.MustParseRange(r.Header.Get("Range"), math.MaxInt64)
233w.Header().Set(headers.ContentLength, fmt.Sprintf("%d", rg.Length))
234if _, err := w.Write(testData[rg.Start : rg.Start+rg.Length]); err != nil {
235t.Error(err)
236}
237},
238taskID: "task-1",
239pieceRange: "bytes=0-99",
240rangeStart: 0,
241rangeSize: 100,
242targetPieceData: testData[:100],
243},
244{
245handleFunc: func(w http.ResponseWriter, r *http.Request) {
246assert.Equal("/download/tas/task-2", r.URL.Path)
247rg := nethttp.MustParseRange(r.Header.Get("Range"), math.MaxInt64)
248w.Header().Set(headers.ContentLength, fmt.Sprintf("%d", rg.Length))
249if _, err := w.Write(testData[rg.Start : rg.Start+rg.Length]); err != nil {
250t.Error(err)
251}
252},
253taskID: "task-2",
254pieceRange: fmt.Sprintf("bytes=512-%d", len(testData)-1),
255rangeStart: 512,
256rangeSize: uint32(len(testData) - 512),
257targetPieceData: testData[512:],
258},
259{
260handleFunc: func(w http.ResponseWriter, r *http.Request) {
261assert.Equal("/download/tas/task-3", r.URL.Path)
262rg := nethttp.MustParseRange(r.Header.Get("Range"), math.MaxInt64)
263w.Header().Set(headers.ContentLength, fmt.Sprintf("%d", rg.Length))
264if _, err := w.Write(testData[rg.Start : rg.Start+rg.Length]); err != nil {
265t.Error(err)
266}
267},
268taskID: "task-3",
269pieceRange: "bytes=512-1024",
270rangeStart: 512,
271rangeSize: 513,
272targetPieceData: testData[512:1025],
273},
274}
275
276for _, tt := range tests {
277server := httptest.NewServer(http.HandlerFunc(tt.handleFunc))
278addr, _ := url.Parse(server.URL)
279pd := NewPieceDownloader(pieceDownloadTimeout, nil)
280hash := md5.New()
281hash.Write(tt.targetPieceData)
282digest := hex.EncodeToString(hash.Sum(nil)[:16])
283r, c, err := pd.DownloadPiece(context.Background(), &DownloadPieceRequest{
284TaskID: tt.taskID,
285DstPid: "",
286DstAddr: addr.Host,
287CalcDigest: true,
288piece: &commonv1.PieceInfo{
289PieceNum: 0,
290RangeStart: tt.rangeStart,
291RangeSize: tt.rangeSize,
292PieceMd5: digest,
293PieceOffset: tt.rangeStart,
294PieceStyle: commonv1.PieceStyle_PLAIN,
295},
296log: logger.With("test", "test"),
297})
298assert.Nil(err, "downloaded piece should success")
299
300data, err := io.ReadAll(r)
301assert.Nil(err, "read piece data should success")
302c.Close()
303
304assert.Equal(data, tt.targetPieceData, "downloaded piece data should match")
305server.Close()
306}
307}
308