github.com/cs3org/reva/v2@v2.27.7/internal/http/services/owncloud/ocdav/tpc.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 ocdav
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"io"
    25  	"net/http"
    26  	"path"
    27  	"strconv"
    28  	"strings"
    29  	"time"
    30  
    31  	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
    32  	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    33  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    34  	typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    35  	"github.com/cs3org/reva/v2/internal/http/services/datagateway"
    36  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors"
    37  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
    38  	"github.com/cs3org/reva/v2/pkg/appctx"
    39  	"github.com/cs3org/reva/v2/pkg/errtypes"
    40  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    41  	"github.com/cs3org/reva/v2/pkg/rhttp"
    42  )
    43  
    44  const (
    45  	// PerfMarkerResponseTime corresponds to the interval at which a performance marker is sent back to the TPC client
    46  	PerfMarkerResponseTime float64 = 5
    47  )
    48  
    49  // PerfResponse provides a single chunk of permormance marker response
    50  type PerfResponse struct {
    51  	Timestamp time.Time
    52  	Bytes     uint64
    53  	Index     int
    54  	Count     int
    55  }
    56  
    57  func (p *PerfResponse) getPerfResponseString() string {
    58  	var sb strings.Builder
    59  	sb.WriteString("Perf Marker\n")
    60  	sb.WriteString("Timestamp: " + strconv.FormatInt(p.Timestamp.Unix(), 10) + "\n")
    61  	sb.WriteString("Stripe Bytes Transferred: " + strconv.FormatUint(p.Bytes, 10) + "\n")
    62  	sb.WriteString("Strip Index: " + strconv.Itoa(p.Index) + "\n")
    63  	sb.WriteString("Total Stripe Count: " + strconv.Itoa(p.Count) + "\n")
    64  	sb.WriteString("End\n")
    65  	return sb.String()
    66  }
    67  
    68  // WriteCounter counts the number of bytes transferred and reports
    69  // back to the TPC client about the progress of the transfer
    70  // through the performance marker response stream.
    71  type WriteCounter struct {
    72  	Total    uint64
    73  	PrevTime time.Time
    74  	w        http.ResponseWriter
    75  }
    76  
    77  // SendPerfMarker flushes a single chunk (performance marker) as
    78  // part of the chunked transfer encoding scheme.
    79  func (wc *WriteCounter) SendPerfMarker(size uint64) {
    80  	flusher, ok := wc.w.(http.Flusher)
    81  	if !ok {
    82  		panic("expected http.ResponseWriter to be an http.Flusher")
    83  	}
    84  	perfResp := PerfResponse{time.Now(), size, 0, 1}
    85  	pString := perfResp.getPerfResponseString()
    86  	fmt.Fprintln(wc.w, pString)
    87  	flusher.Flush()
    88  }
    89  
    90  func (wc *WriteCounter) Write(p []byte) (int, error) {
    91  
    92  	n := len(p)
    93  	wc.Total += uint64(n)
    94  	NowTime := time.Now()
    95  
    96  	diff := NowTime.Sub(wc.PrevTime).Seconds()
    97  	if diff >= PerfMarkerResponseTime {
    98  		wc.SendPerfMarker(wc.Total)
    99  		wc.PrevTime = NowTime
   100  	}
   101  	return n, nil
   102  }
   103  
   104  //
   105  // An example of an HTTP TPC Pull
   106  //
   107  // +-----------------+        GET          +----------------+
   108  // |  Src server     |  <----------------  |  Dest server   |
   109  // |  (Remote)       |  ---------------->  |  (Reva)        |
   110  // +-----------------+       Data          +----------------+
   111  // 												^
   112  // 												|
   113  // 												| COPY
   114  // 												|
   115  // 										   +----------+
   116  // 										   |  Client  |
   117  // 										   +----------+
   118  
   119  // handleTPCPull performs a GET request on the remote site and upload it
   120  // the requested reva endpoint.
   121  func (s *svc) handleTPCPull(ctx context.Context, w http.ResponseWriter, r *http.Request, ns string) {
   122  	src := r.Header.Get("Source")
   123  	dst := path.Join(ns, r.URL.Path)
   124  	sublog := appctx.GetLogger(ctx).With().Str("src", src).Str("dst", dst).Logger()
   125  
   126  	oh := r.Header.Get(net.HeaderOverwrite)
   127  	overwrite, err := net.ParseOverwrite(oh)
   128  	if err != nil {
   129  		w.WriteHeader(http.StatusBadRequest)
   130  		m := fmt.Sprintf("Overwrite header is set to incorrect value %v", overwrite)
   131  		sublog.Warn().Msgf("HTTP TPC Pull: %s", m)
   132  		b, err := errors.Marshal(http.StatusBadRequest, m, "", "")
   133  		errors.HandleWebdavError(&sublog, w, b, err)
   134  		return
   135  	}
   136  	sublog.Debug().Bool("overwrite", overwrite).Msg("TPC Pull")
   137  
   138  	client, err := s.gatewaySelector.Next()
   139  	if err != nil {
   140  		sublog.Error().Err(err).Msg("error selecting next gateway client")
   141  		w.WriteHeader(http.StatusInternalServerError)
   142  		return
   143  	}
   144  	// check if destination exists
   145  	ref := &provider.Reference{Path: dst}
   146  	dstStatReq := &provider.StatRequest{Ref: ref}
   147  	dstStatRes, err := client.Stat(ctx, dstStatReq)
   148  	if err != nil {
   149  		sublog.Error().Err(err).Msg("error sending grpc stat request")
   150  		w.WriteHeader(http.StatusInternalServerError)
   151  		return
   152  	}
   153  	if dstStatRes.Status.Code != rpc.Code_CODE_OK && dstStatRes.Status.Code != rpc.Code_CODE_NOT_FOUND {
   154  		errors.HandleErrorStatus(&sublog, w, dstStatRes.Status)
   155  		return
   156  	}
   157  	if dstStatRes.Status.Code == rpc.Code_CODE_OK && oh == "F" {
   158  		sublog.Warn().Bool("overwrite", overwrite).Msg("Destination already exists")
   159  		w.WriteHeader(http.StatusPreconditionFailed) // 412, see https://tools.ietf.org/html/rfc4918#section-9.8.5
   160  		return
   161  	}
   162  
   163  	err = s.performHTTPPull(ctx, s.gatewaySelector, r, w, ns)
   164  	if err != nil {
   165  		sublog.Error().Err(err).Msg("error performing TPC Pull")
   166  		return
   167  	}
   168  	fmt.Fprintf(w, "success: Created")
   169  }
   170  
   171  func (s *svc) performHTTPPull(ctx context.Context, selector pool.Selectable[gateway.GatewayAPIClient], r *http.Request, w http.ResponseWriter, ns string) error {
   172  	src := r.Header.Get("Source")
   173  	dst := path.Join(ns, r.URL.Path)
   174  	sublog := appctx.GetLogger(ctx)
   175  	sublog.Debug().Str("src", src).Str("dst", dst).Msg("Performing HTTP Pull")
   176  
   177  	// get http client for remote
   178  	httpClient := &http.Client{}
   179  
   180  	req, err := http.NewRequest("GET", src, nil)
   181  	if err != nil {
   182  		w.WriteHeader(http.StatusInternalServerError)
   183  		return err
   184  	}
   185  
   186  	// add authentication header
   187  	bearerHeader := r.Header.Get(net.HeaderTransferAuth)
   188  	req.Header.Add("Authorization", bearerHeader)
   189  
   190  	// do download
   191  	httpDownloadRes, err := httpClient.Do(req) // lgtm[go/request-forgery]
   192  	if err != nil {
   193  		w.WriteHeader(http.StatusInternalServerError)
   194  		return err
   195  	}
   196  	defer httpDownloadRes.Body.Close()
   197  
   198  	if httpDownloadRes.StatusCode == http.StatusNotImplemented {
   199  		w.WriteHeader(http.StatusBadRequest)
   200  		return errtypes.NotSupported("Third-Party copy not supported, source might be a folder")
   201  	}
   202  	if httpDownloadRes.StatusCode != http.StatusOK {
   203  		w.WriteHeader(httpDownloadRes.StatusCode)
   204  		return errtypes.InternalError(fmt.Sprintf("Remote GET returned status code %d", httpDownloadRes.StatusCode))
   205  	}
   206  
   207  	client, err := s.gatewaySelector.Next()
   208  	if err != nil {
   209  		sublog.Error().Err(err).Msg("error selecting next gateway client")
   210  		w.WriteHeader(http.StatusInternalServerError)
   211  		return errtypes.InternalError(err.Error())
   212  	}
   213  	// get upload url
   214  	uReq := &provider.InitiateFileUploadRequest{
   215  		Ref: &provider.Reference{Path: dst},
   216  		Opaque: &typespb.Opaque{
   217  			Map: map[string]*typespb.OpaqueEntry{
   218  				"sizedeferred": {
   219  					Value: []byte("true"),
   220  				},
   221  			},
   222  		},
   223  	}
   224  	uRes, err := client.InitiateFileUpload(ctx, uReq)
   225  	if err != nil {
   226  		w.WriteHeader(http.StatusInternalServerError)
   227  		return err
   228  	}
   229  
   230  	if uRes.Status.Code != rpc.Code_CODE_OK {
   231  		w.WriteHeader(http.StatusInternalServerError)
   232  		return fmt.Errorf("status code %d", uRes.Status.Code)
   233  	}
   234  
   235  	var uploadEP, uploadToken string
   236  	for _, p := range uRes.Protocols {
   237  		if p.Protocol == "simple" {
   238  			uploadEP, uploadToken = p.UploadEndpoint, p.Token
   239  		}
   240  	}
   241  
   242  	// send performance markers periodically every PerfMarkerResponseTime (5 seconds unless configured)
   243  	w.WriteHeader(http.StatusAccepted)
   244  	wc := WriteCounter{0, time.Now(), w}
   245  	tempReader := io.TeeReader(httpDownloadRes.Body, &wc)
   246  
   247  	// do Upload
   248  	httpUploadReq, err := rhttp.NewRequest(ctx, "PUT", uploadEP, tempReader)
   249  	if err != nil {
   250  		w.WriteHeader(http.StatusInternalServerError)
   251  		return err
   252  	}
   253  	httpUploadReq.Header.Set(datagateway.TokenTransportHeader, uploadToken)
   254  	httpUploadRes, err := s.client.Do(httpUploadReq)
   255  	if err != nil {
   256  		w.WriteHeader(http.StatusInternalServerError)
   257  		return err
   258  	}
   259  
   260  	defer httpUploadRes.Body.Close()
   261  	if httpUploadRes.StatusCode != http.StatusOK {
   262  		w.WriteHeader(httpUploadRes.StatusCode)
   263  		return err
   264  	}
   265  	return nil
   266  }
   267  
   268  //
   269  // An example of an HTTP TPC Push
   270  //
   271  // +-----------------+        PUT          +----------------+
   272  // |  Dest server    |  <----------------  |  Src server    |
   273  // |  (Remote)       |  ---------------->  |  (Reva)        |
   274  // +-----------------+       Done          +----------------+
   275  // 												^
   276  // 												|
   277  // 												| COPY
   278  // 												|
   279  // 										   +----------+
   280  // 										   |  Client  |
   281  // 										   +----------+
   282  
   283  // handleTPCPush performs a PUT request on the remote site and while downloading
   284  // data from the requested reva endpoint.
   285  func (s *svc) handleTPCPush(ctx context.Context, w http.ResponseWriter, r *http.Request, ns string) {
   286  	src := path.Join(ns, r.URL.Path)
   287  	dst := r.Header.Get("Destination")
   288  	sublog := appctx.GetLogger(ctx).With().Str("src", src).Str("dst", dst).Logger()
   289  
   290  	oh := r.Header.Get(net.HeaderOverwrite)
   291  	overwrite, err := net.ParseOverwrite(oh)
   292  	if err != nil {
   293  		w.WriteHeader(http.StatusBadRequest)
   294  		m := fmt.Sprintf("Overwrite header is set to incorrect value %v", overwrite)
   295  		sublog.Warn().Msgf("HTTP TPC Push: %s", m)
   296  		b, err := errors.Marshal(http.StatusBadRequest, m, "", "")
   297  		errors.HandleWebdavError(&sublog, w, b, err)
   298  		return
   299  	}
   300  
   301  	sublog.Debug().Bool("overwrite", overwrite).Msg("TPC Push")
   302  
   303  	client, err := s.gatewaySelector.Next()
   304  	if err != nil {
   305  		sublog.Error().Err(err).Msg("error selecting next gateway client")
   306  		w.WriteHeader(http.StatusInternalServerError)
   307  		return
   308  	}
   309  	ref := &provider.Reference{Path: src}
   310  	srcStatReq := &provider.StatRequest{Ref: ref}
   311  	srcStatRes, err := client.Stat(ctx, srcStatReq)
   312  	if err != nil {
   313  		sublog.Error().Err(err).Msg("error sending grpc stat request")
   314  		w.WriteHeader(http.StatusInternalServerError)
   315  		return
   316  	}
   317  	if srcStatRes.Status.Code != rpc.Code_CODE_OK && srcStatRes.Status.Code != rpc.Code_CODE_NOT_FOUND {
   318  		errors.HandleErrorStatus(&sublog, w, srcStatRes.Status)
   319  		return
   320  	}
   321  	if srcStatRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER {
   322  		sublog.Error().Msg("Third-Party copy of a folder is not supported")
   323  		w.WriteHeader(http.StatusBadRequest)
   324  		return
   325  	}
   326  
   327  	err = s.performHTTPPush(ctx, r, w, srcStatRes.Info, ns)
   328  	if err != nil {
   329  		sublog.Error().Err(err).Msg("error performing TPC Push")
   330  		return
   331  	}
   332  	fmt.Fprintf(w, "success: Created")
   333  }
   334  
   335  func (s *svc) performHTTPPush(ctx context.Context, r *http.Request, w http.ResponseWriter, srcInfo *provider.ResourceInfo, ns string) error {
   336  	src := path.Join(ns, r.URL.Path)
   337  	dst := r.Header.Get("Destination")
   338  
   339  	sublog := appctx.GetLogger(ctx)
   340  	sublog.Debug().Str("src", src).Str("dst", dst).Msg("Performing HTTP Push")
   341  
   342  	// get download url
   343  	dReq := &provider.InitiateFileDownloadRequest{
   344  		Ref: &provider.Reference{Path: src},
   345  	}
   346  
   347  	client, err := s.gatewaySelector.Next()
   348  	if err != nil {
   349  		sublog.Error().Err(err).Msg("error selecting next gateway client")
   350  		w.WriteHeader(http.StatusInternalServerError)
   351  		return err
   352  	}
   353  	dRes, err := client.InitiateFileDownload(ctx, dReq)
   354  	if err != nil {
   355  		w.WriteHeader(http.StatusInternalServerError)
   356  		return err
   357  	}
   358  
   359  	if dRes.Status.Code != rpc.Code_CODE_OK {
   360  		w.WriteHeader(http.StatusInternalServerError)
   361  		return fmt.Errorf("status code %d", dRes.Status.Code)
   362  	}
   363  
   364  	var downloadEP, downloadToken string
   365  	for _, p := range dRes.Protocols {
   366  		if p.Protocol == "simple" {
   367  			downloadEP, downloadToken = p.DownloadEndpoint, p.Token
   368  		}
   369  	}
   370  
   371  	// do download
   372  	httpDownloadReq, err := rhttp.NewRequest(ctx, "GET", downloadEP, nil)
   373  	if err != nil {
   374  		w.WriteHeader(http.StatusInternalServerError)
   375  		return err
   376  	}
   377  	httpDownloadReq.Header.Set(datagateway.TokenTransportHeader, downloadToken)
   378  
   379  	httpDownloadRes, err := s.client.Do(httpDownloadReq)
   380  	if err != nil {
   381  		w.WriteHeader(http.StatusInternalServerError)
   382  		return err
   383  	}
   384  	defer httpDownloadRes.Body.Close()
   385  	if httpDownloadRes.StatusCode != http.StatusOK {
   386  		w.WriteHeader(httpDownloadRes.StatusCode)
   387  		return fmt.Errorf("Remote PUT returned status code %d", httpDownloadRes.StatusCode)
   388  	}
   389  
   390  	// send performance markers periodically every PerfMarkerResponseTime (5 seconds unless configured)
   391  	w.WriteHeader(http.StatusAccepted)
   392  	wc := WriteCounter{0, time.Now(), w}
   393  	tempReader := io.TeeReader(httpDownloadRes.Body, &wc)
   394  
   395  	// get http client for a remote call
   396  	httpClient := &http.Client{}
   397  	req, err := http.NewRequest("PUT", dst, tempReader)
   398  	if err != nil {
   399  		w.WriteHeader(http.StatusInternalServerError)
   400  		return err
   401  	}
   402  
   403  	// add authentication header and content length
   404  	bearerHeader := r.Header.Get(net.HeaderTransferAuth)
   405  	req.Header.Add("Authorization", bearerHeader)
   406  	req.ContentLength = int64(srcInfo.GetSize())
   407  
   408  	// do Upload
   409  	httpUploadRes, err := httpClient.Do(req) // lgtm[go/request-forgery]
   410  	if err != nil {
   411  		w.WriteHeader(http.StatusInternalServerError)
   412  		return err
   413  	}
   414  	defer httpUploadRes.Body.Close()
   415  
   416  	if httpUploadRes.StatusCode != http.StatusOK {
   417  		w.WriteHeader(httpUploadRes.StatusCode)
   418  		return err
   419  	}
   420  
   421  	return nil
   422  }