github.com/TrueCloudLab/frostfs-api-go/v2@v2.0.0-20230228134343-196241c4e79a/netmap/attributes.go (about)

     1  package netmap
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/TrueCloudLab/frostfs-api-go/v2/refs"
     9  )
    10  
    11  // prefix of keys to subnet attributes.
    12  const attrSubnetPrefix = "__NEOFS__SUBNET_"
    13  
    14  const (
    15  	// subnet attribute's value denoting subnet entry
    16  	attrSubnetValEntry = "True"
    17  
    18  	// subnet attribute's value denoting subnet exit
    19  	attrSubnetValExit = "False"
    20  )
    21  
    22  // NodeSubnetInfo groups information about subnet which can be written to NodeInfo.
    23  //
    24  // Zero value represents entry to zero subnet.
    25  type NodeSubnetInfo struct {
    26  	exit bool
    27  
    28  	id *refs.SubnetID
    29  }
    30  
    31  // Enabled returns true iff subnet membership is enabled for the node.
    32  func (x NodeSubnetInfo) Enabled() bool {
    33  	return !x.exit
    34  }
    35  
    36  // SetEntryFlag sets the subnet entry flag.
    37  func (x *NodeSubnetInfo) SetEntryFlag(enters bool) {
    38  	x.exit = !enters
    39  }
    40  
    41  // ID returns identifier of the subnet.
    42  func (x NodeSubnetInfo) ID() *refs.SubnetID {
    43  	return x.id
    44  }
    45  
    46  // SetID sets identifier of the subnet.
    47  func (x *NodeSubnetInfo) SetID(id *refs.SubnetID) {
    48  	x.id = id
    49  }
    50  
    51  func subnetAttributeKey(id *refs.SubnetID) string {
    52  	txt, _ := id.MarshalText() // never returns an error
    53  
    54  	return attrSubnetPrefix + string(txt)
    55  }
    56  
    57  // WriteSubnetInfo writes NodeSubnetInfo to NodeInfo via attributes. NodeInfo must not be nil.
    58  //
    59  // Existing subnet attributes are expected to be key-unique, otherwise undefined behavior.
    60  //
    61  // Does not add (removes existing) attribute if node:
    62  //   - disables non-zero subnet;
    63  //   - enables zero subnet.
    64  //
    65  // Attribute key is calculated from ID using format `__NEOFS__SUBNET_%s`.
    66  // Attribute Value is:
    67  //   - `True` if node enters the subnet;
    68  //   - `False`, otherwise.
    69  func WriteSubnetInfo(node *NodeInfo, info NodeSubnetInfo) {
    70  	attrs := node.GetAttributes()
    71  
    72  	id := info.ID()
    73  	enters := info.Enabled()
    74  
    75  	// calculate attribute key
    76  	key := subnetAttributeKey(id)
    77  
    78  	if refs.IsZeroSubnet(id) == enters {
    79  		for i := range attrs {
    80  			if attrs[i].GetKey() == key {
    81  				attrs = append(attrs[:i], attrs[i+1:]...)
    82  				break // attributes are expected to be key-unique
    83  			}
    84  		}
    85  	} else {
    86  		var val string
    87  
    88  		if enters {
    89  			val = attrSubnetValEntry
    90  		} else {
    91  			val = attrSubnetValExit
    92  		}
    93  
    94  		presented := false
    95  
    96  		for i := range attrs {
    97  			if attrs[i].GetKey() == key {
    98  				attrs[i].SetValue(val)
    99  				presented = true
   100  			}
   101  		}
   102  
   103  		if !presented {
   104  			index := len(attrs)
   105  			attrs = append(attrs, Attribute{})
   106  			attrs[index].SetKey(key)
   107  			attrs[index].SetValue(val)
   108  		}
   109  	}
   110  
   111  	node.SetAttributes(attrs)
   112  }
   113  
   114  // ErrRemoveSubnet is returned when a node needs to leave the subnet.
   115  var ErrRemoveSubnet = errors.New("remove subnet")
   116  
   117  var errNoSubnets = errors.New("no subnets")
   118  
   119  // IterateSubnets iterates over all subnets the node belongs to and passes the IDs to f.
   120  // Handler must not be nil.
   121  //
   122  // Subnet attributes are expected to be key-unique, otherwise undefined behavior.
   123  //
   124  // If f returns ErrRemoveSubnet, then removes subnet entry. Note that this leads to an instant mutation of NodeInfo.
   125  // Breaks on any other non-nil error and returns it.
   126  //
   127  // Returns an error if any subnet attribute has wrong format.
   128  // Returns an error if the node is not included in any subnet by the end of the loop.
   129  func IterateSubnets(node *NodeInfo, f func(refs.SubnetID) error) error {
   130  	attrs := node.GetAttributes()
   131  
   132  	var (
   133  		err     error
   134  		id      refs.SubnetID
   135  		entries uint
   136  
   137  		zeroEntry = true
   138  	)
   139  
   140  	for i := 0; i < len(attrs); i++ { // range must not be used because of attrs mutation in body
   141  		key := attrs[i].GetKey()
   142  
   143  		// cut subnet ID string
   144  		idTxt := strings.TrimPrefix(key, attrSubnetPrefix)
   145  		if len(idTxt) == len(key) {
   146  			// not a subnet attribute
   147  			continue
   148  		}
   149  
   150  		// check value
   151  		val := attrs[i].GetValue()
   152  		if val != attrSubnetValExit && val != attrSubnetValEntry {
   153  			return fmt.Errorf("invalid attribute value: %s", val)
   154  		}
   155  
   156  		// decode subnet ID
   157  		if err = id.UnmarshalText([]byte(idTxt)); err != nil {
   158  			return fmt.Errorf("invalid ID text: %w", err)
   159  		}
   160  
   161  		// update status of zero subnet
   162  		isZero := refs.IsZeroSubnet(&id)
   163  
   164  		if isZero {
   165  			zeroEntry = val == attrSubnetValEntry
   166  		}
   167  
   168  		// continue to process only the subnets to which the node belongs
   169  		if val == attrSubnetValExit {
   170  			continue
   171  		}
   172  
   173  		// pass ID to the handler
   174  		err = f(id)
   175  
   176  		isRemoveErr := errors.Is(err, ErrRemoveSubnet)
   177  
   178  		if err != nil && !isRemoveErr {
   179  			return err
   180  		}
   181  
   182  		if isRemoveErr {
   183  			if isZero {
   184  				// we can't remove attribute of zero subnet because it means entry
   185  				attrs[i].SetValue(attrSubnetValExit)
   186  			} else {
   187  				// we can set False or remove attribute, latter is more memory/network efficient.
   188  				attrs = append(attrs[:i], attrs[i+1:]...)
   189  				i--
   190  			}
   191  
   192  			continue
   193  		}
   194  
   195  		entries++
   196  	}
   197  
   198  	if zeroEntry {
   199  		// missing attribute of zero subnet equivalent to entry
   200  		refs.MakeZeroSubnet(&id)
   201  
   202  		err = f(id)
   203  		if err != nil {
   204  			if !errors.Is(err, ErrRemoveSubnet) {
   205  				return err
   206  			}
   207  
   208  			// zero subnet should be clearly removed with False value
   209  			index := len(attrs)
   210  			attrs = append(attrs, Attribute{})
   211  			attrs[index].SetKey(subnetAttributeKey(&id))
   212  			attrs[index].SetValue(attrSubnetValExit)
   213  		} else {
   214  			entries++
   215  		}
   216  	}
   217  
   218  	if entries <= 0 {
   219  		return errNoSubnets
   220  	}
   221  
   222  	node.SetAttributes(attrs)
   223  
   224  	return nil
   225  }