github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/prxnotif_internal_test.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  	"bytes"
     9  	"io"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"time"
    13  
    14  	"github.com/NVIDIA/aistore/api/apc"
    15  	"github.com/NVIDIA/aistore/cmn"
    16  	"github.com/NVIDIA/aistore/cmn/atomic"
    17  	"github.com/NVIDIA/aistore/cmn/cos"
    18  	"github.com/NVIDIA/aistore/core"
    19  	"github.com/NVIDIA/aistore/core/meta"
    20  	"github.com/NVIDIA/aistore/core/mock"
    21  	"github.com/NVIDIA/aistore/nl"
    22  	"github.com/NVIDIA/aistore/xact"
    23  	. "github.com/onsi/ginkgo/v2"
    24  	. "github.com/onsi/gomega"
    25  )
    26  
    27  type nopHB struct{}
    28  
    29  func (*nopHB) HeardFrom(string, int64) {}
    30  func (*nopHB) TimedOut(string) bool    { return false }
    31  func (*nopHB) reg(string)              {}
    32  func (*nopHB) set(time.Duration) bool  { return false }
    33  
    34  var _ hbTracker = (*nopHB)(nil)
    35  
    36  var _ = Describe("Notifications xaction test", func() {
    37  	// NOTE: constants and functions declared inside 'Describe' to avoid cluttering of `ais` namespace.
    38  	const (
    39  		target1ID = "target1"
    40  		target2ID = "target2"
    41  		pDaemonID = "primary-id"
    42  	)
    43  
    44  	cos.InitShortID(0)
    45  	xid := cos.GenUUID()
    46  
    47  	// helper functions
    48  	var (
    49  		mockNode = func(id, daeType string) *meta.Snode {
    50  			server := discoverServerDefaultHandler(1, 1)
    51  			info := serverTCPAddr(server.URL)
    52  			return newSnode(id, daeType, info, info, info)
    53  		}
    54  
    55  		getNodeMap = func(ids ...string) (snodes meta.NodeMap) {
    56  			snodes = make(meta.NodeMap, len(ids))
    57  			for _, id := range ids {
    58  				snodes[id] = mockNode(id, apc.Target)
    59  			}
    60  			return
    61  		}
    62  
    63  		mockProxyRunner = func(name string) *proxy {
    64  			tracker := &mock.StatsTracker{}
    65  			p := &proxy{
    66  				htrun: htrun{
    67  					si:     mockNode(name, apc.Proxy),
    68  					statsT: tracker,
    69  				},
    70  			}
    71  			g.client.data = &http.Client{}
    72  			g.client.control = &http.Client{}
    73  
    74  			palive := newPalive(p, tracker, atomic.NewBool(true))
    75  			palive.keepalive.hb = &nopHB{}
    76  			p.keepalive = palive
    77  			return p
    78  		}
    79  
    80  		testNotifs = func() *notifs {
    81  			n := &notifs{
    82  				p:   mockProxyRunner(pDaemonID),
    83  				nls: newListeners(),
    84  				fin: newListeners(),
    85  			}
    86  			smap := &smapX{Smap: meta.Smap{Version: 1}}
    87  			n.p.htrun.owner.smap = newSmapOwner(cmn.GCO.Get())
    88  			n.p.htrun.owner.smap.put(smap)
    89  			n.p.htrun.startup.cluster = *atomic.NewInt64(1)
    90  			return n
    91  		}
    92  
    93  		baseXact = func(xid string, counts ...int64) *core.Snap {
    94  			var (
    95  				objCount  int64
    96  				byteCount int64
    97  			)
    98  			if len(counts) > 0 {
    99  				objCount = counts[0]
   100  			}
   101  			if len(counts) > 1 {
   102  				byteCount = counts[1]
   103  			}
   104  			return &core.Snap{
   105  				ID: xid,
   106  				Stats: core.Stats{
   107  					Bytes: byteCount,
   108  					Objs:  objCount,
   109  				}}
   110  		}
   111  
   112  		finishedXact = func(xid string, counts ...int64) (snap *core.Snap) {
   113  			snap = baseXact(xid, counts...)
   114  			snap.EndTime = time.Now()
   115  			return
   116  		}
   117  
   118  		abortedXact = func(xid string, counts ...int64) (snap *core.Snap) {
   119  			snap = finishedXact(xid, counts...)
   120  			snap.AbortedX = true
   121  			return
   122  		}
   123  
   124  		notifRequest = func(daeID, xid, notifKind string, stats *core.Snap) *http.Request {
   125  			nm := core.NotifMsg{
   126  				UUID:     xid,
   127  				Data:     cos.MustMarshal(stats),
   128  				AbortedX: stats.AbortedX,
   129  			}
   130  			body := bytes.NewBuffer(cos.MustMarshal(nm))
   131  			req := httptest.NewRequest(http.MethodPost, apc.URLPathNotifs.Join(notifKind), body)
   132  			req.Header = make(http.Header)
   133  			req.Header.Add(apc.HdrCallerID, daeID)
   134  			return req
   135  		}
   136  
   137  		checkRequest = func(n *notifs, req *http.Request, expectedStatus int) []byte {
   138  			writer := httptest.NewRecorder()
   139  			n.handler(writer, req)
   140  			resp := writer.Result()
   141  			respBody, _ := io.ReadAll(resp.Body)
   142  			resp.Body.Close()
   143  			Expect(resp.StatusCode).To(BeEquivalentTo(expectedStatus))
   144  			return respBody
   145  		}
   146  	)
   147  
   148  	var (
   149  		n       *notifs
   150  		smap    = &smapX{}
   151  		nl      nl.Listener
   152  		targets = getNodeMap(target1ID, target2ID)
   153  	)
   154  
   155  	BeforeEach(func() {
   156  		n = testNotifs()
   157  		nl = xact.NewXactNL(xid, apc.ActECEncode, &smap.Smap, targets)
   158  	})
   159  
   160  	Describe("handleMsg", func() {
   161  		It("should add node to finished set on receiving finished stats", func() {
   162  			Expect(nl.FinCount()).To(BeEquivalentTo(0))
   163  			snap := finishedXact(xid)
   164  			msg := &core.NotifMsg{Data: cos.MustMarshal(snap)}
   165  			n._finished(nl, targets[target1ID], msg)
   166  			Expect(nl.ActiveNotifiers().Contains(target1ID)).To(BeFalse())
   167  			Expect(nl.Finished()).To(BeFalse())
   168  		})
   169  
   170  		It("should set error when source sends an error message", func() {
   171  			Expect(nl.Err()).To(BeNil())
   172  			snap := finishedXact(xid)
   173  			msg := &core.NotifMsg{Data: cos.MustMarshal(snap), ErrMsg: "some error"}
   174  			n._finished(nl, targets[target1ID], msg)
   175  			Expect(msg.ErrMsg).To(BeEquivalentTo(nl.Err().Error()))
   176  			Expect(nl.ActiveNotifiers().Contains(target1ID)).To(BeFalse())
   177  		})
   178  
   179  		It("should finish when all the Notifiers finished", func() {
   180  			Expect(nl.FinCount()).To(BeEquivalentTo(0))
   181  			n.add(nl)
   182  			snap := finishedXact(xid)
   183  			msg := &core.NotifMsg{Data: cos.MustMarshal(snap)}
   184  			n._finished(nl, targets[target1ID], msg)
   185  			n._finished(nl, targets[target2ID], msg)
   186  			Expect(nl.FinCount()).To(BeEquivalentTo(len(targets)))
   187  			Expect(nl.Finished()).To(BeTrue())
   188  		})
   189  
   190  		It("should be done if xaction Aborted", func() {
   191  			snap := abortedXact(xid)
   192  			msg := &core.NotifMsg{Data: cos.MustMarshal(snap), AbortedX: snap.AbortedX}
   193  			n._finished(nl, targets[target1ID], msg)
   194  			Expect(nl.Aborted()).To(BeTrue())
   195  			Expect(nl.Err()).NotTo(BeNil())
   196  		})
   197  
   198  		It("should update local stats upon progress", func() {
   199  			var (
   200  				initObjCount     int64 = 5
   201  				initByteCount    int64 = 30
   202  				updatedObjCount  int64 = 10
   203  				updatedByteCount int64 = 120
   204  			)
   205  
   206  			statsFirst := baseXact(xid, initObjCount, initByteCount)
   207  			statsProgress := baseXact(xid, updatedObjCount, updatedByteCount)
   208  
   209  			// Handle fist set of stats
   210  			msg := &core.NotifMsg{Data: cos.MustMarshal(statsFirst)}
   211  			nl.Lock()
   212  			n._progress(nl, targets[target1ID], msg)
   213  			nl.Unlock()
   214  			val, _ := nl.NodeStats().Load(target1ID)
   215  			snap, ok := val.(*core.Snap)
   216  			Expect(ok).To(BeTrue())
   217  			Expect(snap.Stats.Objs).To(BeEquivalentTo(initObjCount))
   218  			Expect(snap.Stats.Bytes).To(BeEquivalentTo(initByteCount))
   219  
   220  			// Next a Finished notification with stats
   221  			msg = &core.NotifMsg{Data: cos.MustMarshal(statsProgress)}
   222  			n._finished(nl, targets[target1ID], msg)
   223  			val, _ = nl.NodeStats().Load(target1ID)
   224  			snap, ok = val.(*core.Snap)
   225  			Expect(ok).To(BeTrue())
   226  			Expect(snap.Stats.Objs).To(BeEquivalentTo(updatedObjCount))
   227  			Expect(snap.Stats.Bytes).To(BeEquivalentTo(updatedByteCount))
   228  		})
   229  	})
   230  
   231  	Describe("ListenSmapChanged", func() {
   232  		It("should mark xaction Aborted when node not in smap", func() {
   233  			notifiers := getNodeMap(target1ID, target2ID)
   234  			nl = xact.NewXactNL(xid, apc.ActECEncode, &smap.Smap, notifiers)
   235  			n = testNotifs()
   236  			n.add(nl)
   237  
   238  			// Update smap, remove a target
   239  			smap := n.p.owner.smap.get()
   240  			smap.Tmap = getNodeMap(target1ID) // target 2 removed
   241  			smap.Version++
   242  			n.p.owner.smap.put(smap)
   243  
   244  			n.ListenSmapChanged()
   245  			Expect(nl.Finished()).To(BeTrue())
   246  			Expect(nl.Aborted()).To(BeTrue())
   247  		})
   248  	})
   249  
   250  	Describe("handler", func() {
   251  		It("should mark xaction finished when done", func() {
   252  			stats := finishedXact(xid)
   253  			n.add(nl)
   254  
   255  			request := notifRequest(target1ID, xid, apc.Finished, stats)
   256  			checkRequest(n, request, http.StatusOK)
   257  
   258  			// Second target sends progress
   259  			request = notifRequest(target2ID, xid, apc.Progress, stats)
   260  			checkRequest(n, request, http.StatusOK)
   261  
   262  			// `nl` should not be marked finished on progress notification
   263  			Expect(nl.Finished()).To(BeFalse())
   264  
   265  			// Second target finished
   266  			request = notifRequest(target2ID, xid, apc.Finished, stats)
   267  			checkRequest(n, request, http.StatusOK)
   268  
   269  			// `nl` should be marked finished
   270  			Expect(nl.Finished()).To(BeTrue())
   271  		})
   272  
   273  		It("should accept finished notifications after a target aborts", func() {
   274  			stats := finishedXact(xid)
   275  			abortStats := abortedXact(xid)
   276  			n.add(nl)
   277  
   278  			// First target aborts an xaction
   279  			request := notifRequest(target1ID, xid, apc.Finished, abortStats)
   280  			checkRequest(n, request, http.StatusOK)
   281  
   282  			// `nl` should be marked finished when an xaction aborts
   283  			Expect(nl.Finished()).To(BeTrue())
   284  			Expect(nl.FinCount()).To(BeEquivalentTo(1))
   285  
   286  			// Second target sends finished stats
   287  			request = notifRequest(target2ID, xid, apc.Finished, stats)
   288  			checkRequest(n, request, http.StatusOK)
   289  			Expect(nl.Finished()).To(BeTrue())
   290  			Expect(nl.FinCount()).To(BeEquivalentTo(2))
   291  		})
   292  	})
   293  })