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 := ¬ifs{ 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 })