git.frostfs.info/TrueCloudLab/frostfs-sdk-go@v0.0.0-20241022124111-5361f0ecebd3/netmap/node_info.go (about)

     1  package netmap
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"slices"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap"
    11  	frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
    12  	"git.frostfs.info/TrueCloudLab/hrw"
    13  )
    14  
    15  // NodeInfo groups information about FrostFS storage node which is reflected
    16  // in the FrostFS network map. Storage nodes advertise this information when
    17  // registering with the FrostFS network. After successful registration, information
    18  // about the nodes is available to all network participants to work with the network
    19  // map (mainly to comply with container storage policies).
    20  //
    21  // NodeInfo is mutually compatible with git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap.NodeInfo
    22  // message. See ReadFromV2 / WriteToV2 methods.
    23  //
    24  // Instances can be created using built-in var declaration.
    25  type NodeInfo struct {
    26  	m    netmap.NodeInfo
    27  	hash uint64
    28  }
    29  
    30  // reads NodeInfo from netmap.NodeInfo message. If checkFieldPresence is set,
    31  // returns an error on absence of any protocol-required field. Verifies format of any
    32  // presented field according to FrostFS API V2 protocol.
    33  func (x *NodeInfo) readFromV2(m netmap.NodeInfo, checkFieldPresence bool) error {
    34  	var err error
    35  
    36  	binPublicKey := m.GetPublicKey()
    37  	if checkFieldPresence && len(binPublicKey) == 0 {
    38  		return errors.New("missing public key")
    39  	}
    40  
    41  	if checkFieldPresence && m.NumberOfAddresses() <= 0 {
    42  		return errors.New("missing network endpoints")
    43  	}
    44  
    45  	attributes := m.GetAttributes()
    46  	mAttr := make(map[string]struct{}, len(attributes))
    47  	for i := range attributes {
    48  		key := attributes[i].GetKey()
    49  		if key == "" {
    50  			return fmt.Errorf("empty key of the attribute #%d", i)
    51  		} else if _, ok := mAttr[key]; ok {
    52  			return fmt.Errorf("duplicated attbiuted %s", key)
    53  		}
    54  
    55  		switch {
    56  		case key == attrCapacity:
    57  			_, err = strconv.ParseUint(attributes[i].GetValue(), 10, 64)
    58  			if err != nil {
    59  				return fmt.Errorf("invalid %s attribute: %w", attrCapacity, err)
    60  			}
    61  		case key == attrPrice:
    62  			var err error
    63  			_, err = strconv.ParseUint(attributes[i].GetValue(), 10, 64)
    64  			if err != nil {
    65  				return fmt.Errorf("invalid %s attribute: %w", attrPrice, err)
    66  			}
    67  		default:
    68  			if attributes[i].GetValue() == "" {
    69  				return fmt.Errorf("empty value of the attribute %s", key)
    70  			}
    71  		}
    72  	}
    73  
    74  	x.m = m
    75  	x.hash = hrw.Hash(binPublicKey)
    76  
    77  	return nil
    78  }
    79  
    80  // ReadFromV2 reads NodeInfo from the netmap.NodeInfo message. Checks if the
    81  // message conforms to FrostFS API V2 protocol.
    82  //
    83  // See also WriteToV2.
    84  func (x *NodeInfo) ReadFromV2(m netmap.NodeInfo) error {
    85  	return x.readFromV2(m, true)
    86  }
    87  
    88  // WriteToV2 writes NodeInfo to the netmap.NodeInfo message. The message MUST NOT
    89  // be nil.
    90  //
    91  // See also ReadFromV2.
    92  func (x NodeInfo) WriteToV2(m *netmap.NodeInfo) {
    93  	*m = x.m
    94  }
    95  
    96  // Marshal encodes NodeInfo into a binary format of the FrostFS API protocol
    97  // (Protocol Buffers with direct field order).
    98  //
    99  // See also Unmarshal.
   100  func (x NodeInfo) Marshal() []byte {
   101  	var m netmap.NodeInfo
   102  	x.WriteToV2(&m)
   103  
   104  	return m.StableMarshal(nil)
   105  }
   106  
   107  // Unmarshal decodes FrostFS API protocol binary format into the NodeInfo
   108  // (Protocol Buffers with direct field order). Returns an error describing
   109  // a format violation.
   110  //
   111  // See also Marshal.
   112  func (x *NodeInfo) Unmarshal(data []byte) error {
   113  	var m netmap.NodeInfo
   114  
   115  	err := m.Unmarshal(data)
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	return x.readFromV2(m, false)
   121  }
   122  
   123  // MarshalJSON encodes NodeInfo into a JSON format of the FrostFS API protocol
   124  // (Protocol Buffers JSON).
   125  //
   126  // See also UnmarshalJSON.
   127  func (x NodeInfo) MarshalJSON() ([]byte, error) {
   128  	var m netmap.NodeInfo
   129  	x.WriteToV2(&m)
   130  
   131  	return m.MarshalJSON()
   132  }
   133  
   134  // UnmarshalJSON decodes FrostFS API protocol JSON format into the NodeInfo
   135  // (Protocol Buffers JSON). Returns an error describing a format violation.
   136  //
   137  // See also MarshalJSON.
   138  func (x *NodeInfo) UnmarshalJSON(data []byte) error {
   139  	var m netmap.NodeInfo
   140  
   141  	err := m.UnmarshalJSON(data)
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	return x.readFromV2(m, false)
   147  }
   148  
   149  // SetPublicKey sets binary-encoded public key bound to the node. The key
   150  // authenticates the storage node, so it MUST be unique within the network.
   151  //
   152  // Argument MUST NOT be mutated, make a copy first.
   153  //
   154  // See also PublicKey.
   155  func (x *NodeInfo) SetPublicKey(key []byte) {
   156  	x.m.SetPublicKey(key)
   157  	x.hash = hrw.Hash(x.m.GetPublicKey())
   158  }
   159  
   160  // PublicKey returns value set using SetPublicKey.
   161  //
   162  // Zero NodeInfo has no public key, which is incorrect according to
   163  // FrostFS system requirements.
   164  //
   165  // Return value MUST not be mutated, make a copy first.
   166  func (x NodeInfo) PublicKey() []byte {
   167  	return x.m.GetPublicKey()
   168  }
   169  
   170  // StringifyPublicKey returns HEX representation of PublicKey.
   171  func StringifyPublicKey(node NodeInfo) string {
   172  	return frostfscrypto.StringifyKeyBinary(node.PublicKey())
   173  }
   174  
   175  // SetNetworkEndpoints sets list to the announced node's network endpoints.
   176  // Node MUSt have at least one announced endpoint. List MUST be unique.
   177  // Endpoints are used for communication with the storage node within FrostFS
   178  // network. It is expected that node serves storage node services on these
   179  // endpoints (it also adds a wait on their network availability).
   180  //
   181  // Argument MUST NOT be mutated, make a copy first.
   182  //
   183  // See also IterateNetworkEndpoints.
   184  func (x *NodeInfo) SetNetworkEndpoints(v ...string) {
   185  	x.m.SetAddresses(v...)
   186  }
   187  
   188  // NumberOfNetworkEndpoints returns number of network endpoints announced by the node.
   189  //
   190  // See also SetNetworkEndpoints.
   191  func (x NodeInfo) NumberOfNetworkEndpoints() int {
   192  	return x.m.NumberOfAddresses()
   193  }
   194  
   195  // IterateNetworkEndpoints iterates over network endpoints announced by the
   196  // node and pass them into f. Breaks iteration on f's true return. Handler
   197  // MUST NOT be nil.
   198  //
   199  // Zero NodeInfo contains no endpoints which is incorrect according to
   200  // FrostFS system requirements.
   201  //
   202  // See also SetNetworkEndpoints.
   203  func (x NodeInfo) IterateNetworkEndpoints(f func(string) bool) {
   204  	x.m.IterateAddresses(f)
   205  }
   206  
   207  // IterateNetworkEndpoints is an extra-sugared function over IterateNetworkEndpoints
   208  // method which allows to unconditionally iterate over all node's network endpoints.
   209  func IterateNetworkEndpoints(node NodeInfo, f func(string)) {
   210  	node.IterateNetworkEndpoints(func(addr string) bool {
   211  		f(addr)
   212  		return false
   213  	})
   214  }
   215  
   216  // assert NodeInfo type provides hrw.Hasher required for HRW sorting.
   217  var _ hrw.Hasher = NodeInfo{}
   218  
   219  // Hash implements hrw.Hasher interface.
   220  //
   221  // Hash is needed to support weighted HRW therefore sort function sorts nodes
   222  // based on their public key. Hash isn't expected to be used directly.
   223  func (x NodeInfo) Hash() uint64 {
   224  	if x.hash != 0 {
   225  		return x.hash
   226  	}
   227  	return hrw.Hash(x.m.GetPublicKey())
   228  }
   229  
   230  func (x *NodeInfo) setNumericAttribute(key string, num uint64) {
   231  	x.SetAttribute(key, strconv.FormatUint(num, 10))
   232  }
   233  
   234  // SetPrice sets the storage cost declared by the node. By default, zero
   235  // price is announced.
   236  func (x *NodeInfo) SetPrice(price uint64) {
   237  	x.setNumericAttribute(attrPrice, price)
   238  }
   239  
   240  // Price returns price set using SetPrice.
   241  //
   242  // Zero NodeInfo has zero price.
   243  func (x NodeInfo) Price() uint64 {
   244  	val := x.Attribute(attrPrice)
   245  	if val == "" {
   246  		return 0
   247  	}
   248  
   249  	price, err := strconv.ParseUint(val, 10, 64)
   250  	if err != nil {
   251  		panic(fmt.Sprintf("unexpected price parsing error %s: %v", val, err))
   252  	}
   253  
   254  	return price
   255  }
   256  
   257  // SetCapacity sets the storage capacity declared by the node. By default, zero
   258  // capacity is announced.
   259  func (x *NodeInfo) SetCapacity(capacity uint64) {
   260  	x.setNumericAttribute(attrCapacity, capacity)
   261  }
   262  
   263  // capacity returns capacity set using SetCapacity.
   264  //
   265  // Zero NodeInfo has zero capacity.
   266  func (x NodeInfo) capacity() uint64 {
   267  	val := x.Attribute(attrCapacity)
   268  	if val == "" {
   269  		return 0
   270  	}
   271  
   272  	capacity, err := strconv.ParseUint(val, 10, 64)
   273  	if err != nil {
   274  		panic(fmt.Sprintf("unexpected capacity parsing error %s: %v", val, err))
   275  	}
   276  
   277  	return capacity
   278  }
   279  
   280  const attrUNLOCODE = "UN-LOCODE"
   281  
   282  // SetLOCODE specifies node's geographic location in UN/LOCODE format. Each
   283  // storage node MUST declare it for entrance to the FrostFS network. Node MAY
   284  // declare the code of the nearest location as needed, for example, when it is
   285  // impossible to unambiguously attribute the node to any location from UN/LOCODE
   286  // database.
   287  //
   288  // See also LOCODE.
   289  func (x *NodeInfo) SetLOCODE(locode string) {
   290  	x.SetAttribute(attrUNLOCODE, locode)
   291  }
   292  
   293  // LOCODE returns node's location code set using SetLOCODE.
   294  //
   295  // Zero NodeInfo has empty location code which is invalid according to
   296  // FrostFS API system requirement.
   297  func (x NodeInfo) LOCODE() string {
   298  	return x.Attribute(attrUNLOCODE)
   299  }
   300  
   301  // SetCountryCode sets code of the country in ISO 3166-1_alpha-2 to which
   302  // storage node belongs (or the closest one).
   303  //
   304  // SetCountryCode is intended only for processing the network registration
   305  // request by the Inner Ring. Other parties SHOULD NOT use it.
   306  func (x *NodeInfo) SetCountryCode(countryCode string) {
   307  	x.SetAttribute("CountryCode", countryCode)
   308  }
   309  
   310  // SetCountryName sets short name of the country in ISO-3166 format to which
   311  // storage node belongs (or the closest one).
   312  //
   313  // SetCountryName is intended only for processing the network registration
   314  // request by the Inner Ring. Other parties SHOULD NOT use it.
   315  func (x *NodeInfo) SetCountryName(country string) {
   316  	x.SetAttribute("Country", country)
   317  }
   318  
   319  // SetLocationName sets storage node's location name from "NameWoDiacritics"
   320  // column in the UN/LOCODE record corresponding to the specified LOCODE.
   321  //
   322  // SetLocationName is intended only for processing the network registration
   323  // request by the Inner Ring. Other parties SHOULD NOT use it.
   324  func (x *NodeInfo) SetLocationName(location string) {
   325  	x.SetAttribute("Location", location)
   326  }
   327  
   328  // SetSubdivisionCode sets storage node's subdivision code from "SubDiv" column in
   329  // the UN/LOCODE record corresponding to the specified LOCODE.
   330  //
   331  // SetSubdivisionCode is intended only for processing the network registration
   332  // request by the Inner Ring. Other parties SHOULD NOT use it.
   333  func (x *NodeInfo) SetSubdivisionCode(subDiv string) {
   334  	x.SetAttribute("SubDivCode", subDiv)
   335  }
   336  
   337  // SetSubdivisionName sets storage node's subdivision name in ISO 3166-2 format.
   338  //
   339  // SetSubdivisionName is intended only for processing the network registration
   340  // request by the Inner Ring. Other parties SHOULD NOT use it.
   341  func (x *NodeInfo) SetSubdivisionName(subDiv string) {
   342  	x.SetAttribute("SubDiv", subDiv)
   343  }
   344  
   345  // SetContinentName sets name of the storage node's continent from
   346  // Seven-Continent model.
   347  //
   348  // SetContinentName is intended only for processing the network registration
   349  // request by the Inner Ring. Other parties SHOULD NOT use it.
   350  func (x *NodeInfo) SetContinentName(continent string) {
   351  	x.SetAttribute("Continent", continent)
   352  }
   353  
   354  // Enumeration of well-known attributes.
   355  const (
   356  	// attrPrice is a key to the node attribute that indicates the
   357  	// price in GAS tokens for storing one GB of data during one Epoch.
   358  	attrPrice = "Price"
   359  
   360  	// attrCapacity is a key to the node attribute that indicates the
   361  	// total available disk space in Gigabytes.
   362  	attrCapacity = "Capacity"
   363  
   364  	// attrExternalAddr is a key for the attribute storing node external addresses.
   365  	attrExternalAddr = "ExternalAddr"
   366  	// sepExternalAddr is a separator for multi-value ExternalAddr attribute.
   367  	sepExternalAddr = ","
   368  )
   369  
   370  // SetExternalAddresses sets multi-addresses to use
   371  // to connect to this node from outside.
   372  //
   373  // Panics if addr is an empty list.
   374  func (x *NodeInfo) SetExternalAddresses(addr ...string) {
   375  	x.SetAttribute(attrExternalAddr, strings.Join(addr, sepExternalAddr))
   376  }
   377  
   378  // ExternalAddresses returns list of multi-addresses to use
   379  // to connect to this node from outside.
   380  func (x NodeInfo) ExternalAddresses() []string {
   381  	a := x.Attribute(attrExternalAddr)
   382  	if len(a) == 0 {
   383  		return nil
   384  	}
   385  
   386  	return strings.Split(a, sepExternalAddr)
   387  }
   388  
   389  // NumberOfAttributes returns number of attributes announced by the node.
   390  //
   391  // See also SetAttribute.
   392  func (x NodeInfo) NumberOfAttributes() int {
   393  	return len(x.m.GetAttributes())
   394  }
   395  
   396  // IterateAttributes iterates over all node attributes and passes the into f.
   397  // Handler MUST NOT be nil.
   398  func (x NodeInfo) IterateAttributes(f func(key, value string)) {
   399  	a := x.m.GetAttributes()
   400  	for i := range a {
   401  		f(a[i].GetKey(), a[i].GetValue())
   402  	}
   403  }
   404  
   405  // SetAttribute sets value of the node attribute value by the given key.
   406  // Both key and value MUST NOT be empty.
   407  func (x *NodeInfo) SetAttribute(key, value string) {
   408  	if key == "" {
   409  		panic("empty key in SetAttribute")
   410  	} else if value == "" {
   411  		panic("empty value in SetAttribute")
   412  	}
   413  
   414  	a := x.m.GetAttributes()
   415  	for i := range a {
   416  		if a[i].GetKey() == key {
   417  			a[i].SetValue(value)
   418  			return
   419  		}
   420  	}
   421  
   422  	a = append(a, netmap.Attribute{})
   423  	a[len(a)-1].SetKey(key)
   424  	a[len(a)-1].SetValue(value)
   425  
   426  	x.m.SetAttributes(a)
   427  }
   428  
   429  // Attribute returns value of the node attribute set using SetAttribute by the
   430  // given key. Returns empty string if attribute is missing.
   431  func (x NodeInfo) Attribute(key string) string {
   432  	a := x.m.GetAttributes()
   433  	for i := range a {
   434  		if a[i].GetKey() == key {
   435  			return a[i].GetValue()
   436  		}
   437  	}
   438  
   439  	return ""
   440  }
   441  
   442  // SortAttributes sorts node attributes set using SetAttribute lexicographically.
   443  // The method is only needed to make NodeInfo consistent, e.g. for signing.
   444  func (x *NodeInfo) SortAttributes() {
   445  	as := x.m.GetAttributes()
   446  	if len(as) == 0 {
   447  		return
   448  	}
   449  
   450  	slices.SortFunc(as, func(ai, aj netmap.Attribute) int {
   451  		if r := strings.Compare(ai.GetKey(), aj.GetKey()); r != 0 {
   452  			return r
   453  		}
   454  		return strings.Compare(ai.GetValue(), aj.GetValue())
   455  	})
   456  
   457  	x.m.SetAttributes(as)
   458  }
   459  
   460  // SetOffline sets the state of the node to "offline". When a node updates
   461  // information about itself in the network map, this action is interpreted as
   462  // an intention to leave the network.
   463  //
   464  // See also IsOffline.
   465  //
   466  // Deprecated: use SetStatus instead.
   467  func (x *NodeInfo) SetOffline() {
   468  	x.m.SetState(netmap.Offline)
   469  }
   470  
   471  // IsOffline checks if the node is in the "offline" state.
   472  //
   473  // Zero NodeInfo has undefined state which is not offline (note that it does not
   474  // mean online).
   475  //
   476  // See also SetOffline.
   477  //
   478  // Deprecated: use Status instead.
   479  func (x NodeInfo) IsOffline() bool {
   480  	return x.m.GetState() == netmap.Offline
   481  }
   482  
   483  // SetOnline sets the state of the node to "online". When a node updates
   484  // information about itself in the network map, this
   485  // action is interpreted as an intention to enter the network.
   486  //
   487  // See also IsOnline.
   488  //
   489  // Deprecated: use SetStatus instead.
   490  func (x *NodeInfo) SetOnline() {
   491  	x.m.SetState(netmap.Online)
   492  }
   493  
   494  // IsOnline checks if the node is in the "online" state.
   495  //
   496  // Zero NodeInfo has undefined state which is not online (note that it does not
   497  // mean offline).
   498  //
   499  // See also SetOnline.
   500  //
   501  // Deprecated: use Status instead.
   502  func (x NodeInfo) IsOnline() bool {
   503  	return x.m.GetState() == netmap.Online
   504  }
   505  
   506  // SetMaintenance sets the state of the node to "maintenance". When a node updates
   507  // information about itself in the network map, this
   508  // state declares temporal unavailability for a node.
   509  //
   510  // See also IsMaintenance.
   511  //
   512  // Deprecated: use SetStatus instead.
   513  func (x *NodeInfo) SetMaintenance() {
   514  	x.m.SetState(netmap.Maintenance)
   515  }
   516  
   517  // IsMaintenance checks if the node is in the "maintenance" state.
   518  //
   519  // Zero NodeInfo has undefined state.
   520  //
   521  // See also SetMaintenance.
   522  //
   523  // Deprecated: use Status instead.
   524  func (x NodeInfo) IsMaintenance() bool {
   525  	return x.m.GetState() == netmap.Maintenance
   526  }
   527  
   528  type NodeState netmap.NodeState
   529  
   530  const (
   531  	UnspecifiedState = NodeState(netmap.UnspecifiedState)
   532  	Online           = NodeState(netmap.Online)
   533  	Offline          = NodeState(netmap.Offline)
   534  	Maintenance      = NodeState(netmap.Maintenance)
   535  )
   536  
   537  // ToV2 converts NodeState to v2.
   538  func (ns NodeState) ToV2() netmap.NodeState {
   539  	return netmap.NodeState(ns)
   540  }
   541  
   542  // FromV2 reads NodeState to v2.
   543  func (ns *NodeState) FromV2(state netmap.NodeState) {
   544  	*ns = NodeState(state)
   545  }
   546  
   547  // Status returns the current state of the node in the network map.
   548  //
   549  // Zero NodeInfo has an undefined state, neither online nor offline.
   550  func (x NodeInfo) Status() NodeState {
   551  	return NodeState(x.m.GetState())
   552  }
   553  
   554  // SetState updates the state of the node in the network map.
   555  //
   556  // The state determines the node's current status within the network:
   557  //   - "online": Indicates the node intends to enter the network.
   558  //   - "offline": Indicates the node intends to leave the network.
   559  //   - "maintenance": Indicates the node is temporarily unavailable.
   560  //
   561  // See also Status.
   562  func (x *NodeInfo) SetStatus(state NodeState) {
   563  	x.m.SetState(netmap.NodeState(state))
   564  }
   565  
   566  // String implements fmt.Stringer.
   567  //
   568  // String is designed to be human-readable, and its format MAY differ between
   569  // SDK versions.
   570  func (ns NodeState) String() string {
   571  	return netmap.NodeState(ns).String()
   572  }
   573  
   574  // IsOnline checks if the current state is "online".
   575  func (ns NodeState) IsOnline() bool { return ns == Online }
   576  
   577  // IsOffline checks if the current state is "offline".
   578  func (ns NodeState) IsOffline() bool { return ns == Offline }
   579  
   580  // IsMaintenance checks if the current state is "maintenance".
   581  func (ns NodeState) IsMaintenance() bool { return ns == Maintenance }