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  }