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 }