/* * Copyright 2020 The Dragonfly Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rpcserver import ( "context" "fmt" "math" "net" "os" "github.com/pkg/errors" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "d7y.io/dragonfly/v2/client/clientutil" "d7y.io/dragonfly/v2/client/daemon/peer" "d7y.io/dragonfly/v2/client/daemon/storage" "d7y.io/dragonfly/v2/internal/dferrors" logger "d7y.io/dragonfly/v2/internal/dflog" "d7y.io/dragonfly/v2/pkg/idgen" "d7y.io/dragonfly/v2/pkg/rpc/base" dfdaemongrpc "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon" dfdaemonserver "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/server" "d7y.io/dragonfly/v2/pkg/rpc/scheduler" "d7y.io/dragonfly/v2/pkg/util/rangeutils" ) type Server interface { clientutil.KeepAlive ServeDownload(listener net.Listener) error ServePeer(listener net.Listener) error Stop() } type server struct { clientutil.KeepAlive peerHost *scheduler.PeerHost peerTaskManager peer.TaskManager storageManager storage.Manager downloadServer *grpc.Server peerServer *grpc.Server uploadAddr string } func New(peerHost *scheduler.PeerHost, peerTaskManager peer.TaskManager, storageManager storage.Manager, downloadOpts []grpc.ServerOption, peerOpts []grpc.ServerOption) (Server, error) { svr := &server{ KeepAlive: clientutil.NewKeepAlive("rpc server"), peerHost: peerHost, peerTaskManager: peerTaskManager, storageManager: storageManager, } svr.downloadServer = dfdaemonserver.New(svr, downloadOpts...) svr.peerServer = dfdaemonserver.New(svr, peerOpts...) return svr, nil } func (s *server) ServeDownload(listener net.Listener) error { return s.downloadServer.Serve(listener) } func (s *server) ServePeer(listener net.Listener) error { s.uploadAddr = fmt.Sprintf("%s:%d", s.peerHost.Ip, s.peerHost.DownPort) return s.peerServer.Serve(listener) } func (s *server) Stop() { s.peerServer.GracefulStop() s.downloadServer.GracefulStop() } func (s *server) GetPieceTasks(ctx context.Context, request *base.PieceTaskRequest) (*base.PiecePacket, error) { s.Keep() p, err := s.storageManager.GetPieces(ctx, request) if err != nil { code := base.Code_UnknownError if err == dferrors.ErrInvalidArgument { code = base.Code_BadRequest } if err != storage.ErrTaskNotFound { logger.Errorf("get piece tasks error: %s, task id: %s, src peer: %s, dst peer: %s, piece num: %d, limit: %d", err, request.TaskId, request.SrcPid, request.DstPid, request.StartNum, request.Limit) return nil, dferrors.New(code, err.Error()) } // dst peer is not running if !s.peerTaskManager.IsPeerTaskRunning(request.TaskId) { code = base.Code_PeerTaskNotFound logger.Errorf("get piece tasks error: peer task not found, task id: %s, src peer: %s, dst peer: %s, piece num: %d, limit: %d", request.TaskId, request.SrcPid, request.DstPid, request.StartNum, request.Limit) return nil, dferrors.New(code, err.Error()) } logger.Infof("try to get piece tasks, "+ "but target peer task is initializing, "+ "there is no available pieces, "+ "task id: %s, src peer: %s, dst peer: %s, piece num: %d, limit: %d", request.TaskId, request.SrcPid, request.DstPid, request.StartNum, request.Limit) // dst peer is running, send empty result, src peer will retry later return &base.PiecePacket{ TaskId: request.TaskId, DstPid: request.DstPid, DstAddr: s.uploadAddr, PieceInfos: nil, TotalPiece: -1, ContentLength: -1, PieceMd5Sign: "", }, nil } logger.Debugf("receive get piece tasks request, task id: %s, src peer: %s, dst peer: %s, piece num: %d, limit: %d, length: %d", request.TaskId, request.SrcPid, request.DstPid, request.StartNum, request.Limit, len(p.PieceInfos)) p.DstAddr = s.uploadAddr return p, nil } // sendExistPieces will send as much as possible pieces func (s *server) sendExistPieces(request *base.PieceTaskRequest, sync dfdaemongrpc.Daemon_SyncPieceTasksServer, sentMap map[int32]struct{}) (total int32, sent int, err error) { return sendExistPieces(sync.Context(), s.GetPieceTasks, request, sync, sentMap) } func (s *server) SyncPieceTasks(sync dfdaemongrpc.Daemon_SyncPieceTasksServer) error { request, err := sync.Recv() if err != nil { return err } skipPieceCount := request.StartNum var sentMap = make(map[int32]struct{}) total, sent, err := s.sendExistPieces(request, sync, sentMap) if err != nil { return err } // task is done, just return if int(total) == sent { return nil } // subscribe peer task message for remaining pieces result, ok := s.peerTaskManager.Subscribe(request) if !ok { // task not found, double check for done task total, sent, err = s.sendExistPieces(request, sync, sentMap) if err != nil { return err } if int(total) > sent { return status.Errorf(codes.Unavailable, "peer task not finish, but no running task found") } return nil } var sub = &subscriber{ SubscribeResult: result, sync: sync, request: request, skipPieceCount: skipPieceCount, totalPieces: total, sentMap: sentMap, done: make(chan struct{}), uploadAddr: s.uploadAddr, SugaredLoggerOnWith: logger.With("taskID", request.TaskId, "localPeerID", request.DstPid, "remotePeerID", request.SrcPid), } go sub.receiveRemainingPieceTaskRequests() return sub.sendRemainingPieceTasks() } func (s *server) CheckHealth(context.Context) error { s.Keep() return nil } func (s *server) Download(ctx context.Context, req *dfdaemongrpc.DownRequest, results chan<- *dfdaemongrpc.DownResult) error { s.Keep() if req.UrlMeta == nil { req.UrlMeta = &base.UrlMeta{} } // init peer task request, peer uses different peer id to generate every request peerTask := &peer.FileTaskRequest{ PeerTaskRequest: scheduler.PeerTaskRequest{ Url: req.Url, UrlMeta: req.UrlMeta, PeerId: idgen.PeerID(s.peerHost.Ip), PeerHost: s.peerHost, }, Output: req.Output, Limit: req.Limit, DisableBackSource: req.DisableBackSource, Pattern: req.Pattern, Callsystem: req.Callsystem, KeepOriginalOffset: req.KeepOriginalOffset, } if len(req.UrlMeta.Range) > 0 { r, err := rangeutils.ParseRange(req.UrlMeta.Range, math.MaxInt) if err != nil { err = fmt.Errorf("parse range %s error: %s", req.UrlMeta.Range, err) return err } peerTask.Range = &clientutil.Range{ Start: int64(r.StartIndex), Length: int64(r.Length()), } } log := logger.With("peer", peerTask.PeerId, "component", "downloadService") peerTaskProgress, tiny, err := s.peerTaskManager.StartFileTask(ctx, peerTask) if err != nil { return dferrors.New(base.Code_UnknownError, fmt.Sprintf("%s", err)) } if tiny != nil { results <- &dfdaemongrpc.DownResult{ TaskId: tiny.TaskID, PeerId: tiny.PeerID, CompletedLength: uint64(len(tiny.Content)), Done: true, } log.Infof("tiny file, wrote to output") if req.Uid != 0 && req.Gid != 0 { if err = os.Chown(req.Output, int(req.Uid), int(req.Gid)); err != nil { log.Errorf("change own failed: %s", err) return err } } return nil } for { select { case p, ok := <-peerTaskProgress: if !ok { err = errors.New("progress closed unexpected") log.Errorf(err.Error()) return dferrors.New(base.Code_UnknownError, err.Error()) } if !p.State.Success { log.Errorf("task %s/%s failed: %d/%s", p.PeerID, p.TaskID, p.State.Code, p.State.Msg) return dferrors.New(p.State.Code, p.State.Msg) } results <- &dfdaemongrpc.DownResult{ TaskId: p.TaskID, PeerId: p.PeerID, CompletedLength: uint64(p.CompletedLength), Done: p.PeerTaskDone, } // peer task sets PeerTaskDone to true only once if p.PeerTaskDone { p.DoneCallback() log.Infof("task %s/%s done", p.PeerID, p.TaskID) if req.Uid != 0 && req.Gid != 0 { log.Infof("change own to uid %d gid %d", req.Uid, req.Gid) if err = os.Chown(req.Output, int(req.Uid), int(req.Gid)); err != nil { log.Errorf("change own failed: %s", err) return err } } return nil } case <-ctx.Done(): results <- &dfdaemongrpc.DownResult{ CompletedLength: 0, Done: true, } log.Infof("context done due to %s", ctx.Err()) return status.Error(codes.Canceled, ctx.Err().Error()) } } }