github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/tgtimpl.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 "context" 9 "errors" 10 "net/http" 11 "net/url" 12 "os" 13 "time" 14 15 "github.com/NVIDIA/aistore/ais/backend" 16 "github.com/NVIDIA/aistore/api/apc" 17 "github.com/NVIDIA/aistore/cmn" 18 "github.com/NVIDIA/aistore/cmn/cos" 19 "github.com/NVIDIA/aistore/cmn/debug" 20 "github.com/NVIDIA/aistore/cmn/mono" 21 "github.com/NVIDIA/aistore/cmn/nlog" 22 "github.com/NVIDIA/aistore/core" 23 "github.com/NVIDIA/aistore/core/meta" 24 "github.com/NVIDIA/aistore/fs" 25 "github.com/NVIDIA/aistore/memsys" 26 "github.com/NVIDIA/aistore/stats" 27 "github.com/NVIDIA/aistore/transport/bundle" 28 "github.com/NVIDIA/aistore/xact/xreg" 29 ) 30 31 // interface guard 32 var _ core.Target = (*target)(nil) 33 34 func (t *target) FSHC(err error, path string) { t.fsErr(err, path) } 35 func (t *target) PageMM() *memsys.MMSA { return t.gmm } 36 func (t *target) ByteMM() *memsys.MMSA { return t.smm } 37 38 func (*target) DataClient() *http.Client { return g.client.data } 39 40 func (*target) GetAllRunning(inout *core.AllRunningInOut, periodic bool) { 41 xreg.GetAllRunning(inout, periodic) 42 } 43 44 func (t *target) Health(si *meta.Snode, timeout time.Duration, query url.Values) ([]byte, int, error) { 45 return t.reqHealth(si, timeout, query, t.owner.smap.get()) 46 } 47 48 func (t *target) Backend(bck *meta.Bck) core.Backend { 49 if bck.IsRemoteAIS() { 50 return t.backend[apc.AIS] 51 } 52 if bck.IsHTTP() { 53 return t.backend[apc.HTTP] 54 } 55 provider := bck.Provider 56 if bck.Props != nil { 57 provider = bck.RemoteBck().Provider 58 } 59 config := cmn.GCO.Get() 60 if _, ok := config.Backend.Providers[provider]; ok { 61 bp, k := t.backend[provider] 62 debug.Assert(k, provider) 63 if bp != nil { 64 return bp 65 } 66 // nil when configured & not-built 67 } 68 c, _ := backend.NewDummyBackend(t) 69 return c 70 } 71 72 func (t *target) PutObject(lom *core.LOM, params *core.PutParams) error { 73 debug.Assert(params.WorkTag != "" && !params.Atime.IsZero()) 74 workFQN := fs.CSM.Gen(lom, fs.WorkfileType, params.WorkTag) 75 poi := allocPOI() 76 { 77 poi.t = t 78 poi.lom = lom 79 poi.config = cmn.GCO.Get() 80 poi.r = params.Reader 81 poi.workFQN = workFQN 82 poi.atime = params.Atime.UnixNano() 83 poi.xctn = params.Xact 84 poi.size = params.Size 85 poi.owt = params.OWT 86 poi.skipEC = params.SkipEC 87 poi.coldGET = params.ColdGET 88 } 89 if poi.owt != cmn.OwtPut { 90 poi.cksumToUse = params.Cksum 91 } 92 _, err := poi.putObject() 93 freePOI(poi) 94 debug.Assert(err != nil || params.Size <= 0 || params.Size == lom.SizeBytes(true), lom.String(), params.Size, lom.SizeBytes(true)) 95 return err 96 } 97 98 func (t *target) FinalizeObj(lom *core.LOM, workFQN string, xctn core.Xact, owt cmn.OWT) (ecode int, err error) { 99 if err = cos.Stat(workFQN); err != nil { 100 return 101 } 102 poi := allocPOI() 103 { 104 poi.t = t 105 poi.atime = time.Now().UnixNano() 106 poi.lom = lom 107 poi.workFQN = workFQN 108 poi.owt = owt 109 poi.xctn = xctn 110 } 111 ecode, err = poi.finalize() 112 freePOI(poi) 113 return 114 } 115 116 func (t *target) EvictObject(lom *core.LOM) (ecode int, err error) { 117 ecode, err = t.DeleteObject(lom, true /*evict*/) 118 return 119 } 120 121 func (t *target) HeadObjT2T(lom *core.LOM, si *meta.Snode) bool { 122 return t.headt2t(lom, si, t.owner.smap.get()) 123 } 124 125 // CopyObject: 126 // - either creates a full replica of the source object (the `lom` argument) 127 // - or transforms the object 128 // 129 // In both cases, the result is placed at the `params`-defined destination 130 // in accordance with the configured destination bucket policies. 131 // 132 // Destination object _may_ have a different name and _may_ be located in a different bucket. 133 // 134 // Scenarios include (but are not limited to): 135 // - if both src and dst LOMs are from local buckets the copying then takes place between AIS targets 136 // (of this same cluster); 137 // - if the src is located in a remote bucket, we always first make sure it is also present in 138 // the AIS cluster (by performing a cold GET if need be). 139 // - if the dst is cloud, we perform a regular PUT logic thus also making sure that the new 140 // replica gets created in the cloud bucket of _this_ AIS cluster. 141 func (t *target) CopyObject(lom *core.LOM, dm core.DM, params *core.CopyParams) (size int64, err error) { 142 coi := (*copyOI)(params) 143 // defaults 144 coi.OWT = cmn.OwtCopy 145 coi.Finalize = false 146 if coi.ObjnameTo == "" { 147 coi.ObjnameTo = lom.ObjName 148 } 149 realDM, ok := dm.(*bundle.DataMover) // TODO -- FIXME: eliminate typecast 150 debug.Assert(ok) 151 152 size, err = coi.do(t, realDM, lom) 153 154 coi.stats(size, err) 155 return size, err 156 } 157 158 // use `backend.GetObj` (compare w/ other instances calling `backend.GetObjReader`) 159 func (t *target) GetCold(ctx context.Context, lom *core.LOM, owt cmn.OWT) (ecode int, err error) { 160 // 1. lock 161 switch owt { 162 case cmn.OwtGetPrefetchLock: 163 // do nothing 164 case cmn.OwtGetTryLock, cmn.OwtGetLock: 165 if owt == cmn.OwtGetTryLock { 166 if !lom.TryLock(true) { 167 if cmn.Rom.FastV(4, cos.SmoduleAIS) { 168 nlog.Warningf("%s: %s(%s) is busy", t, lom, owt) 169 } 170 return 0, cmn.ErrSkip // e.g. prefetch can skip it and keep on going 171 } 172 } else { 173 lom.Lock(true) 174 } 175 default: 176 // for cmn.OwtGet, see goi.getCold 177 debug.Assert(false, owt.String()) 178 return http.StatusInternalServerError, errors.New("invalid " + owt.String()) 179 } 180 181 // 2. GET remote object and store it 182 now := mono.NanoTime() 183 if ecode, err = t.Backend(lom.Bck()).GetObj(ctx, lom, owt, nil /*origReq*/); err != nil { 184 if owt != cmn.OwtGetPrefetchLock { 185 lom.Unlock(true) 186 } 187 nlog.Infoln(t.String()+":", "failed to GET remote", lom.Cname()+":", err, ecode) 188 return ecode, err 189 } 190 191 // 3. unlock 192 switch owt { 193 case cmn.OwtGetPrefetchLock: 194 // do nothing 195 case cmn.OwtGetTryLock, cmn.OwtGetLock: 196 lom.Unlock(true) 197 } 198 199 // 4. stats 200 t.statsT.AddMany( 201 cos.NamedVal64{Name: stats.GetColdCount, Value: 1}, 202 cos.NamedVal64{Name: stats.GetColdSize, Value: lom.SizeBytes()}, 203 cos.NamedVal64{Name: stats.GetColdRwLatency, Value: mono.SinceNano(now)}, 204 ) 205 return 0, nil 206 } 207 208 func (t *target) GetColdBlob(params *core.BlobParams, oa *cmn.ObjAttrs) (xctn core.Xact, err error) { 209 debug.Assert(params.Lom != nil) 210 debug.Assert(params.Msg != nil) 211 _, xctn, err = t.blobdl(params, oa) 212 return xctn, err 213 } 214 215 func (t *target) Promote(params *core.PromoteParams) (ecode int, err error) { 216 lom := core.AllocLOM(params.ObjName) 217 if err = lom.InitBck(params.Bck.Bucket()); err == nil { 218 ecode, err = t._promote(params, lom) 219 } 220 core.FreeLOM(lom) 221 return 222 } 223 224 func (t *target) _promote(params *core.PromoteParams, lom *core.LOM) (ecode int, err error) { 225 smap := t.owner.smap.get() 226 tsi, local, erh := lom.HrwTarget(&smap.Smap) 227 if erh != nil { 228 return 0, erh 229 } 230 var size int64 231 if local { 232 size, ecode, err = t._promLocal(params, lom) 233 } else { 234 size, err = t._promRemote(params, lom, tsi, smap) 235 if err == nil && size >= 0 && params.Xact != nil { 236 params.Xact.OutObjsAdd(1, size) 237 } 238 } 239 if err != nil { 240 return 241 } 242 if size >= 0 && params.Xact != nil { 243 params.Xact.ObjsAdd(1, size) // (as initiator) 244 } 245 if params.DeleteSrc { 246 if errRm := cos.RemoveFile(params.SrcFQN); errRm != nil { 247 nlog.Errorf("%s: failed to remove promoted source %q: %v", t, params.SrcFQN, errRm) 248 } 249 } 250 return 251 } 252 253 func (t *target) _promLocal(params *core.PromoteParams, lom *core.LOM) (fileSize int64, ecode int, err error) { 254 var ( 255 cksum *cos.CksumHash 256 workFQN string 257 extraCopy = true 258 ) 259 fileSize = -1 260 261 if err = lom.Load(true /*cache it*/, false /*locked*/); err == nil && !params.OverwriteDst { 262 return 263 } 264 if params.DeleteSrc { 265 // To use `params.SrcFQN` as `workFQN`, make sure both are 266 // located on the same filesystem. About "filesystem sharing" see also: 267 // * https://github.com/NVIDIA/aistore/blob/main/docs/overview.md#terminology 268 mi, _, err := fs.FQN2Mpath(params.SrcFQN) 269 extraCopy = err != nil || !mi.FS.Equal(lom.Mountpath().FS) 270 } 271 if extraCopy { 272 workFQN = fs.CSM.Gen(lom, fs.WorkfileType, fs.WorkfilePut) 273 buf, slab := t.gmm.Alloc() 274 fileSize, cksum, err = cos.CopyFile(params.SrcFQN, workFQN, buf, lom.CksumType()) 275 slab.Free(buf) 276 if err != nil { 277 return 278 } 279 lom.SetCksum(cksum.Clone()) 280 } else { 281 // avoid extra copy: use the source as `workFQN` 282 var fi os.FileInfo 283 fi, err = os.Stat(params.SrcFQN) 284 if err != nil { 285 if os.IsNotExist(err) { 286 err = nil 287 } 288 return 289 } 290 291 fileSize = fi.Size() 292 workFQN = params.SrcFQN 293 if params.Cksum != nil { 294 lom.SetCksum(params.Cksum) // already computed somewhere else, use it 295 } else { 296 clone := lom.CloneMD(params.SrcFQN) 297 if cksum, err = clone.ComputeCksum(lom.CksumType()); err != nil { 298 core.FreeLOM(clone) 299 return 300 } 301 lom.SetCksum(cksum.Clone()) 302 core.FreeLOM(clone) 303 } 304 } 305 if params.Cksum != nil && cksum != nil { 306 if !cksum.Equal(params.Cksum) { 307 err = cos.NewErrDataCksum( 308 cksum.Clone(), 309 params.Cksum, 310 params.SrcFQN+" => "+lom.String() /*detail*/) 311 return 312 } 313 } 314 poi := allocPOI() 315 { 316 poi.atime = time.Now().UnixNano() 317 poi.t = t 318 poi.config = params.Config 319 poi.lom = lom 320 poi.workFQN = workFQN 321 poi.owt = cmn.OwtPromote 322 poi.xctn = params.Xact 323 } 324 lom.SetSize(fileSize) 325 ecode, err = poi.finalize() 326 freePOI(poi) 327 return 328 } 329 330 // TODO: use DM streams 331 // TODO: Xact.InObjsAdd on the receive side 332 func (t *target) _promRemote(params *core.PromoteParams, lom *core.LOM, tsi *meta.Snode, smap *smapX) (int64, error) { 333 lom.FQN = params.SrcFQN 334 335 // when not overwriting check w/ remote target first (and separately) 336 if !params.OverwriteDst && t.headt2t(lom, tsi, smap) { 337 return -1, nil 338 } 339 340 coiParams := core.AllocCOI() 341 { 342 coiParams.BckTo = lom.Bck() 343 coiParams.OWT = cmn.OwtPromote 344 coiParams.Xact = params.Xact 345 coiParams.Config = params.Config 346 } 347 coi := (*copyOI)(coiParams) 348 size, err := coi.send(t, nil /*DM*/, lom, lom.ObjName, tsi) 349 core.FreeCOI(coiParams) 350 351 return size, err 352 } 353 354 // 355 // implements health.fspathDispatcher interface 356 // 357 358 func (t *target) DisableMpath(mpath, reason string) (err error) { 359 nlog.Warningf("Disabling mountpath %s: %s", mpath, reason) 360 _, err = t.fsprg.disableMpath(mpath, true /*dont-resilver*/) // NOTE: not resilvering upon FSCH calling 361 return 362 }