[#1350] node: Add ability to evacuate objects from REP 1 only
All checks were successful
DCO action / DCO (pull_request) Successful in 2m48s
Build / Build Components (1.22) (pull_request) Successful in 2m55s
Build / Build Components (1.23) (pull_request) Successful in 2m54s
Tests and linters / Run gofumpt (pull_request) Successful in 2m38s
Vulncheck / Vulncheck (pull_request) Successful in 2m43s
Pre-commit hooks / Pre-commit (pull_request) Successful in 3m16s
Tests and linters / Staticcheck (pull_request) Successful in 3m22s
Tests and linters / Tests (1.23) (pull_request) Successful in 3m29s
Tests and linters / Tests (1.22) (pull_request) Successful in 3m32s
Tests and linters / Tests with -race (pull_request) Successful in 4m10s
Tests and linters / Lint (pull_request) Successful in 4m17s
Tests and linters / gopls check (pull_request) Successful in 4m22s

Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
This commit is contained in:
Anton Nikiforov 2024-09-03 15:42:38 +03:00
parent 8266e15fa4
commit 99441bf077
8 changed files with 182 additions and 1 deletions

View file

@ -24,6 +24,7 @@ func evacuateShard(cmd *cobra.Command, _ []string) {
req := &control.EvacuateShardRequest{Body: new(control.EvacuateShardRequest_Body)} req := &control.EvacuateShardRequest{Body: new(control.EvacuateShardRequest_Body)}
req.Body.Shard_ID = getShardIDList(cmd) req.Body.Shard_ID = getShardIDList(cmd)
req.Body.IgnoreErrors, _ = cmd.Flags().GetBool(ignoreErrorsFlag) req.Body.IgnoreErrors, _ = cmd.Flags().GetBool(ignoreErrorsFlag)
req.Body.RepOneOnly, _ = cmd.Flags().GetBool(repOneOnlyFlag)
signRequest(cmd, pk, req) signRequest(cmd, pk, req)

View file

@ -20,6 +20,7 @@ const (
awaitFlag = "await" awaitFlag = "await"
noProgressFlag = "no-progress" noProgressFlag = "no-progress"
scopeFlag = "scope" scopeFlag = "scope"
repOneOnlyFlag = "rep-one-only"
scopeAll = "all" scopeAll = "all"
scopeObjects = "objects" scopeObjects = "objects"
@ -64,12 +65,14 @@ func startEvacuateShard(cmd *cobra.Command, _ []string) {
pk := key.Get(cmd) pk := key.Get(cmd)
ignoreErrors, _ := cmd.Flags().GetBool(ignoreErrorsFlag) ignoreErrors, _ := cmd.Flags().GetBool(ignoreErrorsFlag)
repOneOnly, _ := cmd.Flags().GetBool(repOneOnlyFlag)
req := &control.StartShardEvacuationRequest{ req := &control.StartShardEvacuationRequest{
Body: &control.StartShardEvacuationRequest_Body{ Body: &control.StartShardEvacuationRequest_Body{
Shard_ID: getShardIDList(cmd), Shard_ID: getShardIDList(cmd),
IgnoreErrors: ignoreErrors, IgnoreErrors: ignoreErrors,
Scope: getEvacuationScope(cmd), Scope: getEvacuationScope(cmd),
RepOneOnly: repOneOnly,
}, },
} }
@ -371,6 +374,7 @@ func initControlStartEvacuationShardCmd() {
flags.String(scopeFlag, scopeAll, fmt.Sprintf("Evacuation scope; possible values: %s, %s, %s", scopeTrees, scopeObjects, scopeAll)) flags.String(scopeFlag, scopeAll, fmt.Sprintf("Evacuation scope; possible values: %s, %s, %s", scopeTrees, scopeObjects, scopeAll))
flags.Bool(awaitFlag, false, "Block execution until evacuation is completed") flags.Bool(awaitFlag, false, "Block execution until evacuation is completed")
flags.Bool(noProgressFlag, false, fmt.Sprintf("Print progress if %s provided", awaitFlag)) flags.Bool(noProgressFlag, false, fmt.Sprintf("Print progress if %s provided", awaitFlag))
flags.Bool(repOneOnlyFlag, false, "Evacuate objects only from containers with policy `REP 1 ...`")
startEvacuationShardCmd.MarkFlagsMutuallyExclusive(shardIDFlag, shardAllFlag) startEvacuationShardCmd.MarkFlagsMutuallyExclusive(shardIDFlag, shardAllFlag)
} }

View file

@ -79,6 +79,7 @@ type EvacuateShardPrm struct {
IgnoreErrors bool IgnoreErrors bool
Async bool Async bool
Scope EvacuateScope Scope EvacuateScope
RepOneOnly bool
} }
// EvacuateShardRes represents result of the EvacuateShard operation. // EvacuateShardRes represents result of the EvacuateShard operation.
@ -270,6 +271,7 @@ func (e *StorageEngine) evacuateShards(ctx context.Context, shardIDs []string, p
attribute.Bool("async", prm.Async), attribute.Bool("async", prm.Async),
attribute.Bool("ignoreErrors", prm.IgnoreErrors), attribute.Bool("ignoreErrors", prm.IgnoreErrors),
attribute.Stringer("scope", prm.Scope), attribute.Stringer("scope", prm.Scope),
attribute.Bool("repOneOnly", prm.RepOneOnly),
)) ))
defer func() { defer func() {
@ -653,7 +655,16 @@ func (e *StorageEngine) evacuateObjects(ctx context.Context, sh *shard.Shard, to
default: default:
} }
addr := toEvacuate[i].Address addr := toEvacuate[i].Address
if prm.RepOneOnly {
repOne, err := e.isRepOne(addr.Container())
if err != nil {
return err
}
if !repOne {
res.objSkipped.Add(1)
continue
}
}
var getPrm shard.GetPrm var getPrm shard.GetPrm
getPrm.SetAddress(addr) getPrm.SetAddress(addr)
getPrm.SkipEvacCheck(true) getPrm.SkipEvacCheck(true)
@ -703,6 +714,20 @@ func (e *StorageEngine) evacuateObjects(ctx context.Context, sh *shard.Shard, to
return nil return nil
} }
func (e *StorageEngine) isRepOne(cid cid.ID) (bool, error) {
c, err := e.containerSource.Load().cs.Get(cid)
if err != nil {
return false, err
}
p := c.Value.PlacementPolicy()
for i := 0; i < p.NumberOfReplicas(); i++ {
if p.ReplicaDescriptor(i).NumberOfObjects() == 1 {
return true, nil
}
}
return false, nil
}
func (e *StorageEngine) tryEvacuateObjectLocal(ctx context.Context, addr oid.Address, object *objectSDK.Object, sh *shard.Shard, func (e *StorageEngine) tryEvacuateObjectLocal(ctx context.Context, addr oid.Address, object *objectSDK.Object, sh *shard.Shard,
shards []pooledShard, shardsToEvacuate map[string]*shard.Shard, res *EvacuateShardRes, shards []pooledShard, shardsToEvacuate map[string]*shard.Shard, res *EvacuateShardRes,
) (bool, error) { ) (bool, error) {

View file

@ -9,6 +9,7 @@ import (
"testing" "testing"
"time" "time"
coreContainer "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
objectCore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/object" objectCore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/object"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/fstree" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/fstree"
@ -18,14 +19,31 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard/mode" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard/mode"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test" cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
type containerStorage struct {
cntmap map[cid.ID]*container.Container
}
func (cs *containerStorage) Get(id cid.ID) (*coreContainer.Container, error) {
coreCnt := coreContainer.Container{
Value: *cs.cntmap[id],
}
return &coreCnt, nil
}
func (cs *containerStorage) DeletionInfo(cid.ID) (*coreContainer.DelInfo, error) {
return nil, nil
}
func newEngineEvacuate(t *testing.T, shardNum int, objPerShard int) (*StorageEngine, []*shard.ID, []*objectSDK.Object) { func newEngineEvacuate(t *testing.T, shardNum int, objPerShard int) (*StorageEngine, []*shard.ID, []*objectSDK.Object) {
dir := t.TempDir() dir := t.TempDir()
@ -605,3 +623,67 @@ func TestEvacuateTreesRemote(t *testing.T) {
require.Equal(t, expectedTreeOps, evacuatedTreeOps) require.Equal(t, expectedTreeOps, evacuatedTreeOps)
} }
func TestEvacuateShardObjectsRepOneOnly(t *testing.T) {
e, ids, _ := newEngineEvacuate(t, 2, 0)
defer func() {
require.NoError(t, e.Close(context.Background()))
}()
// Create container with policy REP 2
cnr1 := container.Container{}
p1 := netmap.PlacementPolicy{}
p1.SetContainerBackupFactor(1)
x1 := netmap.ReplicaDescriptor{}
x1.SetNumberOfObjects(2)
p1.AddReplicas(x1)
cnr1.SetPlacementPolicy(p1)
var idCnr1 cid.ID
container.CalculateID(&idCnr1, cnr1)
cnrmap := make(map[cid.ID]*container.Container)
var cids []cid.ID
cnrmap[idCnr1] = &cnr1
cids = append(cids, idCnr1)
// Create container with policy REP 1
cnr2 := container.Container{}
p2 := netmap.PlacementPolicy{}
p2.SetContainerBackupFactor(1)
x2 := netmap.ReplicaDescriptor{}
x2.SetNumberOfObjects(1)
p2.AddReplicas(x2)
cnr2.SetPlacementPolicy(p2)
var idCnr2 cid.ID
container.CalculateID(&idCnr2, cnr2)
cnrmap[idCnr2] = &cnr2
cids = append(cids, idCnr2)
e.SetContainerSource(&containerStorage{cntmap: cnrmap})
for _, sh := range ids {
for i := 0; i < 4; i++ {
obj := testutil.GenerateObjectWithCID(cids[i%2])
var putPrm shard.PutPrm
putPrm.SetObject(obj)
_, err := e.shards[sh.String()].Put(context.Background(), putPrm)
require.NoError(t, err)
}
}
var prm EvacuateShardPrm
prm.ShardID = ids[0:1]
prm.Scope = EvacuateScopeObjects
prm.RepOneOnly = true
require.NoError(t, e.shards[ids[0].String()].SetMode(mode.ReadOnly))
res, err := e.Evacuate(context.Background(), prm)
require.NoError(t, err)
require.Equal(t, uint64(2), res.ObjectsEvacuated())
require.Equal(t, uint64(2), res.ObjectsSkipped())
require.Equal(t, uint64(0), res.ObjectsFailed())
}

View file

@ -37,6 +37,7 @@ func (s *Server) EvacuateShard(ctx context.Context, req *control.EvacuateShardRe
IgnoreErrors: req.GetBody().GetIgnoreErrors(), IgnoreErrors: req.GetBody().GetIgnoreErrors(),
ObjectsHandler: s.replicateObject, ObjectsHandler: s.replicateObject,
Scope: engine.EvacuateScopeObjects, Scope: engine.EvacuateScopeObjects,
RepOneOnly: req.GetBody().GetRepOneOnly(),
} }
res, err := s.s.Evacuate(ctx, prm) res, err := s.s.Evacuate(ctx, prm)

View file

@ -29,6 +29,7 @@ func (s *Server) StartShardEvacuation(ctx context.Context, req *control.StartSha
TreeHandler: s.replicateTree, TreeHandler: s.replicateTree,
Async: true, Async: true,
Scope: engine.EvacuateScope(req.GetBody().GetScope()), Scope: engine.EvacuateScope(req.GetBody().GetScope()),
RepOneOnly: req.GetBody().GetRepOneOnly(),
} }
_, err = s.s.Evacuate(ctx, prm) _, err = s.s.Evacuate(ctx, prm)

View file

@ -313,6 +313,9 @@ message EvacuateShardRequest {
// Flag indicating whether object read errors should be ignored. // Flag indicating whether object read errors should be ignored.
bool ignore_errors = 2; bool ignore_errors = 2;
// Choose for evacuation objects in `REP 1` containers only.
bool rep_one_only = 3;
} }
Body body = 1; Body body = 1;
@ -391,6 +394,8 @@ message StartShardEvacuationRequest {
bool ignore_errors = 2; bool ignore_errors = 2;
// Evacuation scope. // Evacuation scope.
uint32 scope = 3; uint32 scope = 3;
// Choose for evacuation objects in `REP 1` containers only.
bool rep_one_only = 4;
} }
Body body = 1; Body body = 1;

