package netmap import ( "fmt" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/netmap" "git.frostfs.info/TrueCloudLab/hrw" ) // NetMap represents FrostFS network map. It includes information about all // storage nodes registered in FrostFS the network. // // NetMap is mutually compatible with git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/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 FrostFS 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 FrostFS network. // // Argument MUST NOT be mutated, make a copy first. // // See also Nodes. func (m *NetMap) SetNodes(nodes []NodeInfo) { m.nodes = nodes } // Nodes returns nodes set using SetNodes. // // Return value MUST not be mutated, make a copy first. 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 // assert nodes type provides hrw.Hasher required for HRW sorting. var _ hrw.Hasher = nodes{} // Hash is a function from hrw.Hasher interface. It is implemented // to support weighted hrw sorting of buckets. Each bucket is already sorted by hrw, // thus giving us needed "randomness". func (n nodes) Hash() uint64 { if len(n) > 0 { return n[0].Hash() } return 0 } func (n nodes) appendWeightsTo(wf weightFunc, w []float64) []float64 { if cap(w) < len(n) { w = make([]float64, 0, len(n)) } for i := range n { w = append(w, wf(n[i])) } return w } func flattenNodes(ns []nodes) nodes { var sz, i int for i = range ns { sz += len(ns[i]) } result := make(nodes, 0, sz) for i := range ns { result = append(result, ns[i]...) } return result } // PlacementVectors sorts container nodes returned by ContainerNodes method // and returns placement vectors for the entity identified by the given pivot. // For example, in order to build node list to store the object, binary-encoded // object identifier can be used as pivot. Result is deterministic for // the fixed NetMap and parameters. func (m NetMap) PlacementVectors(vectors [][]NodeInfo, pivot []byte) ([][]NodeInfo, error) { h := hrw.Hash(pivot) wf := defaultWeightFunc(m.nodes) result := make([][]NodeInfo, len(vectors)) var ws []float64 for i := range vectors { result[i] = make([]NodeInfo, len(vectors[i])) copy(result[i], vectors[i]) ws = nodes(result[i]).appendWeightsTo(wf, ws[:0]) hrw.SortHasherSliceByWeightValue(result[i], ws, h) } return result, nil } // SelectFilterNodes returns a two-dimensional list of nodes as a result of applying the // given SelectFilterExpr to the NetMap. // If the SelectFilterExpr contains only filters, the result contains a single row with the // result of the last filter application. // If the SelectFilterExpr contains only selectors, the result contains the selection rows // of the last select application. func (m NetMap) SelectFilterNodes(expr *SelectFilterExpr) ([][]NodeInfo, error) { p := PlacementPolicy{ filters: expr.filters, } if expr.selector != nil { p.selectors = append(p.selectors, *expr.selector) } c := newContext(m) c.setCBF(expr.cbf) if err := c.processFilters(p); err != nil { return nil, err } if err := c.processSelectors(p); err != nil { return nil, err } if expr.selector == nil { var ret []NodeInfo lastFilter := expr.filters[len(expr.filters)-1] for _, ni := range m.nodes { if c.match(c.processedFilters[lastFilter.GetName()], ni) { ret = append(ret, ni) } } return [][]NodeInfo{ret}, nil } sel, err := c.getSelection(*c.processedSelectors[expr.selector.GetName()]) if err != nil { return nil, err } var ret [][]NodeInfo for i, ns := range sel { ret = append(ret, []NodeInfo{}) for _, n := range ns { ret[i] = append(ret[i], n) } } return ret, nil } func countNodes(r netmap.Replica) uint32 { if r.GetCount() != 0 { return r.GetCount() } return r.GetECDataCount() + r.GetECParityCount() } func (p PlacementPolicy) isUnique() bool { if p.unique { return true } for _, r := range p.replicas { if r.GetECDataCount() != 0 || r.GetECParityCount() != 0 { return true } } return false } // ContainerNodes returns two-dimensional list of nodes as a result of applying // given PlacementPolicy to the NetMap. Each line of the list corresponds to a // replica descriptor. Line order corresponds to order of ReplicaDescriptor list // in the policy. Nodes are pre-filtered according to the Filter list from // the policy, and then selected by Selector list. Result is deterministic for // the fixed NetMap and parameters. // // Result can be used in PlacementVectors. func (m NetMap) ContainerNodes(p PlacementPolicy, pivot []byte) ([][]NodeInfo, error) { c := newContext(m) c.setPivot(pivot) c.setCBF(p.backupFactor) if err := c.processFilters(p); err != nil { return nil, err } if err := c.processSelectors(p); err != nil { return nil, err } unique := p.isUnique() result := make([][]NodeInfo, len(p.replicas)) // Note that the cached selectors are not used when the policy contains the UNIQUE flag. // This is necessary because each selection vector affects potentially the subsequent vectors // and thus we call getSelection in such case, in order to take into account nodes previously // marked as used by earlier replicas. for i := range p.replicas { sName := p.replicas[i].GetSelector() if sName == "" && !(len(p.replicas) == 1 && len(p.selectors) == 1) { var s netmap.Selector s.SetCount(countNodes(p.replicas[i])) s.SetFilter(mainFilterName) nodes, err := c.getSelection(s) if err != nil { return nil, err } result[i] = append(result[i], flattenNodes(nodes)...) if unique { c.addUsedNodes(result[i]...) } continue } if unique { if c.processedSelectors[sName] == nil { return nil, fmt.Errorf("selector not found: '%s'", sName) } nodes, err := c.getSelection(*c.processedSelectors[sName]) if err != nil { return nil, err } result[i] = append(result[i], flattenNodes(nodes)...) c.addUsedNodes(result[i]...) } else { nodes, ok := c.selections[sName] if !ok { return nil, fmt.Errorf("selector not found: REPLICA '%s'", sName) } result[i] = append(result[i], flattenNodes(nodes)...) } } return result, nil }