github.com/TrueCloudLab/frostfs-api-go/v2@v2.0.0-20230228134343-196241c4e79a/netmap/attributes_test.go (about) 1 package netmap_test 2 3 import ( 4 "strconv" 5 "testing" 6 7 "github.com/TrueCloudLab/frostfs-api-go/v2/netmap" 8 netmaptest "github.com/TrueCloudLab/frostfs-api-go/v2/netmap/test" 9 "github.com/TrueCloudLab/frostfs-api-go/v2/refs" 10 "github.com/stretchr/testify/require" 11 ) 12 13 func subnetAttrKey(val string) string { 14 return "__NEOFS__SUBNET_" + val 15 } 16 17 func assertSubnetAttrKey(t *testing.T, attr *netmap.Attribute, num uint32) { 18 require.Equal(t, subnetAttrKey(strconv.FormatUint(uint64(num), 10)), attr.GetKey()) 19 } 20 21 func BenchmarkNodeAttributes(b *testing.B) { 22 const size = 50 23 24 id := new(refs.SubnetID) 25 id.SetValue(12) 26 27 attrs := make([]netmap.Attribute, size) 28 for i := range attrs { 29 if i == size/2 { 30 attrs[i] = *netmaptest.GenerateAttribute(false) 31 } else { 32 data, err := id.MarshalText() 33 require.NoError(b, err) 34 35 attrs[i].SetKey(subnetAttrKey(string(data))) 36 attrs[i].SetValue("True") 37 } 38 } 39 40 var info netmap.NodeSubnetInfo 41 info.SetID(id) 42 info.SetEntryFlag(false) 43 44 node := new(netmap.NodeInfo) 45 46 // When using a single slice `StartTimer` overhead is comparable to the 47 // function execution time, so we reduce this cost by updating slices in groups. 48 const cacheSize = 1000 49 a := make([][]netmap.Attribute, cacheSize) 50 for i := range a { 51 a[i] = make([]netmap.Attribute, size) 52 } 53 54 b.ResetTimer() 55 b.ReportAllocs() 56 for i := 0; i < b.N; i++ { 57 if i%cacheSize == 0 { 58 b.StopTimer() 59 for j := range a { 60 copy(a[j], attrs) 61 } 62 b.StartTimer() 63 } 64 node.SetAttributes(a[i%cacheSize]) 65 netmap.WriteSubnetInfo(node, info) 66 if len(node.GetAttributes())+1 != len(attrs) { 67 b.FailNow() 68 } 69 } 70 } 71 72 func TestWriteSubnetInfo(t *testing.T) { 73 t.Run("entry", func(t *testing.T) { 74 t.Run("zero subnet", func(t *testing.T) { 75 var ( 76 node netmap.NodeInfo 77 info netmap.NodeSubnetInfo 78 ) 79 80 netmap.WriteSubnetInfo(&node, info) 81 82 // entry to zero subnet does not require an attribute 83 attrs := node.GetAttributes() 84 require.Empty(t, attrs) 85 86 // exit the subnet 87 info.SetEntryFlag(false) 88 89 netmap.WriteSubnetInfo(&node, info) 90 91 // exit from zero subnet should be clearly reflected in attributes 92 attrs = node.GetAttributes() 93 require.Len(t, attrs, 1) 94 95 attr := &attrs[0] 96 assertSubnetAttrKey(t, attr, 0) 97 require.Equal(t, "False", attr.GetValue()) 98 99 // again enter to zero subnet 100 info.SetEntryFlag(true) 101 102 netmap.WriteSubnetInfo(&node, info) 103 104 // attribute should be removed 105 attrs = node.GetAttributes() 106 require.Empty(t, attrs) 107 }) 108 109 t.Run("non-zero subnet", func(t *testing.T) { 110 var ( 111 node netmap.NodeInfo 112 info netmap.NodeSubnetInfo 113 id refs.SubnetID 114 ) 115 116 // create non-zero subnet ID 117 const num = 15 118 119 id.SetValue(num) 120 121 // enter to the subnet 122 info.SetID(&id) 123 info.SetEntryFlag(true) 124 125 netmap.WriteSubnetInfo(&node, info) 126 127 // check attribute format 128 attrs := node.GetAttributes() 129 require.Len(t, attrs, 1) 130 131 attr := &attrs[0] 132 assertSubnetAttrKey(t, attr, num) 133 require.Equal(t, "True", attr.GetValue()) 134 135 // again exit the subnet 136 info.SetEntryFlag(false) 137 138 netmap.WriteSubnetInfo(&node, info) 139 140 // attribute should be removed 141 attrs = node.GetAttributes() 142 require.Empty(t, attrs) 143 }) 144 }) 145 } 146 147 func TestSubnets(t *testing.T) { 148 t.Run("empty", func(t *testing.T) { 149 var node netmap.NodeInfo 150 151 called := 0 152 153 err := netmap.IterateSubnets(&node, func(id refs.SubnetID) error { 154 called++ 155 156 require.True(t, refs.IsZeroSubnet(&id)) 157 158 return nil 159 }) 160 161 require.NoError(t, err) 162 require.EqualValues(t, 1, called) 163 }) 164 165 t.Run("with correct attribute", func(t *testing.T) { 166 var ( 167 node netmap.NodeInfo 168 169 attrEntry, attrExit netmap.Attribute 170 ) 171 172 const ( 173 numEntry = 13 174 numExit = 14 175 ) 176 177 attrEntry.SetKey(subnetAttrKey(strconv.FormatUint(numEntry, 10))) 178 attrEntry.SetValue("True") 179 180 attrExit.SetKey(subnetAttrKey(strconv.FormatUint(numExit, 10))) 181 attrExit.SetValue("False") 182 183 attrs := []netmap.Attribute{attrEntry, attrEntry} 184 185 node.SetAttributes(attrs) 186 187 mCalledNums := make(map[uint32]struct{}) 188 189 err := netmap.IterateSubnets(&node, func(id refs.SubnetID) error { 190 mCalledNums[id.GetValue()] = struct{}{} 191 192 return nil 193 }) 194 195 require.NoError(t, err) 196 require.Len(t, mCalledNums, 2) 197 198 _, ok := mCalledNums[numEntry] 199 require.True(t, ok) 200 201 _, ok = mCalledNums[numExit] 202 require.False(t, ok) 203 204 _, ok = mCalledNums[0] 205 require.True(t, ok) 206 }) 207 208 t.Run("with incorrect attribute", func(t *testing.T) { 209 assertErr := func(attr netmap.Attribute) { 210 var node netmap.NodeInfo 211 212 node.SetAttributes([]netmap.Attribute{attr}) 213 214 require.Error(t, netmap.IterateSubnets(&node, func(refs.SubnetID) error { 215 return nil 216 })) 217 } 218 219 t.Run("incorrect key", func(t *testing.T) { 220 var attr netmap.Attribute 221 222 attr.SetKey(subnetAttrKey("one-two-three")) 223 224 assertErr(attr) 225 }) 226 227 t.Run("incorrect value", func(t *testing.T) { 228 var attr netmap.Attribute 229 230 attr.SetKey(subnetAttrKey("1")) 231 232 for _, invalidVal := range []string{ 233 "", 234 "Troo", 235 "Fols", 236 } { 237 attr.SetValue(invalidVal) 238 assertErr(attr) 239 } 240 241 assertErr(attr) 242 }) 243 }) 244 245 t.Run("remove entry", func(t *testing.T) { 246 t.Run("zero", func(t *testing.T) { 247 var node netmap.NodeInfo 248 249 // enter to some non-zero subnet so that zero is not the only one 250 var attr netmap.Attribute 251 252 attr.SetKey(subnetAttrKey("321")) 253 attr.SetValue("True") 254 255 attrs := []netmap.Attribute{attr} 256 node.SetAttributes(attrs) 257 258 err := netmap.IterateSubnets(&node, func(id refs.SubnetID) error { 259 if refs.IsZeroSubnet(&id) { 260 return netmap.ErrRemoveSubnet 261 } 262 263 return nil 264 }) 265 266 require.NoError(t, err) 267 268 attrs = node.GetAttributes() 269 require.Len(t, attrs, 2) 270 271 found := false 272 273 for i := range attrs { 274 if attrs[i].GetKey() == subnetAttrKey("0") { 275 require.Equal(t, "False", attrs[i].GetValue()) 276 found = true 277 } 278 } 279 280 require.True(t, found) 281 }) 282 283 t.Run("non-zero", func(t *testing.T) { 284 var ( 285 node netmap.NodeInfo 286 attr netmap.Attribute 287 ) 288 289 attr.SetKey(subnetAttrKey("99")) 290 attr.SetValue("True") 291 292 attrs := []netmap.Attribute{attr} 293 node.SetAttributes(attrs) 294 295 err := netmap.IterateSubnets(&node, func(id refs.SubnetID) error { 296 if !refs.IsZeroSubnet(&id) { 297 return netmap.ErrRemoveSubnet 298 } 299 300 return nil 301 }) 302 303 require.NoError(t, err) 304 305 attrs = node.GetAttributes() 306 require.Empty(t, attrs) 307 }) 308 309 t.Run("all", func(t *testing.T) { 310 var ( 311 node netmap.NodeInfo 312 attrs []netmap.Attribute 313 ) 314 315 // enter to some non-zero subnet so that zero is not the only one 316 for i := 1; i <= 5; i++ { 317 var attr netmap.Attribute 318 319 attr.SetKey(subnetAttrKey(strconv.Itoa(i))) 320 attr.SetValue("True") 321 322 attrs = append(attrs, attr) 323 } 324 325 node.SetAttributes(attrs) 326 327 err := netmap.IterateSubnets(&node, func(id refs.SubnetID) error { 328 return netmap.ErrRemoveSubnet 329 }) 330 331 require.Error(t, err) 332 }) 333 }) 334 335 t.Run("zero subnet removal via attribute", func(t *testing.T) { 336 var ( 337 node netmap.NodeInfo 338 339 attrZero, attrOther netmap.Attribute 340 ) 341 342 attrZero.SetKey(subnetAttrKey("0")) 343 attrZero.SetValue("False") 344 345 attrOther.SetKey(subnetAttrKey("1")) 346 attrOther.SetValue("True") 347 348 node.SetAttributes([]netmap.Attribute{attrZero, attrOther}) 349 350 calledCount := 0 351 352 err := netmap.IterateSubnets(&node, func(id refs.SubnetID) error { 353 require.False(t, refs.IsZeroSubnet(&id)) 354 calledCount++ 355 return nil 356 }) 357 358 require.NoError(t, err) 359 require.EqualValues(t, 1, calledCount) 360 }) 361 }