View file

@ -4535,6 +4535,7 @@ func (x *SynchronizeTreeResponse) UnmarshalEasyJSON(in *jlexer.Lexer) {
type EvacuateShardRequest_Body struct { type EvacuateShardRequest_Body struct {
Shard_ID [][]byte `json:"shardID"` Shard_ID [][]byte `json:"shardID"`
IgnoreErrors bool `json:"ignoreErrors"` IgnoreErrors bool `json:"ignoreErrors"`
RepOneOnly bool `json:"repOneOnly"`
} }
var ( var (
@ -4553,6 +4554,7 @@ func (x *EvacuateShardRequest_Body) StableSize() (size int) {
} }
size += proto.RepeatedBytesSize(1, x.Shard_ID) size += proto.RepeatedBytesSize(1, x.Shard_ID)
size += proto.BoolSize(2, x.IgnoreErrors) size += proto.BoolSize(2, x.IgnoreErrors)
size += proto.BoolSize(3, x.RepOneOnly)
return size return size
} }
@ -4575,6 +4577,9 @@ func (x *EvacuateShardRequest_Body) EmitProtobuf(mm *easyproto.MessageMarshaler)
if x.IgnoreErrors { if x.IgnoreErrors {
mm.AppendBool(2, x.IgnoreErrors) mm.AppendBool(2, x.IgnoreErrors)
} }
if x.RepOneOnly {
mm.AppendBool(3, x.RepOneOnly)
}
} }
// UnmarshalProtobuf implements the encoding.ProtoUnmarshaler interface. // UnmarshalProtobuf implements the encoding.ProtoUnmarshaler interface.
@ -4598,6 +4603,12 @@ func (x *EvacuateShardRequest_Body) UnmarshalProtobuf(src []byte) (err error) {
return fmt.Errorf("cannot unmarshal field %s", "IgnoreErrors") return fmt.Errorf("cannot unmarshal field %s", "IgnoreErrors")
} }
x.IgnoreErrors = data x.IgnoreErrors = data
case 3: // RepOneOnly
data, ok := fc.Bool()
if !ok {
return fmt.Errorf("cannot unmarshal field %s", "RepOneOnly")
}
x.RepOneOnly = data
} }
} }
return nil return nil
@ -4620,6 +4631,15 @@ func (x *EvacuateShardRequest_Body) GetIgnoreErrors() bool {
func (x *EvacuateShardRequest_Body) SetIgnoreErrors(v bool) { func (x *EvacuateShardRequest_Body) SetIgnoreErrors(v bool) {
x.IgnoreErrors = v x.IgnoreErrors = v
} }
func (x *EvacuateShardRequest_Body) GetRepOneOnly() bool {
if x != nil {
return x.RepOneOnly
}
return false
}
func (x *EvacuateShardRequest_Body) SetRepOneOnly(v bool) {
x.RepOneOnly = v
}
// MarshalJSON implements the json.Marshaler interface. // MarshalJSON implements the json.Marshaler interface.
func (x *EvacuateShardRequest_Body) MarshalJSON() ([]byte, error) { func (x *EvacuateShardRequest_Body) MarshalJSON() ([]byte, error) {
@ -4650,6 +4670,11 @@ func (x *EvacuateShardRequest_Body) MarshalEasyJSON(out *jwriter.Writer) {
out.RawString(prefix) out.RawString(prefix)
out.Bool(x.IgnoreErrors) out.Bool(x.IgnoreErrors)
} }
{
const prefix string = ",\"repOneOnly\":"
out.RawString(prefix)
out.Bool(x.RepOneOnly)
}
out.RawByte('}') out.RawByte('}')
} }
@ -4697,6 +4722,12 @@ func (x *EvacuateShardRequest_Body) UnmarshalEasyJSON(in *jlexer.Lexer) {
f = in.Bool() f = in.Bool()
x.IgnoreErrors = f x.IgnoreErrors = f
} }
case "repOneOnly":
{
var f bool
f = in.Bool()
x.RepOneOnly = f
}
} }
in.WantComma() in.WantComma()
} }
@ -6514,6 +6545,7 @@ type StartShardEvacuationRequest_Body struct {
Shard_ID [][]byte `json:"shardID"` Shard_ID [][]byte `json:"shardID"`
IgnoreErrors bool `json:"ignoreErrors"` IgnoreErrors bool `json:"ignoreErrors"`
Scope uint32 `json:"scope"` Scope uint32 `json:"scope"`
RepOneOnly bool `json:"repOneOnly"`
} }
var ( var (
@ -6533,6 +6565,7 @@ func (x *StartShardEvacuationRequest_Body) StableSize() (size int) {
size += proto.RepeatedBytesSize(1, x.Shard_ID) size += proto.RepeatedBytesSize(1, x.Shard_ID)
size += proto.BoolSize(2, x.IgnoreErrors) size += proto.BoolSize(2, x.IgnoreErrors)
size += proto.UInt32Size(3, x.Scope) size += proto.UInt32Size(3, x.Scope)
size += proto.BoolSize(4, x.RepOneOnly)
return size return size
} }
@ -6558,6 +6591,9 @@ func (x *StartShardEvacuationRequest_Body) EmitProtobuf(mm *easyproto.MessageMar
if x.Scope != 0 { if x.Scope != 0 {
mm.AppendUint32(3, x.Scope) mm.AppendUint32(3, x.Scope)
} }
if x.RepOneOnly {
mm.AppendBool(4, x.RepOneOnly)
}
} }
// UnmarshalProtobuf implements the encoding.ProtoUnmarshaler interface. // UnmarshalProtobuf implements the encoding.ProtoUnmarshaler interface.
@ -6587,6 +6623,12 @@ func (x *StartShardEvacuationRequest_Body) UnmarshalProtobuf(src []byte) (err er
return fmt.Errorf("cannot unmarshal field %s", "Scope") return fmt.Errorf("cannot unmarshal field %s", "Scope")
} }
x.Scope = data x.Scope = data
case 4: // RepOneOnly
data, ok := fc.Bool()
if !ok {
return fmt.Errorf("cannot unmarshal field %s", "RepOneOnly")
}
x.RepOneOnly = data
} }
} }
return nil return nil
@ -6618,6 +6660,15 @@ func (x *StartShardEvacuationRequest_Body) GetScope() uint32 {
func (x *StartShardEvacuationRequest_Body) SetScope(v uint32) { func (x *StartShardEvacuationRequest_Body) SetScope(v uint32) {
x.Scope = v x.Scope = v
} }
func (x *StartShardEvacuationRequest_Body) GetRepOneOnly() bool {
if x != nil {
return x.RepOneOnly
}
return false
}
func (x *StartShardEvacuationRequest_Body) SetRepOneOnly(v bool) {
x.RepOneOnly = v
}
// MarshalJSON implements the json.Marshaler interface. // MarshalJSON implements the json.Marshaler interface.
func (x *StartShardEvacuationRequest_Body) MarshalJSON() ([]byte, error) { func (x *StartShardEvacuationRequest_Body) MarshalJSON() ([]byte, error) {
@ -6653,6 +6704,11 @@ func (x *StartShardEvacuationRequest_Body) MarshalEasyJSON(out *jwriter.Writer)
out.RawString(prefix) out.RawString(prefix)
out.Uint32(x.Scope) out.Uint32(x.Scope)
} }
{
const prefix string = ",\"repOneOnly\":"
out.RawString(prefix)
out.Bool(x.RepOneOnly)
}
out.RawByte('}') out.RawByte('}')
} }
@ -6706,6 +6762,12 @@ func (x *StartShardEvacuationRequest_Body) UnmarshalEasyJSON(in *jlexer.Lexer) {
f = in.Uint32() f = in.Uint32()
x.Scope = f x.Scope = f
} }
case "repOneOnly":
{
var f bool
f = in.Bool()
x.RepOneOnly = f
}
} }
in.WantComma() in.WantComma()
} }