github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/plstcx.go (about) 1 // Package ais provides core functionality for the AIStore object storage. 2 /* 3 * Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package ais 6 7 import ( 8 "fmt" 9 "net/http" 10 "strings" 11 "sync" 12 13 "github.com/NVIDIA/aistore/api/apc" 14 "github.com/NVIDIA/aistore/cmn" 15 "github.com/NVIDIA/aistore/cmn/atomic" 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/xact" 22 "github.com/NVIDIA/aistore/xact/xs" 23 ) 24 25 type ( 26 lstca struct { 27 a map[string]*lstcx 28 mu sync.Mutex 29 } 30 lstcx struct { 31 p *proxy 32 // arg 33 bckFrom *meta.Bck 34 bckTo *meta.Bck 35 amsg *apc.ActMsg // orig 36 config *cmn.Config 37 smap *smapX 38 hdr http.Header 39 // work 40 tsi *meta.Snode 41 xid string // x-tco 42 cnt int 43 lsmsg apc.LsoMsg 44 altmsg apc.ActMsg 45 tcomsg cmn.TCObjsMsg 46 stopped atomic.Bool 47 } 48 ) 49 50 func (a *lstca) add(c *lstcx) { 51 a.mu.Lock() 52 if a.a == nil { 53 a.a = make(map[string]*lstcx, 4) 54 } 55 a.a[c.xid] = c 56 a.mu.Unlock() 57 } 58 59 func (a *lstca) del(c *lstcx) { 60 a.mu.Lock() 61 delete(a.a, c.xid) 62 a.mu.Unlock() 63 } 64 65 func (a *lstca) abort(xargs *xact.ArgsMsg) { 66 switch { 67 case xargs.ID != "": 68 if !strings.HasPrefix(xargs.ID, xs.PrefixTcoID) { 69 return 70 } 71 a.mu.Lock() 72 if c, ok := a.a[xargs.ID]; ok { 73 c.stopped.Store(true) 74 } 75 a.mu.Unlock() 76 nlog.Infoln(xargs.ID, "aborted") 77 case xargs.Kind == apc.ActCopyObjects || xargs.Kind == apc.ActETLObjects: 78 var ids []string 79 a.mu.Lock() 80 for uuid, c := range a.a { 81 c.stopped.Store(true) 82 ids = append(ids, uuid) 83 } 84 clear(a.a) 85 a.mu.Unlock() 86 if len(ids) > 0 { 87 nlog.Infoln(ids, "aborted") 88 } 89 } 90 } 91 92 func (c *lstcx) do() (string, error) { 93 // 1. lsmsg 94 c.lsmsg = apc.LsoMsg{ 95 UUID: cos.GenUUID(), 96 Prefix: c.tcomsg.TCBMsg.Prefix, 97 Props: apc.GetPropsName, 98 PageSize: 0, // i.e., backend.MaxPageSize() 99 } 100 c.lsmsg.SetFlag(apc.LsNameOnly) 101 c.smap = c.p.owner.smap.get() 102 tsi, err := c.smap.HrwTargetTask(c.lsmsg.UUID) 103 if err != nil { 104 return "", err 105 } 106 c.tsi = tsi 107 c.lsmsg.SID = tsi.ID() 108 109 // 2. ls 1st page 110 var lst *cmn.LsoRes 111 lst, err = c.p.lsObjsR(c.bckFrom, &c.lsmsg, c.hdr, c.smap, tsi /*designated target*/, c.config, true) 112 if err != nil { 113 return "", err 114 } 115 if len(lst.Entries) == 0 { 116 // TODO: return http status to indicate exactly that (#6393) 117 nlog.Infoln(c.amsg.Action, c.bckFrom.Cname(""), " to ", c.bckTo.Cname("")+": lso counts zero - nothing to do") 118 return c.lsmsg.UUID, nil 119 } 120 121 // 3. assign txn UUID here, and use it to communicate with x-tco directly across pages (ref050724) 122 c.tcomsg.TxnUUID = cos.GenUUID() 123 124 // 4. tcomsg 125 c.tcomsg.ToBck = c.bckTo.Clone() 126 lr, cnt := &c.tcomsg.ListRange, len(lst.Entries) 127 lr.ObjNames = make([]string, 0, cnt) 128 for _, e := range lst.Entries { 129 if e.IsDir() || cos.IsLastB(e.Name, '/') { // skip virtual dir (apc.EntryIsDir) 130 continue 131 } 132 lr.ObjNames = append(lr.ObjNames, e.Name) 133 } 134 135 // 5. multi-obj action: transform/copy 1st page 136 c.altmsg.Value = &c.tcomsg 137 c.altmsg.Action = apc.ActCopyObjects 138 if c.amsg.Action == apc.ActETLBck { 139 c.altmsg.Action = apc.ActETLObjects 140 } 141 142 if c.xid, err = c.p.tcobjs(c.bckFrom, c.bckTo, c.config, &c.altmsg, &c.tcomsg); err != nil { 143 return "", err 144 } 145 146 nlog.Infoln("'ls --all' to execute [" + c.amsg.Action + " -> " + c.altmsg.Action + "]") 147 s := fmt.Sprintf("%s[%s] %s => %s", c.altmsg.Action, c.xid, c.bckFrom, c.bckTo) 148 149 // 6. more pages, if any 150 if lst.ContinuationToken != "" { 151 // Run 152 nlog.Infoln("run", s, "...") 153 c.lsmsg.ContinuationToken = lst.ContinuationToken 154 go c.pages(s, cnt) 155 } else { 156 nlog.Infoln(s, "count", cnt) 157 } 158 return c.xid, nil 159 } 160 161 func (c *lstcx) pages(s string, cnt int) { 162 c.cnt = cnt 163 c.p.lstca.add(c) 164 165 // pages 2, 3, ... 166 var err error 167 for !c.stopped.Load() && c.lsmsg.ContinuationToken != "" { 168 if cnt, err = c._page(); err != nil { 169 break 170 } 171 c.cnt += cnt 172 } 173 c.p.lstca.del(c) 174 nlog.Infoln(s, "count", c.cnt, "stopped", c.stopped.Load(), "c-token", c.lsmsg.ContinuationToken, "err", err) 175 } 176 177 // next page 178 func (c *lstcx) _page() (int, error) { 179 lst, err := c.p.lsObjsR(c.bckFrom, &c.lsmsg, c.hdr, c.smap, c.tsi, c.config, true) 180 if err != nil { 181 return 0, err 182 } 183 c.lsmsg.ContinuationToken = lst.ContinuationToken 184 if len(lst.Entries) == 0 { 185 debug.Assert(lst.ContinuationToken == "") 186 return 0, nil 187 } 188 189 lr := &c.tcomsg.ListRange 190 clear(lr.ObjNames) 191 lr.ObjNames = lr.ObjNames[:0] 192 for _, e := range lst.Entries { 193 if e.IsDir() || cos.IsLastB(e.Name, '/') { // skip virtual dir (apc.EntryIsDir) 194 continue 195 } 196 lr.ObjNames = append(lr.ObjNames, e.Name) 197 } 198 c.altmsg.Name = c.xid 199 c.altmsg.Value = &c.tcomsg 200 err = c.bcast() 201 return len(lr.ObjNames), err 202 } 203 204 // calls t.httpxpost (TODO: slice of names is the only "delta" - optimize) 205 func (c *lstcx) bcast() (err error) { 206 body := cos.MustMarshal(c.altmsg) 207 args := allocBcArgs() 208 { 209 args.req = cmn.HreqArgs{Method: http.MethodPost, Path: apc.URLPathXactions.S, Body: body} 210 args.to = core.Targets 211 args.timeout = cmn.Rom.MaxKeepalive() 212 } 213 if c.stopped.Load() { 214 return 215 } 216 results := c.p.bcastGroup(args) 217 freeBcArgs(args) 218 for _, res := range results { 219 if err = res.err; err != nil { 220 break 221 } 222 } 223 freeBcastRes(results) 224 return err 225 }