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 }