forked from TrueCloudLab/frostfs-sdk-go
581 lines
17 KiB
Go
581 lines
17 KiB
Go
package netmap
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/netmap"
|
|
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
|
|
"git.frostfs.info/TrueCloudLab/hrw"
|
|
)
|
|
|
|
// NodeInfo groups information about FrostFS storage node which is reflected
|
|
// in the FrostFS network map. Storage nodes advertise this information when
|
|
// registering with the FrostFS network. After successful registration, information
|
|
// about the nodes is available to all network participants to work with the network
|
|
// map (mainly to comply with container storage policies).
|
|
//
|
|
// NodeInfo is mutually compatible with git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/netmap.NodeInfo
|
|
// message. See ReadFromV2 / WriteToV2 methods.
|
|
//
|
|
// Instances can be created using built-in var declaration.
|
|
type NodeInfo struct {
|
|
m netmap.NodeInfo
|
|
hash uint64
|
|
}
|
|
|
|
// reads NodeInfo from netmap.NodeInfo message. If checkFieldPresence is set,
|
|
// returns an error on absence of any protocol-required field. Verifies format of any
|
|
// presented field according to FrostFS API V2 protocol.
|
|
func (x *NodeInfo) readFromV2(m netmap.NodeInfo, checkFieldPresence bool) error {
|
|
var err error
|
|
|
|
binPublicKey := m.GetPublicKey()
|
|
if checkFieldPresence && len(binPublicKey) == 0 {
|
|
return errors.New("missing public key")
|
|
}
|
|
|
|
if checkFieldPresence && m.NumberOfAddresses() <= 0 {
|
|
return errors.New("missing network endpoints")
|
|
}
|
|
|
|
attributes := m.GetAttributes()
|
|
mAttr := make(map[string]struct{}, len(attributes))
|
|
for i := range attributes {
|
|
key := attributes[i].GetKey()
|
|
if key == "" {
|
|
return fmt.Errorf("empty key of the attribute #%d", i)
|
|
} else if _, ok := mAttr[key]; ok {
|
|
return fmt.Errorf("duplicated attbiuted %s", key)
|
|
}
|
|
|
|
switch {
|
|
case key == attrCapacity:
|
|
_, err = strconv.ParseUint(attributes[i].GetValue(), 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid %s attribute: %w", attrCapacity, err)
|
|
}
|
|
case key == attrPrice:
|
|
var err error
|
|
_, err = strconv.ParseUint(attributes[i].GetValue(), 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid %s attribute: %w", attrPrice, err)
|
|
}
|
|
default:
|
|
if attributes[i].GetValue() == "" {
|
|
return fmt.Errorf("empty value of the attribute %s", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
x.m = m
|
|
x.hash = hrw.Hash(binPublicKey)
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReadFromV2 reads NodeInfo from the netmap.NodeInfo message. Checks if the
|
|
// message conforms to FrostFS API V2 protocol.
|
|
//
|
|
// See also WriteToV2.
|
|
func (x *NodeInfo) ReadFromV2(m netmap.NodeInfo) error {
|
|
return x.readFromV2(m, true)
|
|
}
|
|
|
|
// WriteToV2 writes NodeInfo to the netmap.NodeInfo message. The message MUST NOT
|
|
// be nil.
|
|
//
|
|
// See also ReadFromV2.
|
|
func (x NodeInfo) WriteToV2(m *netmap.NodeInfo) {
|
|
*m = x.m
|
|
}
|
|
|
|
// Marshal encodes NodeInfo into a binary format of the FrostFS API protocol
|
|
// (Protocol Buffers with direct field order).
|
|
//
|
|
// See also Unmarshal.
|
|
func (x NodeInfo) Marshal() []byte {
|
|
var m netmap.NodeInfo
|
|
x.WriteToV2(&m)
|
|
|
|
return m.StableMarshal(nil)
|
|
}
|
|
|
|
// Unmarshal decodes FrostFS API protocol binary format into the NodeInfo
|
|
// (Protocol Buffers with direct field order). Returns an error describing
|
|
// a format violation.
|
|
//
|
|
// See also Marshal.
|
|
func (x *NodeInfo) Unmarshal(data []byte) error {
|
|
var m netmap.NodeInfo
|
|
|
|
err := m.Unmarshal(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return x.readFromV2(m, false)
|
|
}
|
|
|
|
// MarshalJSON encodes NodeInfo into a JSON format of the FrostFS API protocol
|
|
// (Protocol Buffers JSON).
|
|
//
|
|
// See also UnmarshalJSON.
|
|
func (x NodeInfo) MarshalJSON() ([]byte, error) {
|
|
var m netmap.NodeInfo
|
|
x.WriteToV2(&m)
|
|
|
|
return m.MarshalJSON()
|
|
}
|
|
|
|
// UnmarshalJSON decodes FrostFS API protocol JSON format into the NodeInfo
|
|
// (Protocol Buffers JSON). Returns an error describing a format violation.
|
|
//
|
|
// See also MarshalJSON.
|
|
func (x *NodeInfo) UnmarshalJSON(data []byte) error {
|
|
var m netmap.NodeInfo
|
|
|
|
err := m.UnmarshalJSON(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return x.readFromV2(m, false)
|
|
}
|
|
|
|
// SetPublicKey sets binary-encoded public key bound to the node. The key
|
|
// authenticates the storage node, so it MUST be unique within the network.
|
|
//
|
|
// Argument MUST NOT be mutated, make a copy first.
|
|
//
|
|
// See also PublicKey.
|
|
func (x *NodeInfo) SetPublicKey(key []byte) {
|
|
x.m.SetPublicKey(key)
|
|
x.hash = hrw.Hash(x.m.GetPublicKey())
|
|
}
|
|
|
|
// PublicKey returns value set using SetPublicKey.
|
|
//
|
|
// Zero NodeInfo has no public key, which is incorrect according to
|
|
// FrostFS system requirements.
|
|
//
|
|
// Return value MUST not be mutated, make a copy first.
|
|
func (x NodeInfo) PublicKey() []byte {
|
|
return x.m.GetPublicKey()
|
|
}
|
|
|
|
// StringifyPublicKey returns HEX representation of PublicKey.
|
|
func StringifyPublicKey(node NodeInfo) string {
|
|
return frostfscrypto.StringifyKeyBinary(node.PublicKey())
|
|
}
|
|
|
|
// SetNetworkEndpoints sets list to the announced node's network endpoints.
|
|
// Node MUSt have at least one announced endpoint. List MUST be unique.
|
|
// Endpoints are used for communication with the storage node within FrostFS
|
|
// network. It is expected that node serves storage node services on these
|
|
// endpoints (it also adds a wait on their network availability).
|
|
//
|
|
// Argument MUST NOT be mutated, make a copy first.
|
|
//
|
|
// See also IterateNetworkEndpoints.
|
|
func (x *NodeInfo) SetNetworkEndpoints(v ...string) {
|
|
x.m.SetAddresses(v...)
|
|
}
|
|
|
|
// NumberOfNetworkEndpoints returns number of network endpoints announced by the node.
|
|
//
|
|
// See also SetNetworkEndpoints.
|
|
func (x NodeInfo) NumberOfNetworkEndpoints() int {
|
|
return x.m.NumberOfAddresses()
|
|
}
|
|
|
|
// IterateNetworkEndpoints iterates over network endpoints announced by the
|
|
// node and pass them into f. Breaks iteration on f's true return. Handler
|
|
// MUST NOT be nil.
|
|
//
|
|
// Zero NodeInfo contains no endpoints which is incorrect according to
|
|
// FrostFS system requirements.
|
|
//
|
|
// See also SetNetworkEndpoints.
|
|
func (x NodeInfo) IterateNetworkEndpoints(f func(string) bool) {
|
|
x.m.IterateAddresses(f)
|
|
}
|
|
|
|
// IterateNetworkEndpoints is an extra-sugared function over IterateNetworkEndpoints
|
|
// method which allows to unconditionally iterate over all node's network endpoints.
|
|
func IterateNetworkEndpoints(node NodeInfo, f func(string)) {
|
|
node.IterateNetworkEndpoints(func(addr string) bool {
|
|
f(addr)
|
|
return false
|
|
})
|
|
}
|
|
|
|
// assert NodeInfo type provides hrw.Hasher required for HRW sorting.
|
|
var _ hrw.Hasher = NodeInfo{}
|
|
|
|
// Hash implements hrw.Hasher interface.
|
|
//
|
|
// Hash is needed to support weighted HRW therefore sort function sorts nodes
|
|
// based on their public key. Hash isn't expected to be used directly.
|
|
func (x NodeInfo) Hash() uint64 {
|
|
if x.hash != 0 {
|
|
return x.hash
|
|
}
|
|
return hrw.Hash(x.m.GetPublicKey())
|
|
}
|
|
|
|
func (x *NodeInfo) setNumericAttribute(key string, num uint64) {
|
|
x.SetAttribute(key, strconv.FormatUint(num, 10))
|
|
}
|
|
|
|
// SetPrice sets the storage cost declared by the node. By default, zero
|
|
// price is announced.
|
|
func (x *NodeInfo) SetPrice(price uint64) {
|
|
x.setNumericAttribute(attrPrice, price)
|
|
}
|
|
|
|
// Price returns price set using SetPrice.
|
|
//
|
|
// Zero NodeInfo has zero price.
|
|
func (x NodeInfo) Price() uint64 {
|
|
val := x.Attribute(attrPrice)
|
|
if val == "" {
|
|
return 0
|
|
}
|
|
|
|
price, err := strconv.ParseUint(val, 10, 64)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("unexpected price parsing error %s: %v", val, err))
|
|
}
|
|
|
|
return price
|
|
}
|
|
|
|
// SetCapacity sets the storage capacity declared by the node. By default, zero
|
|
// capacity is announced.
|
|
func (x *NodeInfo) SetCapacity(capacity uint64) {
|
|
x.setNumericAttribute(attrCapacity, capacity)
|
|
}
|
|
|
|
// capacity returns capacity set using SetCapacity.
|
|
//
|
|
// Zero NodeInfo has zero capacity.
|
|
func (x NodeInfo) capacity() uint64 {
|
|
val := x.Attribute(attrCapacity)
|
|
if val == "" {
|
|
return 0
|
|
}
|
|
|
|
capacity, err := strconv.ParseUint(val, 10, 64)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("unexpected capacity parsing error %s: %v", val, err))
|
|
}
|
|
|
|
return capacity
|
|
}
|
|
|
|
const attrUNLOCODE = "UN-LOCODE"
|
|
|
|
// SetLOCODE specifies node's geographic location in UN/LOCODE format. Each
|
|
// storage node MUST declare it for entrance to the FrostFS network. Node MAY
|
|
// declare the code of the nearest location as needed, for example, when it is
|
|
// impossible to unambiguously attribute the node to any location from UN/LOCODE
|
|
// database.
|
|
//
|
|
// See also LOCODE.
|
|
func (x *NodeInfo) SetLOCODE(locode string) {
|
|
x.SetAttribute(attrUNLOCODE, locode)
|
|
}
|
|
|
|
// LOCODE returns node's location code set using SetLOCODE.
|
|
//
|
|
// Zero NodeInfo has empty location code which is invalid according to
|
|
// FrostFS API system requirement.
|
|
func (x NodeInfo) LOCODE() string {
|
|
return x.Attribute(attrUNLOCODE)
|
|
}
|
|
|
|
// SetCountryCode sets code of the country in ISO 3166-1_alpha-2 to which
|
|
// storage node belongs (or the closest one).
|
|
//
|
|
// SetCountryCode is intended only for processing the network registration
|
|
// request by the Inner Ring. Other parties SHOULD NOT use it.
|
|
func (x *NodeInfo) SetCountryCode(countryCode string) {
|
|
x.SetAttribute("CountryCode", countryCode)
|
|
}
|
|
|
|
// SetCountryName sets short name of the country in ISO-3166 format to which
|
|
// storage node belongs (or the closest one).
|
|
//
|
|
// SetCountryName is intended only for processing the network registration
|
|
// request by the Inner Ring. Other parties SHOULD NOT use it.
|
|
func (x *NodeInfo) SetCountryName(country string) {
|
|
x.SetAttribute("Country", country)
|
|
}
|
|
|
|
// SetLocationName sets storage node's location name from "NameWoDiacritics"
|
|
// column in the UN/LOCODE record corresponding to the specified LOCODE.
|
|
//
|
|
// SetLocationName is intended only for processing the network registration
|
|
// request by the Inner Ring. Other parties SHOULD NOT use it.
|
|
func (x *NodeInfo) SetLocationName(location string) {
|
|
x.SetAttribute("Location", location)
|
|
}
|
|
|
|
// SetSubdivisionCode sets storage node's subdivision code from "SubDiv" column in
|
|
// the UN/LOCODE record corresponding to the specified LOCODE.
|
|
//
|
|
// SetSubdivisionCode is intended only for processing the network registration
|
|
// request by the Inner Ring. Other parties SHOULD NOT use it.
|
|
func (x *NodeInfo) SetSubdivisionCode(subDiv string) {
|
|
x.SetAttribute("SubDivCode", subDiv)
|
|
}
|
|
|
|
// SetSubdivisionName sets storage node's subdivision name in ISO 3166-2 format.
|
|
//
|
|
// SetSubdivisionName is intended only for processing the network registration
|
|
// request by the Inner Ring. Other parties SHOULD NOT use it.
|
|
func (x *NodeInfo) SetSubdivisionName(subDiv string) {
|
|
x.SetAttribute("SubDiv", subDiv)
|
|
}
|
|
|
|
// SetContinentName sets name of the storage node's continent from
|
|
// Seven-Continent model.
|
|
//
|
|
// SetContinentName is intended only for processing the network registration
|
|
// request by the Inner Ring. Other parties SHOULD NOT use it.
|
|
func (x *NodeInfo) SetContinentName(continent string) {
|
|
x.SetAttribute("Continent", continent)
|
|
}
|
|
|
|
// Enumeration of well-known attributes.
|
|
const (
|
|
// attrPrice is a key to the node attribute that indicates the
|
|
// price in GAS tokens for storing one GB of data during one Epoch.
|
|
attrPrice = "Price"
|
|
|
|
// attrCapacity is a key to the node attribute that indicates the
|
|
// total available disk space in Gigabytes.
|
|
attrCapacity = "Capacity"
|
|
|
|
// attrExternalAddr is a key for the attribute storing node external addresses.
|
|
attrExternalAddr = "ExternalAddr"
|
|
// sepExternalAddr is a separator for multi-value ExternalAddr attribute.
|
|
sepExternalAddr = ","
|
|
)
|
|
|
|
// SetExternalAddresses sets multi-addresses to use
|
|
// to connect to this node from outside.
|
|
//
|
|
// Panics if addr is an empty list.
|
|
func (x *NodeInfo) SetExternalAddresses(addr ...string) {
|
|
x.SetAttribute(attrExternalAddr, strings.Join(addr, sepExternalAddr))
|
|
}
|
|
|
|
// ExternalAddresses returns list of multi-addresses to use
|
|
// to connect to this node from outside.
|
|
func (x NodeInfo) ExternalAddresses() []string {
|
|
a := x.Attribute(attrExternalAddr)
|
|
if len(a) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return strings.Split(a, sepExternalAddr)
|
|
}
|
|
|
|
// NumberOfAttributes returns number of attributes announced by the node.
|
|
//
|
|
// See also SetAttribute.
|
|
func (x NodeInfo) NumberOfAttributes() int {
|
|
return len(x.m.GetAttributes())
|
|
}
|
|
|
|
// IterateAttributes iterates over all node attributes and passes the into f.
|
|
// Handler MUST NOT be nil.
|
|
func (x NodeInfo) IterateAttributes(f func(key, value string)) {
|
|
a := x.m.GetAttributes()
|
|
for i := range a {
|
|
f(a[i].GetKey(), a[i].GetValue())
|
|
}
|
|
}
|
|
|
|
// SetAttribute sets value of the node attribute value by the given key.
|
|
// Both key and value MUST NOT be empty.
|
|
func (x *NodeInfo) SetAttribute(key, value string) {
|
|
if key == "" {
|
|
panic("empty key in SetAttribute")
|
|
} else if value == "" {
|
|
panic("empty value in SetAttribute")
|
|
}
|
|
|
|
a := x.m.GetAttributes()
|
|
for i := range a {
|
|
if a[i].GetKey() == key {
|
|
a[i].SetValue(value)
|
|
return
|
|
}
|
|
}
|
|
|
|
a = append(a, netmap.Attribute{})
|
|
a[len(a)-1].SetKey(key)
|
|
a[len(a)-1].SetValue(value)
|
|
|
|
x.m.SetAttributes(a)
|
|
}
|
|
|
|
// Attribute returns value of the node attribute set using SetAttribute by the
|
|
// given key. Returns empty string if attribute is missing.
|
|
func (x NodeInfo) Attribute(key string) string {
|
|
a := x.m.GetAttributes()
|
|
for i := range a {
|
|
if a[i].GetKey() == key {
|
|
return a[i].GetValue()
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// SortAttributes sorts node attributes set using SetAttribute lexicographically.
|
|
// The method is only needed to make NodeInfo consistent, e.g. for signing.
|
|
func (x *NodeInfo) SortAttributes() {
|
|
as := x.m.GetAttributes()
|
|
if len(as) == 0 {
|
|
return
|
|
}
|
|
|
|
slices.SortFunc(as, func(ai, aj netmap.Attribute) int {
|
|
if r := strings.Compare(ai.GetKey(), aj.GetKey()); r != 0 {
|
|
return r
|
|
}
|
|
return strings.Compare(ai.GetValue(), aj.GetValue())
|
|
})
|
|
|
|
x.m.SetAttributes(as)
|
|
}
|
|
|
|
// SetOffline sets the state of the node to "offline". When a node updates
|
|
// information about itself in the network map, this action is interpreted as
|
|
// an intention to leave the network.
|
|
//
|
|
// See also IsOffline.
|
|
//
|
|
// Deprecated: use SetStatus instead.
|
|
func (x *NodeInfo) SetOffline() {
|
|
x.m.SetState(netmap.Offline)
|
|
}
|
|
|
|
// IsOffline checks if the node is in the "offline" state.
|
|
//
|
|
// Zero NodeInfo has undefined state which is not offline (note that it does not
|
|
// mean online).
|
|
//
|
|
// See also SetOffline.
|
|
//
|
|
// Deprecated: use Status instead.
|
|
func (x NodeInfo) IsOffline() bool {
|
|
return x.m.GetState() == netmap.Offline
|
|
}
|
|
|
|
// SetOnline sets the state of the node to "online". When a node updates
|
|
// information about itself in the network map, this
|
|
// action is interpreted as an intention to enter the network.
|
|
//
|
|
// See also IsOnline.
|
|
//
|
|
// Deprecated: use SetStatus instead.
|
|
func (x *NodeInfo) SetOnline() {
|
|
x.m.SetState(netmap.Online)
|
|
}
|
|
|
|
// IsOnline checks if the node is in the "online" state.
|
|
//
|
|
// Zero NodeInfo has undefined state which is not online (note that it does not
|
|
// mean offline).
|
|
//
|
|
// See also SetOnline.
|
|
//
|
|
// Deprecated: use Status instead.
|
|
func (x NodeInfo) IsOnline() bool {
|
|
return x.m.GetState() == netmap.Online
|
|
}
|
|
|
|
// SetMaintenance sets the state of the node to "maintenance". When a node updates
|
|
// information about itself in the network map, this
|
|
// state declares temporal unavailability for a node.
|
|
//
|
|
// See also IsMaintenance.
|
|
//
|
|
// Deprecated: use SetStatus instead.
|
|
func (x *NodeInfo) SetMaintenance() {
|
|
x.m.SetState(netmap.Maintenance)
|
|
}
|
|
|
|
// IsMaintenance checks if the node is in the "maintenance" state.
|
|
//
|
|
// Zero NodeInfo has undefined state.
|
|
//
|
|
// See also SetMaintenance.
|
|
//
|
|
// Deprecated: use Status instead.
|
|
func (x NodeInfo) IsMaintenance() bool {
|
|
return x.m.GetState() == netmap.Maintenance
|
|
}
|
|
|
|
type NodeState netmap.NodeState
|
|
|
|
const (
|
|
UnspecifiedState = NodeState(netmap.UnspecifiedState)
|
|
Online = NodeState(netmap.Online)
|
|
Offline = NodeState(netmap.Offline)
|
|
Maintenance = NodeState(netmap.Maintenance)
|
|
)
|
|
|
|
// ToV2 converts NodeState to v2.
|
|
func (ns NodeState) ToV2() netmap.NodeState {
|
|
return netmap.NodeState(ns)
|
|
}
|
|
|
|
// FromV2 reads NodeState to v2.
|
|
func (ns *NodeState) FromV2(state netmap.NodeState) {
|
|
*ns = NodeState(state)
|
|
}
|
|
|
|
// Status returns the current state of the node in the network map.
|
|
//
|
|
// Zero NodeInfo has an undefined state, neither online nor offline.
|
|
func (x NodeInfo) Status() NodeState {
|
|
return NodeState(x.m.GetState())
|
|
}
|
|
|
|
// SetState updates the state of the node in the network map.
|
|
//
|
|
// The state determines the node's current status within the network:
|
|
// - "online": Indicates the node intends to enter the network.
|
|
// - "offline": Indicates the node intends to leave the network.
|
|
// - "maintenance": Indicates the node is temporarily unavailable.
|
|
//
|
|
// See also Status.
|
|
func (x *NodeInfo) SetStatus(state NodeState) {
|
|
x.m.SetState(netmap.NodeState(state))
|
|
}
|
|
|
|
// String implements fmt.Stringer.
|
|
//
|
|
// String is designed to be human-readable, and its format MAY differ between
|
|
// SDK versions.
|
|
func (ns NodeState) String() string {
|
|
return netmap.NodeState(ns).String()
|
|
}
|
|
|
|
// IsOnline checks if the current state is "online".
|
|
func (ns NodeState) IsOnline() bool { return ns == Online }
|
|
|
|
// IsOffline checks if the current state is "offline".
|
|
func (ns NodeState) IsOffline() bool { return ns == Offline }
|
|
|
|
// IsMaintenance checks if the current state is "maintenance".
|
|
func (ns NodeState) IsMaintenance() bool { return ns == Maintenance }
|