2020-09-17 15:37:44 +00:00
|
|
|
package placement
|
|
|
|
|
|
|
|
import (
|
2021-05-18 08:12:51 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2024-10-22 07:06:16 +00:00
|
|
|
"slices"
|
2020-09-17 15:37:44 +00:00
|
|
|
"sync"
|
|
|
|
|
2023-03-07 13:38:26 +00:00
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
|
|
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
|
|
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
2020-09-17 15:37:44 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Builder is an interface of the
|
|
|
|
// object placement vector builder.
|
|
|
|
type Builder interface {
|
|
|
|
// BuildPlacement returns the list of placement vectors
|
|
|
|
// for object according to the placement policy.
|
|
|
|
//
|
|
|
|
// Must return all container nodes if object identifier
|
|
|
|
// is nil.
|
2022-06-08 23:18:26 +00:00
|
|
|
BuildPlacement(cid.ID, *oid.ID, netmap.PlacementPolicy) ([][]netmap.NodeInfo, error)
|
2020-09-17 15:37:44 +00:00
|
|
|
}
|
|
|
|
|
2024-10-22 07:06:16 +00:00
|
|
|
type NodeState interface {
|
|
|
|
// LocalNodeInfo return current node state in FrostFS API v2 NodeInfo structure.
|
|
|
|
LocalNodeInfo() *netmap.NodeInfo
|
|
|
|
}
|
|
|
|
|
2020-09-17 15:37:44 +00:00
|
|
|
// Option represents placement traverser option.
|
|
|
|
type Option func(*cfg)
|
|
|
|
|
|
|
|
// Traverser represents utility for controlling
|
|
|
|
// traversal of object placement vectors.
|
|
|
|
type Traverser struct {
|
2023-05-30 07:14:37 +00:00
|
|
|
mtx sync.RWMutex
|
2020-09-17 15:37:44 +00:00
|
|
|
|
2022-06-08 23:18:26 +00:00
|
|
|
vectors [][]netmap.NodeInfo
|
2020-09-18 15:14:26 +00:00
|
|
|
|
|
|
|
rem []int
|
2020-09-17 15:37:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type cfg struct {
|
2020-12-01 14:10:12 +00:00
|
|
|
trackCopies bool
|
2023-04-11 18:01:00 +00:00
|
|
|
copyNumbers []uint32
|
2020-12-01 14:10:12 +00:00
|
|
|
|
|
|
|
flatSuccess *uint32
|
2020-09-17 15:37:44 +00:00
|
|
|
|
2022-05-31 17:00:41 +00:00
|
|
|
cnr cid.ID
|
|
|
|
|
|
|
|
obj *oid.ID
|
2020-09-17 15:37:44 +00:00
|
|
|
|
2022-06-28 07:01:05 +00:00
|
|
|
policySet bool
|
|
|
|
policy netmap.PlacementPolicy
|
2020-09-17 15:37:44 +00:00
|
|
|
|
|
|
|
builder Builder
|
2024-10-22 07:06:16 +00:00
|
|
|
|
|
|
|
metrics []Metric
|
|
|
|
|
|
|
|
nodeState NodeState
|
2020-09-17 15:37:44 +00:00
|
|
|
}
|
|
|
|
|
2020-09-18 15:14:26 +00:00
|
|
|
const invalidOptsMsg = "invalid traverser options"
|
|
|
|
|
2020-09-17 15:37:44 +00:00
|
|
|
var errNilBuilder = errors.New("placement builder is nil")
|
|
|
|
|
2020-09-18 15:14:26 +00:00
|
|
|
var errNilPolicy = errors.New("placement policy is nil")
|
|
|
|
|
2023-08-30 10:43:43 +00:00
|
|
|
var errCopiesNumberLen = errors.New("copies number accepts only one number or array with length " +
|
|
|
|
"equal to length of replicas")
|
|
|
|
|
2020-09-17 15:37:44 +00:00
|
|
|
func defaultCfg() *cfg {
|
|
|
|
return &cfg{
|
2020-12-01 14:10:12 +00:00
|
|
|
trackCopies: true,
|
2020-09-17 15:37:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewTraverser creates, initializes with options and returns Traverser instance.
|
|
|
|
func NewTraverser(opts ...Option) (*Traverser, error) {
|
|
|
|
cfg := defaultCfg()
|
|
|
|
|
|
|
|
for i := range opts {
|
|
|
|
if opts[i] != nil {
|
|
|
|
opts[i](cfg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-30 10:43:43 +00:00
|
|
|
cnLen := len(cfg.copyNumbers)
|
|
|
|
if cnLen > 0 && cnLen != 1 && cnLen != cfg.policy.NumberOfReplicas() {
|
|
|
|
return nil, errCopiesNumberLen
|
|
|
|
}
|
|
|
|
|
2020-09-17 15:37:44 +00:00
|
|
|
if cfg.builder == nil {
|
2021-05-18 08:12:51 +00:00
|
|
|
return nil, fmt.Errorf("%s: %w", invalidOptsMsg, errNilBuilder)
|
2022-06-28 07:01:05 +00:00
|
|
|
} else if !cfg.policySet {
|
2021-05-18 08:12:51 +00:00
|
|
|
return nil, fmt.Errorf("%s: %w", invalidOptsMsg, errNilPolicy)
|
2020-09-17 15:37:44 +00:00
|
|
|
}
|
|
|
|
|
2022-06-28 07:01:05 +00:00
|
|
|
ns, err := cfg.builder.BuildPlacement(cfg.cnr, cfg.obj, cfg.policy)
|
2020-09-17 15:37:44 +00:00
|
|
|
if err != nil {
|
2021-05-18 08:12:51 +00:00
|
|
|
return nil, fmt.Errorf("could not build placement: %w", err)
|
2020-09-17 15:37:44 +00:00
|
|
|
}
|
|
|
|
|
2023-04-11 18:01:00 +00:00
|
|
|
// backward compatibility for scalar `copies_number`
|
2023-06-27 08:12:01 +00:00
|
|
|
if len(cfg.copyNumbers) == 1 && cfg.copyNumbers[0] != 0 {
|
2023-04-11 18:01:00 +00:00
|
|
|
cfg.flatSuccess = &cfg.copyNumbers[0]
|
|
|
|
}
|
|
|
|
|
2020-12-01 14:10:12 +00:00
|
|
|
var rem []int
|
2024-10-22 07:06:16 +00:00
|
|
|
if len(cfg.metrics) > 0 && cfg.nodeState != nil {
|
|
|
|
rem = defaultCopiesVector(cfg.policy)
|
|
|
|
var unsortedVector []netmap.NodeInfo
|
|
|
|
var regularVector []netmap.NodeInfo
|
|
|
|
for i := range rem {
|
2024-11-21 10:17:16 +00:00
|
|
|
pivot := min(len(ns[i]), rem[i])
|
|
|
|
unsortedVector = append(unsortedVector, ns[i][:pivot]...)
|
|
|
|
regularVector = append(regularVector, ns[i][pivot:]...)
|
2024-10-22 07:06:16 +00:00
|
|
|
}
|
|
|
|
rem = []int{-1, -1}
|
|
|
|
|
|
|
|
sortedVector, err := sortVector(cfg, unsortedVector)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
ns = [][]netmap.NodeInfo{sortedVector, regularVector}
|
|
|
|
} else if cfg.flatSuccess != nil {
|
2020-12-01 14:10:12 +00:00
|
|
|
ns = flatNodes(ns)
|
|
|
|
rem = []int{int(*cfg.flatSuccess)}
|
|
|
|
} else {
|
2023-04-11 18:01:00 +00:00
|
|
|
rem = defaultCopiesVector(cfg.policy)
|
|
|
|
|
2023-08-17 08:27:20 +00:00
|
|
|
// Bool flag which is set when cfg.copyNumbers contains not only zeros.
|
|
|
|
// In this case we should not modify `rem` slice unless track
|
|
|
|
// copies are ignored, because [0, ...] means that all copies should be
|
2023-07-05 16:49:58 +00:00
|
|
|
// stored before returning OK to the client.
|
2023-08-17 08:27:20 +00:00
|
|
|
var considerCopiesNumber bool
|
|
|
|
for _, val := range cfg.copyNumbers {
|
|
|
|
if val != 0 {
|
|
|
|
considerCopiesNumber = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2023-07-05 16:49:58 +00:00
|
|
|
|
2023-04-11 18:01:00 +00:00
|
|
|
for i := range rem {
|
|
|
|
if !cfg.trackCopies {
|
|
|
|
rem[i] = -1
|
2023-08-17 08:27:20 +00:00
|
|
|
} else if considerCopiesNumber && len(cfg.copyNumbers) > i {
|
2023-04-11 18:01:00 +00:00
|
|
|
rem[i] = int(cfg.copyNumbers[i])
|
2020-12-01 14:10:12 +00:00
|
|
|
}
|
2020-09-18 15:14:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-17 15:37:44 +00:00
|
|
|
return &Traverser{
|
2020-09-18 15:14:26 +00:00
|
|
|
rem: rem,
|
2020-09-17 15:37:44 +00:00
|
|
|
vectors: ns,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2023-04-11 18:01:00 +00:00
|
|
|
func defaultCopiesVector(policy netmap.PlacementPolicy) []int {
|
|
|
|
replNum := policy.NumberOfReplicas()
|
|
|
|
copyVector := make([]int, 0, replNum)
|
|
|
|
|
2024-08-30 16:20:55 +00:00
|
|
|
for i := range replNum {
|
2024-03-28 10:46:19 +00:00
|
|
|
copyVector = append(copyVector, int(policy.ReplicaDescriptor(i).NumberOfObjects()+policy.ReplicaDescriptor(i).GetECDataCount()+policy.ReplicaDescriptor(i).GetECParityCount()))
|
2023-04-11 18:01:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return copyVector
|
|
|
|
}
|
|
|
|
|
2022-06-08 23:18:26 +00:00
|
|
|
func flatNodes(ns [][]netmap.NodeInfo) [][]netmap.NodeInfo {
|
2020-12-01 14:10:12 +00:00
|
|
|
sz := 0
|
|
|
|
for i := range ns {
|
|
|
|
sz += len(ns[i])
|
|
|
|
}
|
|
|
|
|
2022-06-08 23:18:26 +00:00
|
|
|
flat := make([]netmap.NodeInfo, 0, sz)
|
2020-12-01 14:10:12 +00:00
|
|
|
for i := range ns {
|
|
|
|
flat = append(flat, ns[i]...)
|
|
|
|
}
|
|
|
|
|
2022-06-08 23:18:26 +00:00
|
|
|
return [][]netmap.NodeInfo{flat}
|
2020-12-01 14:10:12 +00:00
|
|
|
}
|
|
|
|
|
2024-10-22 07:06:16 +00:00
|
|
|
type nodeMetrics struct {
|
|
|
|
index int
|
|
|
|
metrics []int
|
|
|
|
}
|
|
|
|
|
|
|
|
func sortVector(cfg *cfg, unsortedVector []netmap.NodeInfo) ([]netmap.NodeInfo, error) {
|
|
|
|
nm := make([]nodeMetrics, len(unsortedVector))
|
|
|
|
node := cfg.nodeState.LocalNodeInfo()
|
|
|
|
|
|
|
|
for i := range unsortedVector {
|
|
|
|
m := make([]int, len(cfg.metrics))
|
|
|
|
for j, pm := range cfg.metrics {
|
|
|
|
m[j] = pm.CalculateValue(node, &unsortedVector[i])
|
|
|
|
}
|
|
|
|
nm[i] = nodeMetrics{
|
|
|
|
index: i,
|
|
|
|
metrics: m,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
slices.SortFunc(nm, func(a, b nodeMetrics) int {
|
|
|
|
return slices.Compare(a.metrics, b.metrics)
|
|
|
|
})
|
|
|
|
sortedVector := make([]netmap.NodeInfo, len(unsortedVector))
|
|
|
|
for i := range unsortedVector {
|
|
|
|
sortedVector[i] = unsortedVector[nm[i].index]
|
|
|
|
}
|
|
|
|
return sortedVector, nil
|
|
|
|
}
|
|
|
|
|
2021-09-06 11:35:06 +00:00
|
|
|
// Node is a descriptor of storage node with information required for intra-container communication.
|
|
|
|
type Node struct {
|
|
|
|
addresses network.AddressGroup
|
2021-09-06 12:05:04 +00:00
|
|
|
|
2022-09-26 12:34:01 +00:00
|
|
|
externalAddresses network.AddressGroup
|
|
|
|
|
2021-09-06 12:05:04 +00:00
|
|
|
key []byte
|
2021-09-06 11:35:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Addresses returns group of network addresses.
|
|
|
|
func (x Node) Addresses() network.AddressGroup {
|
|
|
|
return x.addresses
|
|
|
|
}
|
|
|
|
|
2022-09-26 12:34:01 +00:00
|
|
|
// ExternalAddresses returns group of network addresses.
|
|
|
|
func (x Node) ExternalAddresses() network.AddressGroup {
|
|
|
|
return x.externalAddresses
|
|
|
|
}
|
|
|
|
|
2021-09-28 05:17:11 +00:00
|
|
|
// PublicKey returns public key in a binary format. Should not be mutated.
|
|
|
|
func (x Node) PublicKey() []byte {
|
2021-09-06 12:05:04 +00:00
|
|
|
return x.key
|
|
|
|
}
|
|
|
|
|
2024-01-12 06:56:13 +00:00
|
|
|
// NewNode creates new Node.
|
|
|
|
func NewNode(addresses network.AddressGroup, externalAddresses network.AddressGroup, key []byte) Node {
|
|
|
|
return Node{
|
|
|
|
addresses: addresses,
|
|
|
|
externalAddresses: externalAddresses,
|
|
|
|
key: key,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-17 15:37:44 +00:00
|
|
|
// Next returns next unprocessed address of the object placement.
|
|
|
|
//
|
|
|
|
// Returns nil if no nodes left or traversal operation succeeded.
|
2021-09-06 11:35:06 +00:00
|
|
|
func (t *Traverser) Next() []Node {
|
2020-09-17 15:37:44 +00:00
|
|
|
t.mtx.Lock()
|
|
|
|
defer t.mtx.Unlock()
|
|
|
|
|
2020-09-18 15:14:26 +00:00
|
|
|
t.skipEmptyVectors()
|
|
|
|
|
|
|
|
if len(t.vectors) == 0 {
|
|
|
|
return nil
|
|
|
|
} else if len(t.vectors[0]) < t.rem[0] {
|
2020-09-17 15:37:44 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-18 15:14:26 +00:00
|
|
|
count := t.rem[0]
|
|
|
|
if count < 0 {
|
|
|
|
count = len(t.vectors[0])
|
2020-09-17 15:37:44 +00:00
|
|
|
}
|
|
|
|
|
2021-09-06 11:35:06 +00:00
|
|
|
nodes := make([]Node, count)
|
2020-09-18 15:14:26 +00:00
|
|
|
|
2024-08-30 16:20:55 +00:00
|
|
|
for i := range count {
|
2022-06-08 23:18:26 +00:00
|
|
|
err := nodes[i].addresses.FromIterator(network.NodeEndpointsIterator(t.vectors[0][i]))
|
2020-09-18 15:14:26 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
2021-09-06 12:05:04 +00:00
|
|
|
|
2022-09-26 12:34:01 +00:00
|
|
|
ext := t.vectors[0][i].ExternalAddresses()
|
|
|
|
if len(ext) > 0 {
|
|
|
|
// Ignore the error if this field is incorrectly formed.
|
|
|
|
_ = nodes[i].externalAddresses.FromStringSlice(ext)
|
|
|
|
}
|
|
|
|
|
2021-09-06 12:05:04 +00:00
|
|
|
nodes[i].key = t.vectors[0][i].PublicKey()
|
2020-09-17 15:37:44 +00:00
|
|
|
}
|
|
|
|
|
2020-09-18 15:14:26 +00:00
|
|
|
t.vectors[0] = t.vectors[0][count:]
|
|
|
|
|
2021-09-06 11:35:06 +00:00
|
|
|
return nodes
|
2020-09-18 15:14:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Traverser) skipEmptyVectors() {
|
|
|
|
for i := 0; i < len(t.vectors); i++ { // don't use range, slice changes in body
|
|
|
|
if len(t.vectors[i]) == 0 && t.rem[i] <= 0 || t.rem[0] == 0 {
|
|
|
|
t.vectors = append(t.vectors[:i], t.vectors[i+1:]...)
|
|
|
|
t.rem = append(t.rem[:i], t.rem[i+1:]...)
|
|
|
|
i--
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2020-09-17 15:37:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SubmitSuccess writes single succeeded node operation.
|
|
|
|
func (t *Traverser) SubmitSuccess() {
|
|
|
|
t.mtx.Lock()
|
2020-09-18 15:14:26 +00:00
|
|
|
if len(t.rem) > 0 {
|
|
|
|
t.rem[0]--
|
|
|
|
}
|
2020-09-17 15:37:44 +00:00
|
|
|
t.mtx.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Success returns true if traversal operation succeeded.
|
|
|
|
func (t *Traverser) Success() bool {
|
|
|
|
t.mtx.RLock()
|
2020-09-18 15:14:26 +00:00
|
|
|
defer t.mtx.RUnlock()
|
|
|
|
|
|
|
|
for i := range t.rem {
|
|
|
|
if t.rem[i] > 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2020-09-17 15:37:44 +00:00
|
|
|
|
2020-09-18 15:14:26 +00:00
|
|
|
return true
|
2020-09-17 15:37:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// UseBuilder is a placement builder setting option.
|
|
|
|
//
|
|
|
|
// Overlaps UseNetworkMap option.
|
|
|
|
func UseBuilder(b Builder) Option {
|
|
|
|
return func(c *cfg) {
|
|
|
|
c.builder = b
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ForContainer is a traversal container setting option.
|
2022-06-28 07:01:05 +00:00
|
|
|
func ForContainer(cnr container.Container) Option {
|
2020-09-17 15:37:44 +00:00
|
|
|
return func(c *cfg) {
|
2020-11-16 09:43:52 +00:00
|
|
|
c.policy = cnr.PlacementPolicy()
|
2022-06-28 07:01:05 +00:00
|
|
|
c.policySet = true
|
|
|
|
container.CalculateID(&c.cnr, cnr)
|
2020-09-17 15:37:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ForObject is a processing object setting option.
|
2022-05-31 17:00:41 +00:00
|
|
|
func ForObject(id oid.ID) Option {
|
2020-09-17 15:37:44 +00:00
|
|
|
return func(c *cfg) {
|
2022-05-31 17:00:41 +00:00
|
|
|
c.obj = &id
|
2020-09-17 15:37:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-01 14:10:12 +00:00
|
|
|
// SuccessAfter is a flat success number setting option.
|
2020-09-17 15:37:44 +00:00
|
|
|
//
|
|
|
|
// Option has no effect if the number is not positive.
|
2020-12-01 14:10:12 +00:00
|
|
|
func SuccessAfter(v uint32) Option {
|
2020-09-17 15:37:44 +00:00
|
|
|
return func(c *cfg) {
|
|
|
|
if v > 0 {
|
2020-12-01 14:10:12 +00:00
|
|
|
c.flatSuccess = &v
|
2020-09-17 15:37:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-23 09:28:09 +00:00
|
|
|
// ResetSuccessAfter resets flat success number setting option.
|
|
|
|
func ResetSuccessAfter() Option {
|
|
|
|
return func(c *cfg) {
|
|
|
|
c.flatSuccess = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-17 15:37:44 +00:00
|
|
|
// WithoutSuccessTracking disables success tracking in traversal.
|
|
|
|
func WithoutSuccessTracking() Option {
|
|
|
|
return func(c *cfg) {
|
2020-12-01 14:10:12 +00:00
|
|
|
c.trackCopies = false
|
2020-09-17 15:37:44 +00:00
|
|
|
}
|
|
|
|
}
|
2023-04-11 18:01:00 +00:00
|
|
|
|
|
|
|
func WithCopyNumbers(v []uint32) Option {
|
|
|
|
return func(c *cfg) {
|
|
|
|
c.copyNumbers = v
|
|
|
|
}
|
|
|
|
}
|
2024-10-22 07:06:16 +00:00
|
|
|
|
|
|
|
// WithPriorityMetrics use provided priority metrics to sort nodes.
|
|
|
|
func WithPriorityMetrics(m []Metric) Option {
|
|
|
|
return func(c *cfg) {
|
|
|
|
c.metrics = m
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithNodeState provide state of the current node.
|
|
|
|
func WithNodeState(s NodeState) Option {
|
|
|
|
return func(c *cfg) {
|
|
|
|
c.nodeState = s
|
|
|
|
}
|
|
|
|
}
|