diff --git a/cmd/frostfs-cli/modules/object/nodes.go b/cmd/frostfs-cli/modules/object/nodes.go index c3bf239cc..2e84cc6a5 100644 --- a/cmd/frostfs-cli/modules/object/nodes.go +++ b/cmd/frostfs-cli/modules/object/nodes.go @@ -29,6 +29,7 @@ import ( const ( verifyPresenceAllFlag = "verify-presence-all" + explainFlag = "explain" ) var errNoAvailableEndpoint = errors.New("failed to create client: no available endpoint") @@ -50,6 +51,11 @@ type boolError struct { err error } +type objectPlacement struct { + requiredNodes []netmapSDK.NodeInfo + confirmedNodes []netmapSDK.NodeInfo +} + var objectNodesCmd = &cobra.Command{ Use: "nodes", Short: "List of nodes where the object is stored", @@ -71,7 +77,8 @@ func initObjectNodesCmd() { flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage) _ = objectGetCmd.MarkFlagRequired(commonflags.OIDFlag) - flags.Bool("verify-presence-all", false, "Verify the actual presence of the object on all netmap nodes") + flags.Bool(verifyPresenceAllFlag, false, "Verify the actual presence of the object on all netmap nodes") + flags.Bool(explainFlag, false, "Show detailed information about the object placement") } func objectNodes(cmd *cobra.Command, _ []string) { @@ -86,11 +93,11 @@ func objectNodes(cmd *cobra.Command, _ []string) { placementPolicy, netmap := getPlacementPolicyAndNetmap(cmd, cnrID, cli) - requiredPlacement := getRequiredPlacement(cmd, objects, placementPolicy, netmap) + requiredNodes, objectsPlacement := getRequiredPlacement(cmd, objects, placementPolicy, netmap) - actualPlacement := getActualPlacement(cmd, netmap, requiredPlacement, pk, objects) + actualPlacement := getActualPlacement(cmd, netmap, requiredNodes, pk, objects, objectsPlacement) - printPlacement(cmd, netmap, requiredPlacement, actualPlacement) + printPlacement(cmd, netmap, requiredNodes, actualPlacement, objID, objects, objectsPlacement) } func getPhyObjects(cmd *cobra.Command, cnrID cid.ID, objID oid.ID, cli *client.Client, pk *ecdsa.PrivateKey) []phyObject { @@ -249,15 +256,16 @@ func getNetMap(ctx context.Context, cli *client.Client) (*netmapSDK.NetMap, erro return &nm, nil } -func getRequiredPlacement(cmd *cobra.Command, objects []phyObject, placementPolicy netmapSDK.PlacementPolicy, netmap *netmapSDK.NetMap) map[uint64]netmapSDK.NodeInfo { +func getRequiredPlacement(cmd *cobra.Command, objects []phyObject, placementPolicy netmapSDK.PlacementPolicy, netmap *netmapSDK.NetMap) (map[uint64]netmapSDK.NodeInfo, map[oid.ID]objectPlacement) { if policy.IsECPlacement(placementPolicy) { return getECRequiredPlacement(cmd, objects, placementPolicy, netmap) } return getReplicaRequiredPlacement(cmd, objects, placementPolicy, netmap) } -func getReplicaRequiredPlacement(cmd *cobra.Command, objects []phyObject, placementPolicy netmapSDK.PlacementPolicy, netmap *netmapSDK.NetMap) map[uint64]netmapSDK.NodeInfo { +func getReplicaRequiredPlacement(cmd *cobra.Command, objects []phyObject, placementPolicy netmapSDK.PlacementPolicy, netmap *netmapSDK.NetMap) (map[uint64]netmapSDK.NodeInfo, map[oid.ID]objectPlacement) { nodes := make(map[uint64]netmapSDK.NodeInfo) + objectsNodes := make(map[oid.ID]objectPlacement) placementBuilder := placement.NewNetworkMapBuilder(netmap) for _, object := range objects { placement, err := placementBuilder.BuildPlacement(object.containerID, &object.objectID, placementPolicy) @@ -270,23 +278,29 @@ func getReplicaRequiredPlacement(cmd *cobra.Command, objects []phyObject, placem break } nodes[n.Hash()] = n + + op := objectsNodes[object.objectID] + op.requiredNodes = append(op.requiredNodes, n) + objectsNodes[object.objectID] = op + nodeIdx++ } } } - return nodes + return nodes, objectsNodes } -func getECRequiredPlacement(cmd *cobra.Command, objects []phyObject, placementPolicy netmapSDK.PlacementPolicy, netmap *netmapSDK.NetMap) map[uint64]netmapSDK.NodeInfo { +func getECRequiredPlacement(cmd *cobra.Command, objects []phyObject, placementPolicy netmapSDK.PlacementPolicy, netmap *netmapSDK.NetMap) (map[uint64]netmapSDK.NodeInfo, map[oid.ID]objectPlacement) { nodes := make(map[uint64]netmapSDK.NodeInfo) + objectsNodes := make(map[oid.ID]objectPlacement) for _, object := range objects { - getECRequiredPlacementInternal(cmd, object, placementPolicy, netmap, nodes) + getECRequiredPlacementInternal(cmd, object, placementPolicy, netmap, nodes, objectsNodes) } - return nodes + return nodes, objectsNodes } -func getECRequiredPlacementInternal(cmd *cobra.Command, object phyObject, placementPolicy netmapSDK.PlacementPolicy, netmap *netmapSDK.NetMap, nodes map[uint64]netmapSDK.NodeInfo) { +func getECRequiredPlacementInternal(cmd *cobra.Command, object phyObject, placementPolicy netmapSDK.PlacementPolicy, netmap *netmapSDK.NetMap, nodes map[uint64]netmapSDK.NodeInfo, objectNodes map[oid.ID]objectPlacement) { placementObjectID := object.objectID if object.ecHeader != nil { placementObjectID = object.ecHeader.parent @@ -299,6 +313,10 @@ func getECRequiredPlacementInternal(cmd *cobra.Command, object phyObject, placem if object.storedOnAllContainerNodes { for _, node := range vector { nodes[node.Hash()] = node + + op := objectNodes[object.objectID] + op.requiredNodes = append(op.requiredNodes, node) + objectNodes[object.objectID] = op } continue } @@ -308,12 +326,16 @@ func getECRequiredPlacementInternal(cmd *cobra.Command, object phyObject, placem nodeIdx := chunkIdx % len(vector) node := vector[nodeIdx] nodes[node.Hash()] = node + + op := objectNodes[object.objectID] + op.requiredNodes = append(op.requiredNodes, node) + objectNodes[object.objectID] = op } } } func getActualPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, requiredPlacement map[uint64]netmapSDK.NodeInfo, - pk *ecdsa.PrivateKey, objects []phyObject, + pk *ecdsa.PrivateKey, objects []phyObject, objectNodes map[oid.ID]objectPlacement, ) map[uint64]boolError { result := make(map[uint64]boolError) resultMtx := &sync.Mutex{} @@ -348,6 +370,11 @@ func getActualPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, requiredPl v.value, v.err = isObjectStoredOnNode(egCtx, cmd, object.containerID, object.objectID, cli, pk) resultMtx.Lock() defer resultMtx.Unlock() + if v.err == nil && v.value { + op := objectNodes[object.objectID] + op.confirmedNodes = append(op.confirmedNodes, cand) + objectNodes[object.objectID] = op + } if prev, exists := result[cand.Hash()]; exists && (prev.err != nil || prev.value) { return nil } @@ -418,12 +445,12 @@ func isObjectStoredOnNode(ctx context.Context, cmd *cobra.Command, cnrID cid.ID, return false, err } -func printPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, requiredPlacement map[uint64]netmapSDK.NodeInfo, actualPlacement map[uint64]boolError) { +func printPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, requiredPlacement map[uint64]netmapSDK.NodeInfo, + actualPlacement map[uint64]boolError, objID oid.ID, objects []phyObject, objectNodes map[oid.ID]objectPlacement, +) { w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 0, 1, ' ', tabwriter.AlignRight|tabwriter.Debug) - defer func() { - commonCmd.ExitOnErr(cmd, "failed to print placement info: %w", w.Flush()) - }() - fmt.Fprintln(w, "Node ID\tShould contain object\tActually contains object\t") + _, err := fmt.Fprintln(w, "Node ID\tShould contain object\tActually contains object\t") + commonCmd.ExitOnErr(cmd, "failed to print placement info: %w", err) for _, n := range netmap.Nodes() { nodeID := hex.EncodeToString(n.PublicKey()) _, required := requiredPlacement[n.Hash()] @@ -436,6 +463,38 @@ func printPlacement(cmd *cobra.Command, netmap *netmapSDK.NetMap, requiredPlacem actualStr = strconv.FormatBool(actual.value) } } - fmt.Fprintf(w, "%s\t%s\t%s\t\n", nodeID, strconv.FormatBool(required), actualStr) + _, err := fmt.Fprintf(w, "%s\t%s\t%s\t\n", nodeID, strconv.FormatBool(required), actualStr) + commonCmd.ExitOnErr(cmd, "failed to print placement info: %w", err) + } + commonCmd.ExitOnErr(cmd, "failed to print placement info: %w", w.Flush()) + + if explain, _ := cmd.Flags().GetBool(explainFlag); !explain { + return + } + + fmt.Fprintf(cmd.OutOrStdout(), "Object %s stores payload in %d data objects:\n", objID.EncodeToString(), len(objects)) + + for _, object := range objects { + fmt.Fprintf(cmd.OutOrStdout(), "- %s\n", object.objectID) + if object.ecHeader != nil { + fmt.Fprintf(cmd.OutOrStdout(), "\tEC index: %d\n", object.ecHeader.index) + fmt.Fprintf(cmd.OutOrStdout(), "\tEC parent: %s\n", object.ecHeader.parent.EncodeToString()) + } + op, ok := objectNodes[object.objectID] + if !ok { + continue + } + if len(op.requiredNodes) > 0 { + fmt.Fprintf(cmd.OutOrStdout(), "\tRequired nodes:\n") + for _, node := range op.requiredNodes { + fmt.Fprintf(cmd.OutOrStdout(), "\t\t- %s\n", hex.EncodeToString(node.PublicKey())) + } + } + if len(op.confirmedNodes) > 0 { + fmt.Fprintf(cmd.OutOrStdout(), "\tActual nodes:\n") + for _, node := range op.confirmedNodes { + fmt.Fprintf(cmd.OutOrStdout(), "\t\t- %s\n", hex.EncodeToString(node.PublicKey())) + } + } } }