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  }