github.com/cs3org/reva/v2@v2.27.7/internal/grpc/services/datatx/datatx.go (about)

     1  // Copyright 2018-2021 CERN
     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  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package datatx
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"fmt"
    25  	"io"
    26  	"net/url"
    27  	"os"
    28  	"sync"
    29  
    30  	ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
    31  	datatx "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1"
    32  	types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    33  	txdriver "github.com/cs3org/reva/v2/pkg/datatx"
    34  	txregistry "github.com/cs3org/reva/v2/pkg/datatx/manager/registry"
    35  	"github.com/cs3org/reva/v2/pkg/errtypes"
    36  	"github.com/cs3org/reva/v2/pkg/rgrpc"
    37  	"github.com/cs3org/reva/v2/pkg/rgrpc/status"
    38  	"github.com/mitchellh/mapstructure"
    39  	"github.com/pkg/errors"
    40  	"github.com/rs/zerolog"
    41  	"google.golang.org/grpc"
    42  )
    43  
    44  func init() {
    45  	rgrpc.Register("datatx", New)
    46  }
    47  
    48  type config struct {
    49  	// transfer driver
    50  	TxDriver  string                            `mapstructure:"txdriver"`
    51  	TxDrivers map[string]map[string]interface{} `mapstructure:"txdrivers"`
    52  	// storage driver to persist share/transfer relation
    53  	StorageDriver       string                            `mapstructure:"storage_driver"`
    54  	StorageDrivers      map[string]map[string]interface{} `mapstructure:"storage_drivers"`
    55  	TxSharesFile        string                            `mapstructure:"tx_shares_file"`
    56  	DataTransfersFolder string                            `mapstructure:"data_transfers_folder"`
    57  }
    58  
    59  type service struct {
    60  	conf          *config
    61  	txManager     txdriver.Manager
    62  	txShareDriver *txShareDriver
    63  }
    64  
    65  type txShareDriver struct {
    66  	sync.Mutex // concurrent access to the file
    67  	model      *txShareModel
    68  }
    69  type txShareModel struct {
    70  	File     string
    71  	TxShares map[string]*txShare `json:"shares"`
    72  }
    73  
    74  type txShare struct {
    75  	TxID          string
    76  	SrcTargetURI  string
    77  	DestTargetURI string
    78  	Opaque        *types.Opaque `json:"opaque"`
    79  }
    80  
    81  type webdavEndpoint struct {
    82  	filePath       string
    83  	endpoint       string
    84  	endpointScheme string
    85  	token          string
    86  }
    87  
    88  func (c *config) init() {
    89  	if c.TxDriver == "" {
    90  		c.TxDriver = "rclone"
    91  	}
    92  	if c.TxSharesFile == "" {
    93  		c.TxSharesFile = "/var/tmp/reva/datatx-shares.json"
    94  	}
    95  	if c.DataTransfersFolder == "" {
    96  		c.DataTransfersFolder = "/home/DataTransfers"
    97  	}
    98  }
    99  
   100  func (s *service) Register(ss *grpc.Server) {
   101  	datatx.RegisterTxAPIServer(ss, s)
   102  }
   103  
   104  func getDatatxManager(c *config) (txdriver.Manager, error) {
   105  	if f, ok := txregistry.NewFuncs[c.TxDriver]; ok {
   106  		return f(c.TxDrivers[c.TxDriver])
   107  	}
   108  	return nil, errtypes.NotFound("datatx service: driver not found: " + c.TxDriver)
   109  }
   110  
   111  func parseConfig(m map[string]interface{}) (*config, error) {
   112  	c := &config{}
   113  	if err := mapstructure.Decode(m, c); err != nil {
   114  		err = errors.Wrap(err, "datatx service: error decoding conf")
   115  		return nil, err
   116  	}
   117  	return c, nil
   118  }
   119  
   120  // New creates a new datatx svc
   121  func New(m map[string]interface{}, ss *grpc.Server, _ *zerolog.Logger) (rgrpc.Service, error) {
   122  
   123  	c, err := parseConfig(m)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	c.init()
   128  
   129  	txManager, err := getDatatxManager(c)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	model, err := loadOrCreate(c.TxSharesFile)
   135  	if err != nil {
   136  		err = errors.Wrap(err, "datatx service: error loading the file containing the transfer shares")
   137  		return nil, err
   138  	}
   139  	txShareDriver := &txShareDriver{
   140  		model: model,
   141  	}
   142  
   143  	service := &service{
   144  		conf:          c,
   145  		txManager:     txManager,
   146  		txShareDriver: txShareDriver,
   147  	}
   148  
   149  	return service, nil
   150  }
   151  
   152  func (s *service) Close() error {
   153  	return nil
   154  }
   155  
   156  func (s *service) UnprotectedEndpoints() []string {
   157  	return []string{}
   158  }
   159  
   160  func (s *service) CreateTransfer(ctx context.Context, req *datatx.CreateTransferRequest) (*datatx.CreateTransferResponse, error) {
   161  	srcEp, err := s.extractEndpointInfo(ctx, req.SrcTargetUri)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	srcRemote := fmt.Sprintf("%s://%s", srcEp.endpointScheme, srcEp.endpoint)
   166  	srcPath := srcEp.filePath
   167  	srcToken := srcEp.token
   168  
   169  	destEp, err := s.extractEndpointInfo(ctx, req.DestTargetUri)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  	dstRemote := fmt.Sprintf("%s://%s", destEp.endpointScheme, destEp.endpoint)
   174  	dstPath := destEp.filePath
   175  	dstToken := destEp.token
   176  
   177  	txInfo, startTransferErr := s.txManager.StartTransfer(ctx, srcRemote, srcPath, srcToken, dstRemote, dstPath, dstToken)
   178  
   179  	// we always save the transfer regardless of start transfer outcome
   180  	// only then, if starting fails, can we try to restart it
   181  	txShare := &txShare{
   182  		TxID:          txInfo.GetId().OpaqueId,
   183  		SrcTargetURI:  req.SrcTargetUri,
   184  		DestTargetURI: req.DestTargetUri,
   185  		Opaque:        req.Opaque,
   186  	}
   187  	s.txShareDriver.Lock()
   188  	defer s.txShareDriver.Unlock()
   189  
   190  	s.txShareDriver.model.TxShares[txInfo.GetId().OpaqueId] = txShare
   191  	if err := s.txShareDriver.model.saveTxShare(); err != nil {
   192  		err = errors.Wrap(err, "datatx service: error saving transfer share: "+datatx.Status_STATUS_INVALID.String())
   193  		return &datatx.CreateTransferResponse{
   194  			Status: status.NewInvalid(ctx, "error pulling transfer"),
   195  		}, err
   196  	}
   197  
   198  	// now check start transfer outcome
   199  	if startTransferErr != nil {
   200  		startTransferErr = errors.Wrap(startTransferErr, "datatx service: error starting transfer job")
   201  		return &datatx.CreateTransferResponse{
   202  			Status: status.NewInvalid(ctx, "datatx service: error pulling transfer"),
   203  			TxInfo: txInfo,
   204  		}, startTransferErr
   205  	}
   206  
   207  	return &datatx.CreateTransferResponse{
   208  		Status: status.NewOK(ctx),
   209  		TxInfo: txInfo,
   210  	}, err
   211  }
   212  
   213  func (s *service) GetTransferStatus(ctx context.Context, req *datatx.GetTransferStatusRequest) (*datatx.GetTransferStatusResponse, error) {
   214  	txShare, ok := s.txShareDriver.model.TxShares[req.GetTxId().GetOpaqueId()]
   215  	if !ok {
   216  		return nil, errtypes.InternalError("datatx service: transfer not found")
   217  	}
   218  
   219  	txInfo, err := s.txManager.GetTransferStatus(ctx, req.GetTxId().OpaqueId)
   220  	if err != nil {
   221  		return &datatx.GetTransferStatusResponse{
   222  			Status: status.NewInternal(ctx, "datatx service: error getting transfer status"),
   223  			TxInfo: txInfo,
   224  		}, err
   225  	}
   226  
   227  	txInfo.ShareId = &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)}
   228  
   229  	return &datatx.GetTransferStatusResponse{
   230  		Status: status.NewOK(ctx),
   231  		TxInfo: txInfo,
   232  	}, nil
   233  }
   234  
   235  func (s *service) CancelTransfer(ctx context.Context, req *datatx.CancelTransferRequest) (*datatx.CancelTransferResponse, error) {
   236  	txShare, ok := s.txShareDriver.model.TxShares[req.GetTxId().GetOpaqueId()]
   237  	if !ok {
   238  		return nil, errtypes.InternalError("datatx service: transfer not found")
   239  	}
   240  
   241  	txInfo, err := s.txManager.CancelTransfer(ctx, req.GetTxId().OpaqueId)
   242  	if err != nil {
   243  		txInfo.ShareId = &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)}
   244  		return &datatx.CancelTransferResponse{
   245  			Status: status.NewInternal(ctx, "error cancelling transfer"),
   246  			TxInfo: txInfo,
   247  		}, err
   248  	}
   249  
   250  	txInfo.ShareId = &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)}
   251  
   252  	return &datatx.CancelTransferResponse{
   253  		Status: status.NewOK(ctx),
   254  		TxInfo: txInfo,
   255  	}, nil
   256  }
   257  
   258  func (s *service) ListTransfers(ctx context.Context, req *datatx.ListTransfersRequest) (*datatx.ListTransfersResponse, error) {
   259  	filters := req.Filters
   260  	var txInfos []*datatx.TxInfo
   261  	for _, txShare := range s.txShareDriver.model.TxShares {
   262  		if len(filters) == 0 {
   263  			txInfos = append(txInfos, &datatx.TxInfo{
   264  				Id:      &datatx.TxId{OpaqueId: txShare.TxID},
   265  				ShareId: &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)},
   266  			})
   267  		} else {
   268  			for _, f := range filters {
   269  				if f.Type == datatx.ListTransfersRequest_Filter_TYPE_SHARE_ID {
   270  					if f.GetShareId().GetOpaqueId() == string(txShare.Opaque.Map["shareId"].Value) {
   271  						txInfos = append(txInfos, &datatx.TxInfo{
   272  							Id:      &datatx.TxId{OpaqueId: txShare.TxID},
   273  							ShareId: &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)},
   274  						})
   275  					}
   276  				}
   277  			}
   278  		}
   279  	}
   280  
   281  	return &datatx.ListTransfersResponse{
   282  		Status:    status.NewOK(ctx),
   283  		Transfers: txInfos,
   284  	}, nil
   285  }
   286  
   287  func (s *service) RetryTransfer(ctx context.Context, req *datatx.RetryTransferRequest) (*datatx.RetryTransferResponse, error) {
   288  	txShare, ok := s.txShareDriver.model.TxShares[req.GetTxId().GetOpaqueId()]
   289  	if !ok {
   290  		return nil, errtypes.InternalError("datatx service: transfer not found")
   291  	}
   292  
   293  	txInfo, err := s.txManager.RetryTransfer(ctx, req.GetTxId().OpaqueId)
   294  	if err != nil {
   295  		return &datatx.RetryTransferResponse{
   296  			Status: status.NewInternal(ctx, "error retrying transfer"),
   297  			TxInfo: txInfo,
   298  		}, err
   299  	}
   300  
   301  	txInfo.ShareId = &ocm.ShareId{OpaqueId: string(txShare.Opaque.Map["shareId"].Value)}
   302  
   303  	return &datatx.RetryTransferResponse{
   304  		Status: status.NewOK(ctx),
   305  		TxInfo: txInfo,
   306  	}, nil
   307  }
   308  
   309  func (s *service) extractEndpointInfo(ctx context.Context, targetURL string) (*webdavEndpoint, error) {
   310  	if targetURL == "" {
   311  		return nil, errtypes.BadRequest("datatx service: ref target is an empty uri")
   312  	}
   313  
   314  	uri, err := url.Parse(targetURL)
   315  	if err != nil {
   316  		return nil, errors.Wrap(err, "datatx service: error parsing target uri: "+targetURL)
   317  	}
   318  
   319  	m, err := url.ParseQuery(uri.RawQuery)
   320  	if err != nil {
   321  		return nil, errors.Wrap(err, "datatx service: error parsing target resource name")
   322  	}
   323  
   324  	return &webdavEndpoint{
   325  		filePath:       m["name"][0],
   326  		endpoint:       uri.Host + uri.Path,
   327  		endpointScheme: uri.Scheme,
   328  		token:          uri.User.String(),
   329  	}, nil
   330  }
   331  
   332  func loadOrCreate(file string) (*txShareModel, error) {
   333  	_, err := os.Stat(file)
   334  	if os.IsNotExist(err) {
   335  		if err := os.WriteFile(file, []byte("{}"), 0700); err != nil {
   336  			err = errors.Wrap(err, "datatx service: error creating the transfer shares storage file: "+file)
   337  			return nil, err
   338  		}
   339  	}
   340  
   341  	fd, err := os.OpenFile(file, os.O_CREATE, 0644)
   342  	if err != nil {
   343  		err = errors.Wrap(err, "datatx service: error opening the transfer shares storage file: "+file)
   344  		return nil, err
   345  	}
   346  	defer fd.Close()
   347  
   348  	data, err := io.ReadAll(fd)
   349  	if err != nil {
   350  		err = errors.Wrap(err, "datatx service: error reading the data")
   351  		return nil, err
   352  	}
   353  
   354  	model := &txShareModel{}
   355  	if err := json.Unmarshal(data, model); err != nil {
   356  		err = errors.Wrap(err, "datatx service: error decoding transfer shares data to json")
   357  		return nil, err
   358  	}
   359  
   360  	if model.TxShares == nil {
   361  		model.TxShares = make(map[string]*txShare)
   362  	}
   363  
   364  	model.File = file
   365  	return model, nil
   366  }
   367  
   368  func (m *txShareModel) saveTxShare() error {
   369  	data, err := json.Marshal(m)
   370  	if err != nil {
   371  		err = errors.Wrap(err, "datatx service: error encoding transfer share data to json")
   372  		return err
   373  	}
   374  
   375  	if err := os.WriteFile(m.File, data, 0644); err != nil {
   376  		err = errors.Wrap(err, "datatx service: error writing transfer share data to file: "+m.File)
   377  		return err
   378  	}
   379  
   380  	return nil
   381  }