using System; using System.Collections.Generic; using System.Linq; using FrostFS.SDK.Client; using FrostFS.SDK.Client.Models.Netmap.Placement; using FrostFS.SDK.Cryptography; namespace FrostFS.SDK; public class FrostFsNetmapSnapshot(ulong epoch, IReadOnlyList nodeInfoCollection) { public ulong Epoch { get; private set; } = epoch; public IReadOnlyList NodeInfoCollection { get; private set; } = nodeInfoCollection; internal static INormalizer NewReverseMinNorm(double minV) { return new ReverseMinNorm { min = minV }; } // newSigmoidNorm returns a normalizer which // normalize values in range of 0.0 to 1.0 to a scaled sigmoid. internal static INormalizer NewSigmoidNorm(double scale) { return new SigmoidNorm(scale); } // 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. public FrostFsNodeInfo[][] PlacementVectors(FrostFsNodeInfo[][] vectors, byte[] pivot) { if (vectors is null) { throw new ArgumentNullException(nameof(vectors)); } using var murmur3 = new Murmur3(0); var hash = murmur3.GetCheckSum64(pivot); var wf = Tools.DefaultWeightFunc(NodeInfoCollection.ToArray()); var result = new FrostFsNodeInfo[vectors.Length][]; var maxSize = vectors.Max(x => x.Length); var spanWeigths = new double[maxSize]; for (int i = 0; i < vectors.Length; i++) { result[i] = new FrostFsNodeInfo[vectors[i].Length]; for (int j = 0; j < vectors[i].Length; j++) { result[i][j] = vectors[i][j]; } Tools.AppendWeightsTo(result[i], wf, ref spanWeigths); result[i] = Tools.SortHasherSliceByWeightValue(result[i].ToList(), spanWeigths, hash).ToArray(); } return result; } // 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. List> SelectFilterNodes(SelectFilterExpr expr) { var policy = new FrostFsPlacementPolicy(false, expr.Cbf, [expr.Selector], expr.Filters); var ctx = new Context(this) { Cbf = expr.Cbf }; ctx.ProcessFilters(policy); ctx.ProcessSelectors(policy); var ret = new List>(); if (expr.Selector == null) { var lastFilter = expr.Filters.Last(); var subCollestion = new List(); ret.Add(subCollestion); foreach (var nodeInfo in NodeInfoCollection) { if (ctx.Match(ctx.ProcessedFilters[lastFilter.Name], nodeInfo)) { subCollestion.Add(nodeInfo); } } } else if (expr.Selector.Name != null) { var sel = ctx.GetSelection(ctx.ProcessedSelectors[expr.Selector.Name]); foreach (var ns in sel) { var subCollestion = new List(); ret.Add(subCollestion); foreach (var n in ns) { subCollestion.Add(n); } } } return ret; } internal static Func NewWeightFunc(INormalizer capNorm, INormalizer priceNorm) { return new Func((FrostFsNodeInfo nodeInfo) => { return capNorm.Normalize(nodeInfo.GetCapacity()) * priceNorm.Normalize(nodeInfo.Price); }); } private static FrostFsNodeInfo[] FlattenNodes(List> nodes) { int sz = 0; foreach (var ns in nodes) { sz += ns.Count; } var result = new FrostFsNodeInfo[sz]; int i = 0; foreach (var ns in nodes) { foreach (var n in ns) { result[i++] = n; } } return result; } // 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. public FrostFsNodeInfo[][] ContainerNodes(FrostFsPlacementPolicy p, byte[]? pivot) { var c = new Context(this) { Cbf = p.BackupFactor == 0 ? 3 : p.BackupFactor }; if (pivot != null && pivot.Length > 0) { c.HrwSeed = pivot; using var murmur = new Murmur3(0); c.HrwSeedHash = murmur.GetCheckSum64(pivot); } c.ProcessFilters(p); c.ProcessSelectors(p); var unique = p.IsUnique(); var result = new List>(p.Replicas.Length); for (int i = 0; i < p.Replicas.Length; i++) { result.Add([]); } // 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 (int i = 0; i < p.Replicas.Length; i++) { var sName = p.Replicas[i].Selector; if (string.IsNullOrEmpty(sName) && !(p.Replicas.Length == 1 && p.Selectors.Count == 1)) { var s = new FrostFsSelector(string.Empty) { Count = p.Replicas[i].CountNodes(), Filter = Context.mainFilterName }; var nodes = c.GetSelection(s); result[i].AddRange(FlattenNodes(nodes)); if (unique) { foreach (var n in result[i]) { c.UsedNodes[n.Hash()] = true; } } continue; } if (unique) { if (!c.ProcessedSelectors.TryGetValue(sName, out var s) || s == null) { throw new FrostFsException($"selector not found: {sName}"); } var nodes = c.GetSelection(c.ProcessedSelectors[sName]); result[i].AddRange(FlattenNodes(nodes)); foreach (var n in result[i]) { c.UsedNodes[n.Hash()] = true; } } else { var nodes = c.Selections[sName]; result[i].AddRange(FlattenNodes(nodes)); } } var collection = new FrostFsNodeInfo[result.Count][]; for (int i = 0; i < result.Count; i++) { collection[i] = [.. result[i]]; } return collection; } }