[#312] netmap: Support NetmapService.NetmapSnapshot RPC

Extend functionality of `NetMap` type. Add `NetMapSnapshot` operation to
`client` package.

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
Leonard Lyubich 2022-09-17 14:07:56 +04:00 committed by LeL
parent f2f97f656d
commit 664392afc2
6 changed files with 208 additions and 1 deletions

View file

@ -196,3 +196,83 @@ func (c *Client) NetworkInfo(ctx context.Context, prm PrmNetworkInfo) (*ResNetwo
return &res, nil
}
// PrmNetMapSnapshot groups parameters of NetMapSnapshot operation.
type PrmNetMapSnapshot struct {
prmCommonMeta
}
// ResNetMapSnapshot groups resulting values of NetMapSnapshot operation.
type ResNetMapSnapshot struct {
statusRes
netMap netmap.NetMap
}
// NetMap returns current server's local network map.
func (x ResNetMapSnapshot) NetMap() netmap.NetMap {
return x.netMap
}
// NetMapSnapshot requests current network view of the remote server.
//
// Any client's internal or transport errors are returned as `error`.
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
// NeoFS status codes are returned as `error`, otherwise, are included
// in the returned result structure.
//
// Context is required and MUST NOT be nil. It is used for network communication.
//
// Exactly one return value is non-nil. Server status return is returned in ResNetMapSnapshot.
// Reflects all internal errors in second return value (transport problems, response processing, etc.).
//
// Return statuses:
// - global (see Client docs).
func (c *Client) NetMapSnapshot(ctx context.Context, prm PrmNetMapSnapshot) (*ResNetMapSnapshot, error) {
// check context
if ctx == nil {
panic(panicMsgMissingContext)
}
// form request
var req v2netmap.SnapshotRequest
// init call context
var (
cc contextCall
res ResNetMapSnapshot
)
c.initCallContext(&cc)
cc.meta = prm.prmCommonMeta
cc.req = &req
cc.statusRes = &res
cc.call = func() (responseV2, error) {
return rpcapi.NetMapSnapshot(&c.c, &req, client.WithContext(ctx))
}
cc.result = func(r responseV2) {
resp := r.(*v2netmap.SnapshotResponse)
const fieldNetMap = "network map"
netMapV2 := resp.GetBody().NetMap()
if netMapV2 == nil {
cc.err = newErrMissingResponseField(fieldNetMap)
return
}
cc.err = res.netMap.ReadFromV2(*netMapV2)
if cc.err != nil {
cc.err = newErrInvalidResponseField(fieldNetMap, cc.err)
return
}
}
// process call
if !cc.processCall() {
return nil, cc.err
}
return &res, nil
}

2
go.mod
View file

@ -9,7 +9,7 @@ require (
github.com/mr-tron/base58 v1.2.0
github.com/nspcc-dev/hrw v1.0.9
github.com/nspcc-dev/neo-go v0.99.2
github.com/nspcc-dev/neofs-api-go/v2 v2.13.2-0.20220916145053-f3e1f8ae7ae3
github.com/nspcc-dev/neofs-api-go/v2 v2.13.2-0.20220919124434-cf868188ef9c
github.com/nspcc-dev/neofs-contract v0.15.3
github.com/nspcc-dev/tzhash v1.6.1
github.com/stretchr/testify v1.7.0

BIN
go.sum

Binary file not shown.

View file

@ -9,10 +9,63 @@ import (
// NetMap represents NeoFS network map. It includes information about all
// storage nodes registered in NeoFS the network.
//
// NetMap is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/netmap.NetMap
// message. See ReadFromV2 / WriteToV2 methods.
//
// Instances can be created using built-in var declaration.
type NetMap struct {
epoch uint64
nodes []NodeInfo
}
// ReadFromV2 reads NetMap from the netmap.NetMap message. Checks if the
// message conforms to NeoFS API V2 protocol.
//
// See also WriteToV2.
func (m *NetMap) ReadFromV2(msg netmap.NetMap) error {
var err error
nodes := msg.Nodes()
if nodes == nil {
m.nodes = nil
} else {
m.nodes = make([]NodeInfo, len(nodes))
for i := range nodes {
err = m.nodes[i].ReadFromV2(nodes[i])
if err != nil {
return fmt.Errorf("invalid node info: %w", err)
}
}
}
m.epoch = msg.Epoch()
return nil
}
// WriteToV2 writes NetMap to the netmap.NetMap message. The message
// MUST NOT be nil.
//
// See also ReadFromV2.
func (m NetMap) WriteToV2(msg *netmap.NetMap) {
var nodes []netmap.NodeInfo
if m.nodes != nil {
nodes = make([]netmap.NodeInfo, len(m.nodes))
for i := range m.nodes {
m.nodes[i].WriteToV2(&nodes[i])
}
msg.SetNodes(nodes)
}
msg.SetEpoch(m.epoch)
}
// SetNodes sets information list about all storage nodes from the NeoFS network.
//
// Argument MUST NOT be mutated, make a copy first.
@ -29,6 +82,20 @@ func (m NetMap) Nodes() []NodeInfo {
return m.nodes
}
// SetEpoch specifies revision number of the NetMap.
//
// See also Epoch.
func (m *NetMap) SetEpoch(epoch uint64) {
m.epoch = epoch
}
// Epoch returns epoch set using SetEpoch.
//
// Zero NetMap has zero revision.
func (m NetMap) Epoch() uint64 {
return m.epoch
}
// nodes is a slice of NodeInfo instances needed for HRW sorting.
type nodes []NodeInfo

47
netmap/netmap_test.go Normal file
View file

@ -0,0 +1,47 @@
package netmap_test
import (
"testing"
v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap"
"github.com/nspcc-dev/neofs-sdk-go/netmap"
netmaptest "github.com/nspcc-dev/neofs-sdk-go/netmap/test"
"github.com/stretchr/testify/require"
)
func TestNetMapNodes(t *testing.T) {
var nm netmap.NetMap
require.Empty(t, nm.Nodes())
nodes := []netmap.NodeInfo{netmaptest.NodeInfo(), netmaptest.NodeInfo()}
nm.SetNodes(nodes)
require.ElementsMatch(t, nodes, nm.Nodes())
nodesV2 := make([]v2netmap.NodeInfo, len(nodes))
for i := range nodes {
nodes[i].WriteToV2(&nodesV2[i])
}
var m v2netmap.NetMap
nm.WriteToV2(&m)
require.ElementsMatch(t, nodesV2, m.Nodes())
}
func TestNetMap_SetEpoch(t *testing.T) {
var nm netmap.NetMap
require.Zero(t, nm.Epoch())
const e = 158
nm.SetEpoch(e)
require.EqualValues(t, e, nm.Epoch())
var m v2netmap.NetMap
nm.WriteToV2(&m)
require.EqualValues(t, e, m.Epoch())
}

View file

@ -1,6 +1,8 @@
package netmaptest
import (
"math/rand"
"github.com/nspcc-dev/neofs-sdk-go/netmap"
subnetidtest "github.com/nspcc-dev/neofs-sdk-go/subnet/id/test"
)
@ -68,3 +70,14 @@ func NetworkInfo() (x netmap.NetworkInfo) {
return
}
// NodeInfo returns random netmap.NodeInfo.
func NodeInfo() (x netmap.NodeInfo) {
key := make([]byte, 33)
rand.Read(key)
x.SetPublicKey(key)
x.SetNetworkEndpoints("1", "2", "3")
return
}