github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/tgtdl.go (about)

     1  // Package ais provides core functionality for the AIStore object storage.
     2  /*
     3   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package ais
     6  
     7  import (
     8  	"fmt"
     9  	"net/http"
    10  	"regexp"
    11  	"strconv"
    12  	"time"
    13  
    14  	"github.com/NVIDIA/aistore/api/apc"
    15  	"github.com/NVIDIA/aistore/cmn"
    16  	"github.com/NVIDIA/aistore/cmn/cos"
    17  	"github.com/NVIDIA/aistore/cmn/debug"
    18  	"github.com/NVIDIA/aistore/cmn/nlog"
    19  	"github.com/NVIDIA/aistore/core"
    20  	"github.com/NVIDIA/aistore/core/meta"
    21  	"github.com/NVIDIA/aistore/ext/dload"
    22  	"github.com/NVIDIA/aistore/fs"
    23  	"github.com/NVIDIA/aistore/nl"
    24  	"github.com/NVIDIA/aistore/xact/xreg"
    25  	jsoniter "github.com/json-iterator/go"
    26  )
    27  
    28  // [METHOD] /v1/download
    29  func (t *target) downloadHandler(w http.ResponseWriter, r *http.Request) {
    30  	var (
    31  		response   any
    32  		respErr    error
    33  		statusCode int
    34  	)
    35  	if !t.ensureIntraControl(w, r, false /* from primary */) {
    36  		return
    37  	}
    38  
    39  	switch r.Method {
    40  	case http.MethodPost:
    41  		// disallow to run when above high wm (let alone OOS)
    42  		cs := fs.Cap()
    43  		if err := cs.Err(); err != nil {
    44  			t.writeErr(w, r, err, http.StatusInsufficientStorage)
    45  			return
    46  		}
    47  		if _, err := t.parseURL(w, r, apc.URLPathDownload.L, 0, false); err != nil {
    48  			return
    49  		}
    50  		var (
    51  			query            = r.URL.Query()
    52  			xid              = query.Get(apc.QparamUUID)
    53  			jobID            = query.Get(apc.QparamJobID)
    54  			dlb              = dload.Body{}
    55  			progressInterval = dload.DownloadProgressInterval
    56  		)
    57  		debug.Assertf(cos.IsValidUUID(xid) && cos.IsValidUUID(jobID), "%q, %q", xid, jobID)
    58  		if err := cmn.ReadJSON(w, r, &dlb); err != nil {
    59  			return
    60  		}
    61  
    62  		dlBodyBase := dload.Base{}
    63  		if err := jsoniter.Unmarshal(dlb.RawMessage, &dlBodyBase); err != nil {
    64  			err = fmt.Errorf(cmn.FmtErrUnmarshal, t, "download message", cos.BHead(dlb.RawMessage), err)
    65  			t.writeErr(w, r, err)
    66  			return
    67  		}
    68  
    69  		if dlBodyBase.ProgressInterval != "" {
    70  			dur, err := time.ParseDuration(dlBodyBase.ProgressInterval)
    71  			if err != nil {
    72  				t.writeErrf(w, r, "%s: invalid progress interval %q: %v", t, dlBodyBase.ProgressInterval, err)
    73  				return
    74  			}
    75  			progressInterval = dur
    76  		}
    77  
    78  		bck := meta.CloneBck(&dlBodyBase.Bck)
    79  		if err := bck.Init(t.Bowner()); err != nil {
    80  			t.writeErr(w, r, err)
    81  			return
    82  		}
    83  
    84  		xdl, err := renewdl(xid, bck)
    85  		if err != nil {
    86  			t.writeErr(w, r, err, http.StatusInternalServerError)
    87  			return
    88  		}
    89  		dljob, err := dload.ParseStartRequest(bck, jobID, dlb, xdl)
    90  		if err != nil {
    91  			xdl.Abort(err)
    92  			t.writeErr(w, r, err)
    93  			return
    94  		}
    95  		if cmn.Rom.FastV(4, cos.SmoduleAIS) {
    96  			nlog.Infoln("Downloading:", dljob.ID())
    97  		}
    98  
    99  		dljob.AddNotif(&dload.NotifDownload{
   100  			Base: nl.Base{
   101  				When:     core.UponProgress,
   102  				Interval: progressInterval,
   103  				Dsts:     []string{equalIC},
   104  				F:        t.notifyTerm,
   105  				P:        t.notifyProgress,
   106  			},
   107  		}, dljob)
   108  		response, statusCode, respErr = xdl.Download(dljob)
   109  
   110  	case http.MethodGet:
   111  		if _, err := t.parseURL(w, r, apc.URLPathDownload.L, 0, false); err != nil {
   112  			return
   113  		}
   114  		msg := &dload.AdminBody{}
   115  		if err := cmn.ReadJSON(w, r, msg); err != nil {
   116  			return
   117  		}
   118  		if err := msg.Validate(false /*requireID*/); err != nil {
   119  			debug.Assert(false)
   120  			t.writeErr(w, r, err)
   121  			return
   122  		}
   123  
   124  		if msg.ID != "" {
   125  			xid := r.URL.Query().Get(apc.QparamUUID)
   126  			debug.Assert(cos.IsValidUUID(xid))
   127  			xdl, err := renewdl(xid, nil)
   128  			if err != nil {
   129  				t.writeErr(w, r, err, http.StatusInternalServerError)
   130  				return
   131  			}
   132  			response, statusCode, respErr = xdl.JobStatus(msg.ID, msg.OnlyActive)
   133  		} else {
   134  			var regex *regexp.Regexp
   135  			if msg.Regex != "" {
   136  				rgx, err := regexp.CompilePOSIX(msg.Regex)
   137  				if err != nil {
   138  					t.writeErr(w, r, err)
   139  					return
   140  				}
   141  				regex = rgx
   142  			}
   143  			response, statusCode, respErr = dload.ListJobs(regex, msg.OnlyActive)
   144  		}
   145  
   146  	case http.MethodDelete:
   147  		items, err := t.parseURL(w, r, apc.URLPathDownload.L, 1, false)
   148  		if err != nil {
   149  			return
   150  		}
   151  		actdelete := items[0]
   152  		if actdelete != apc.Abort && actdelete != apc.Remove {
   153  			t.writeErrAct(w, r, actdelete)
   154  			return
   155  		}
   156  
   157  		payload := &dload.AdminBody{}
   158  		if err = cmn.ReadJSON(w, r, payload); err != nil {
   159  			return
   160  		}
   161  		if err = payload.Validate(true /*requireID*/); err != nil {
   162  			debug.Assert(false)
   163  			t.writeErr(w, r, err)
   164  			return
   165  		}
   166  
   167  		xid := r.URL.Query().Get(apc.QparamUUID)
   168  		debug.Assertf(cos.IsValidUUID(xid), "%q", xid)
   169  		xdl, err := renewdl(xid, nil)
   170  		if err != nil {
   171  			t.writeErr(w, r, err, http.StatusInternalServerError)
   172  			return
   173  		}
   174  		if actdelete == apc.Abort {
   175  			response, statusCode, respErr = xdl.AbortJob(payload.ID)
   176  		} else { // apc.Remove
   177  			response, statusCode, respErr = xdl.RemoveJob(payload.ID)
   178  		}
   179  	default:
   180  		cmn.WriteErr405(w, r, http.MethodDelete, http.MethodGet, http.MethodPost)
   181  		return
   182  	}
   183  
   184  	if statusCode >= http.StatusBadRequest {
   185  		t.writeErr(w, r, respErr, statusCode)
   186  		return
   187  	}
   188  	if response != nil {
   189  		b := cos.MustMarshal(response)
   190  		w.Header().Set(cos.HdrContentType, cos.ContentJSON)
   191  		w.Header().Set(cos.HdrContentLength, strconv.Itoa(len(b)))
   192  		w.Write(b)
   193  	}
   194  }
   195  
   196  func renewdl(xid string, bck *meta.Bck) (*dload.Xact, error) {
   197  	rns := xreg.RenewDownloader(xid, bck)
   198  	if rns.Err != nil {
   199  		return nil, rns.Err
   200  	}
   201  	xctn := rns.Entry.Get()
   202  	return xctn.(*dload.Xact), nil
   203  }