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 }