From 46ce8cbe84ef15eb5c9113662f2e9620d184fb50 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 10 Aug 2026 09:41:00 -0700 Subject: [PATCH] master: stream volume listings (#10676) * master: stream volume listings A listing of 800k volumes is 36MB on the wire but 305MB as messages, and the master built all of it, then held it while grpc encoded it. Two of those at once is most of a small master's heap, and the maintenance scanner asks every 30 minutes. The topology goes out first, listing nothing, then its volumes in batches, so the master holds a batch rather than a cluster: 341MB of live heap for one listing becomes 4.4MB. It allocates much the same either way -- what changes is how much of it has to be live at once, which is what sets the heap ceiling. Batches are built under their disk's lock and sent outside it, so a slow reader stalls the stream rather than the topology. They therefore do not share one instant, which a single listing did not either: it takes each disk's lock in turn, so a volume moving during either can be seen twice or not at all. The client helper hides which kind of master answered: one too old for the stream is asked the old way and its reply cut into the same batches. Either way the topology handed over lists no volumes, so a caller cannot come to depend on finding them there. * admin: stream the listing the maintenance scan reads It asks for every volume in the cluster every 30 minutes. Reassembling it client-side keeps the scan identical -- ActiveTopology splits disks by the disk ids on the volumes, so it needs them in the topology -- while the master no longer builds the whole reply to send it. --- weed/admin/maintenance/maintenance_scanner.go | 4 +- weed/pb/master.proto | 20 + weed/pb/master_pb/master.pb.go | 588 +++++++++++------- weed/pb/master_pb/master_grpc.pb.go | 41 ++ weed/pb/volume_list_stream.go | 212 +++++++ weed/pb/volume_list_stream_test.go | 306 +++++++++ weed/server/master_grpc_server_volume.go | 23 + weed/topology/volume_filter.go | 13 +- weed/topology/volume_stream.go | 137 ++++ weed/topology/volume_stream_bounds_test.go | 45 ++ 10 files changed, 1151 insertions(+), 238 deletions(-) create mode 100644 weed/pb/volume_list_stream.go create mode 100644 weed/pb/volume_list_stream_test.go create mode 100644 weed/topology/volume_stream.go create mode 100644 weed/topology/volume_stream_bounds_test.go diff --git a/weed/admin/maintenance/maintenance_scanner.go b/weed/admin/maintenance/maintenance_scanner.go index 5b963c1e9..f3fdd4d72 100644 --- a/weed/admin/maintenance/maintenance_scanner.go +++ b/weed/admin/maintenance/maintenance_scanner.go @@ -6,6 +6,7 @@ import ( "time" "github.com/seaweedfs/seaweedfs/weed/glog" + "github.com/seaweedfs/seaweedfs/weed/pb" "github.com/seaweedfs/seaweedfs/weed/pb/master_pb" "github.com/seaweedfs/seaweedfs/weed/worker/types" ) @@ -101,7 +102,8 @@ func (ms *MaintenanceScanner) getVolumeHealthMetrics() ([]*types.VolumeHealthMet glog.V(1).Infof("Collecting volume health metrics from master") err := ms.adminClient.WithMasterClient(func(client master_pb.SeaweedClient) error { - resp, err := client.VolumeList(context.Background(), &master_pb.VolumeListRequest{}) + // Streamed, so the master never builds the whole listing to send it. + resp, err := pb.CollectVolumeList(context.Background(), client, &master_pb.VolumeListRequest{}) if err != nil { return err } diff --git a/weed/pb/master.proto b/weed/pb/master.proto index 7ef6bbc4c..5e5c8df2c 100644 --- a/weed/pb/master.proto +++ b/weed/pb/master.proto @@ -27,6 +27,8 @@ service Seaweed { } rpc VolumeList (VolumeListRequest) returns (VolumeListResponse) { } + rpc VolumeListStream (VolumeListRequest) returns (stream VolumeListStreamResponse) { + } rpc LookupEcVolume (LookupEcVolumeRequest) returns (LookupEcVolumeResponse) { } rpc VacuumVolume (VacuumVolumeRequest) returns (VacuumVolumeResponse) { @@ -419,6 +421,24 @@ message VolumeListResponse { uint64 volume_size_limit_mb = 2; } +// VolumeListStream answers the same request as VolumeList without either end +// holding every volume in the cluster at once. At 800k volumes the reply is +// 36MB on the wire but 305MB as messages, which the master built in full +// before sending any of it. +message VolumeListStreamResponse { + // Sent once, first, listing no volumes: the topology, its disks and their + // counters. Every message after carries volumes for one of those disks. + VolumeListResponse header = 1; + // Which disk this batch is from. A disk arrives over as many batches as it + // takes, so append rather than assign. + string data_center = 2; + string rack = 3; + string data_node = 4; + string disk_type = 5; + repeated VolumeInformationMessage volume_infos = 6; + repeated VolumeEcShardInformationMessage ec_shard_infos = 7; +} + message LookupEcVolumeRequest { uint32 volume_id = 1; } diff --git a/weed/pb/master_pb/master.pb.go b/weed/pb/master_pb/master.pb.go index 751edb0f4..6395c36cc 100644 --- a/weed/pb/master_pb/master.pb.go +++ b/weed/pb/master_pb/master.pb.go @@ -2895,6 +2895,106 @@ func (x *VolumeListResponse) GetVolumeSizeLimitMb() uint64 { return 0 } +// VolumeListStream answers the same request as VolumeList without either end +// holding every volume in the cluster at once. At 800k volumes the reply is +// 36MB on the wire but 305MB as messages, which the master built in full +// before sending any of it. +type VolumeListStreamResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Sent once, first, listing no volumes: the topology, its disks and their + // counters. Every message after carries volumes for one of those disks. + Header *VolumeListResponse `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` + // Which disk this batch is from. A disk arrives over as many batches as it + // takes, so append rather than assign. + DataCenter string `protobuf:"bytes,2,opt,name=data_center,json=dataCenter,proto3" json:"data_center,omitempty"` + Rack string `protobuf:"bytes,3,opt,name=rack,proto3" json:"rack,omitempty"` + DataNode string `protobuf:"bytes,4,opt,name=data_node,json=dataNode,proto3" json:"data_node,omitempty"` + DiskType string `protobuf:"bytes,5,opt,name=disk_type,json=diskType,proto3" json:"disk_type,omitempty"` + VolumeInfos []*VolumeInformationMessage `protobuf:"bytes,6,rep,name=volume_infos,json=volumeInfos,proto3" json:"volume_infos,omitempty"` + EcShardInfos []*VolumeEcShardInformationMessage `protobuf:"bytes,7,rep,name=ec_shard_infos,json=ecShardInfos,proto3" json:"ec_shard_infos,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VolumeListStreamResponse) Reset() { + *x = VolumeListStreamResponse{} + mi := &file_master_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VolumeListStreamResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VolumeListStreamResponse) ProtoMessage() {} + +func (x *VolumeListStreamResponse) ProtoReflect() protoreflect.Message { + mi := &file_master_proto_msgTypes[37] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VolumeListStreamResponse.ProtoReflect.Descriptor instead. +func (*VolumeListStreamResponse) Descriptor() ([]byte, []int) { + return file_master_proto_rawDescGZIP(), []int{37} +} + +func (x *VolumeListStreamResponse) GetHeader() *VolumeListResponse { + if x != nil { + return x.Header + } + return nil +} + +func (x *VolumeListStreamResponse) GetDataCenter() string { + if x != nil { + return x.DataCenter + } + return "" +} + +func (x *VolumeListStreamResponse) GetRack() string { + if x != nil { + return x.Rack + } + return "" +} + +func (x *VolumeListStreamResponse) GetDataNode() string { + if x != nil { + return x.DataNode + } + return "" +} + +func (x *VolumeListStreamResponse) GetDiskType() string { + if x != nil { + return x.DiskType + } + return "" +} + +func (x *VolumeListStreamResponse) GetVolumeInfos() []*VolumeInformationMessage { + if x != nil { + return x.VolumeInfos + } + return nil +} + +func (x *VolumeListStreamResponse) GetEcShardInfos() []*VolumeEcShardInformationMessage { + if x != nil { + return x.EcShardInfos + } + return nil +} + type LookupEcVolumeRequest struct { state protoimpl.MessageState `protogen:"open.v1"` VolumeId uint32 `protobuf:"varint,1,opt,name=volume_id,json=volumeId,proto3" json:"volume_id,omitempty"` @@ -2904,7 +3004,7 @@ type LookupEcVolumeRequest struct { func (x *LookupEcVolumeRequest) Reset() { *x = LookupEcVolumeRequest{} - mi := &file_master_proto_msgTypes[37] + mi := &file_master_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2916,7 +3016,7 @@ func (x *LookupEcVolumeRequest) String() string { func (*LookupEcVolumeRequest) ProtoMessage() {} func (x *LookupEcVolumeRequest) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[37] + mi := &file_master_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2929,7 +3029,7 @@ func (x *LookupEcVolumeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LookupEcVolumeRequest.ProtoReflect.Descriptor instead. func (*LookupEcVolumeRequest) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{37} + return file_master_proto_rawDescGZIP(), []int{38} } func (x *LookupEcVolumeRequest) GetVolumeId() uint32 { @@ -2949,7 +3049,7 @@ type LookupEcVolumeResponse struct { func (x *LookupEcVolumeResponse) Reset() { *x = LookupEcVolumeResponse{} - mi := &file_master_proto_msgTypes[38] + mi := &file_master_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2961,7 +3061,7 @@ func (x *LookupEcVolumeResponse) String() string { func (*LookupEcVolumeResponse) ProtoMessage() {} func (x *LookupEcVolumeResponse) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[38] + mi := &file_master_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2974,7 +3074,7 @@ func (x *LookupEcVolumeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LookupEcVolumeResponse.ProtoReflect.Descriptor instead. func (*LookupEcVolumeResponse) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{38} + return file_master_proto_rawDescGZIP(), []int{39} } func (x *LookupEcVolumeResponse) GetVolumeId() uint32 { @@ -3002,7 +3102,7 @@ type VacuumVolumeRequest struct { func (x *VacuumVolumeRequest) Reset() { *x = VacuumVolumeRequest{} - mi := &file_master_proto_msgTypes[39] + mi := &file_master_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3014,7 +3114,7 @@ func (x *VacuumVolumeRequest) String() string { func (*VacuumVolumeRequest) ProtoMessage() {} func (x *VacuumVolumeRequest) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[39] + mi := &file_master_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3027,7 +3127,7 @@ func (x *VacuumVolumeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VacuumVolumeRequest.ProtoReflect.Descriptor instead. func (*VacuumVolumeRequest) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{39} + return file_master_proto_rawDescGZIP(), []int{40} } func (x *VacuumVolumeRequest) GetGarbageThreshold() float32 { @@ -3059,7 +3159,7 @@ type VacuumVolumeResponse struct { func (x *VacuumVolumeResponse) Reset() { *x = VacuumVolumeResponse{} - mi := &file_master_proto_msgTypes[40] + mi := &file_master_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3071,7 +3171,7 @@ func (x *VacuumVolumeResponse) String() string { func (*VacuumVolumeResponse) ProtoMessage() {} func (x *VacuumVolumeResponse) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[40] + mi := &file_master_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3084,7 +3184,7 @@ func (x *VacuumVolumeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VacuumVolumeResponse.ProtoReflect.Descriptor instead. func (*VacuumVolumeResponse) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{40} + return file_master_proto_rawDescGZIP(), []int{41} } type DisableVacuumRequest struct { @@ -3096,7 +3196,7 @@ type DisableVacuumRequest struct { func (x *DisableVacuumRequest) Reset() { *x = DisableVacuumRequest{} - mi := &file_master_proto_msgTypes[41] + mi := &file_master_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3108,7 +3208,7 @@ func (x *DisableVacuumRequest) String() string { func (*DisableVacuumRequest) ProtoMessage() {} func (x *DisableVacuumRequest) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[41] + mi := &file_master_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3121,7 +3221,7 @@ func (x *DisableVacuumRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DisableVacuumRequest.ProtoReflect.Descriptor instead. func (*DisableVacuumRequest) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{41} + return file_master_proto_rawDescGZIP(), []int{42} } func (x *DisableVacuumRequest) GetByPlugin() bool { @@ -3139,7 +3239,7 @@ type DisableVacuumResponse struct { func (x *DisableVacuumResponse) Reset() { *x = DisableVacuumResponse{} - mi := &file_master_proto_msgTypes[42] + mi := &file_master_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3151,7 +3251,7 @@ func (x *DisableVacuumResponse) String() string { func (*DisableVacuumResponse) ProtoMessage() {} func (x *DisableVacuumResponse) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[42] + mi := &file_master_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3164,7 +3264,7 @@ func (x *DisableVacuumResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DisableVacuumResponse.ProtoReflect.Descriptor instead. func (*DisableVacuumResponse) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{42} + return file_master_proto_rawDescGZIP(), []int{43} } type EnableVacuumRequest struct { @@ -3176,7 +3276,7 @@ type EnableVacuumRequest struct { func (x *EnableVacuumRequest) Reset() { *x = EnableVacuumRequest{} - mi := &file_master_proto_msgTypes[43] + mi := &file_master_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3188,7 +3288,7 @@ func (x *EnableVacuumRequest) String() string { func (*EnableVacuumRequest) ProtoMessage() {} func (x *EnableVacuumRequest) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[43] + mi := &file_master_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3201,7 +3301,7 @@ func (x *EnableVacuumRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EnableVacuumRequest.ProtoReflect.Descriptor instead. func (*EnableVacuumRequest) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{43} + return file_master_proto_rawDescGZIP(), []int{44} } func (x *EnableVacuumRequest) GetByPlugin() bool { @@ -3219,7 +3319,7 @@ type EnableVacuumResponse struct { func (x *EnableVacuumResponse) Reset() { *x = EnableVacuumResponse{} - mi := &file_master_proto_msgTypes[44] + mi := &file_master_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3231,7 +3331,7 @@ func (x *EnableVacuumResponse) String() string { func (*EnableVacuumResponse) ProtoMessage() {} func (x *EnableVacuumResponse) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[44] + mi := &file_master_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3244,7 +3344,7 @@ func (x *EnableVacuumResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EnableVacuumResponse.ProtoReflect.Descriptor instead. func (*EnableVacuumResponse) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{44} + return file_master_proto_rawDescGZIP(), []int{45} } type VolumeMarkReadonlyRequest struct { @@ -3264,7 +3364,7 @@ type VolumeMarkReadonlyRequest struct { func (x *VolumeMarkReadonlyRequest) Reset() { *x = VolumeMarkReadonlyRequest{} - mi := &file_master_proto_msgTypes[45] + mi := &file_master_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3276,7 +3376,7 @@ func (x *VolumeMarkReadonlyRequest) String() string { func (*VolumeMarkReadonlyRequest) ProtoMessage() {} func (x *VolumeMarkReadonlyRequest) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[45] + mi := &file_master_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3289,7 +3389,7 @@ func (x *VolumeMarkReadonlyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VolumeMarkReadonlyRequest.ProtoReflect.Descriptor instead. func (*VolumeMarkReadonlyRequest) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{45} + return file_master_proto_rawDescGZIP(), []int{46} } func (x *VolumeMarkReadonlyRequest) GetIp() string { @@ -3363,7 +3463,7 @@ type VolumeMarkReadonlyResponse struct { func (x *VolumeMarkReadonlyResponse) Reset() { *x = VolumeMarkReadonlyResponse{} - mi := &file_master_proto_msgTypes[46] + mi := &file_master_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3375,7 +3475,7 @@ func (x *VolumeMarkReadonlyResponse) String() string { func (*VolumeMarkReadonlyResponse) ProtoMessage() {} func (x *VolumeMarkReadonlyResponse) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[46] + mi := &file_master_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3388,7 +3488,7 @@ func (x *VolumeMarkReadonlyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VolumeMarkReadonlyResponse.ProtoReflect.Descriptor instead. func (*VolumeMarkReadonlyResponse) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{46} + return file_master_proto_rawDescGZIP(), []int{47} } type GetMasterConfigurationRequest struct { @@ -3399,7 +3499,7 @@ type GetMasterConfigurationRequest struct { func (x *GetMasterConfigurationRequest) Reset() { *x = GetMasterConfigurationRequest{} - mi := &file_master_proto_msgTypes[47] + mi := &file_master_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3411,7 +3511,7 @@ func (x *GetMasterConfigurationRequest) String() string { func (*GetMasterConfigurationRequest) ProtoMessage() {} func (x *GetMasterConfigurationRequest) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[47] + mi := &file_master_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3424,7 +3524,7 @@ func (x *GetMasterConfigurationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMasterConfigurationRequest.ProtoReflect.Descriptor instead. func (*GetMasterConfigurationRequest) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{47} + return file_master_proto_rawDescGZIP(), []int{48} } type GetMasterConfigurationResponse struct { @@ -3445,7 +3545,7 @@ type GetMasterConfigurationResponse struct { func (x *GetMasterConfigurationResponse) Reset() { *x = GetMasterConfigurationResponse{} - mi := &file_master_proto_msgTypes[48] + mi := &file_master_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3457,7 +3557,7 @@ func (x *GetMasterConfigurationResponse) String() string { func (*GetMasterConfigurationResponse) ProtoMessage() {} func (x *GetMasterConfigurationResponse) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[48] + mi := &file_master_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3470,7 +3570,7 @@ func (x *GetMasterConfigurationResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMasterConfigurationResponse.ProtoReflect.Descriptor instead. func (*GetMasterConfigurationResponse) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{48} + return file_master_proto_rawDescGZIP(), []int{49} } func (x *GetMasterConfigurationResponse) GetMetricsAddress() string { @@ -3547,7 +3647,7 @@ type ListClusterNodesRequest struct { func (x *ListClusterNodesRequest) Reset() { *x = ListClusterNodesRequest{} - mi := &file_master_proto_msgTypes[49] + mi := &file_master_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3559,7 +3659,7 @@ func (x *ListClusterNodesRequest) String() string { func (*ListClusterNodesRequest) ProtoMessage() {} func (x *ListClusterNodesRequest) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[49] + mi := &file_master_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3572,7 +3672,7 @@ func (x *ListClusterNodesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListClusterNodesRequest.ProtoReflect.Descriptor instead. func (*ListClusterNodesRequest) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{49} + return file_master_proto_rawDescGZIP(), []int{50} } func (x *ListClusterNodesRequest) GetClientType() string { @@ -3605,7 +3705,7 @@ type ListClusterNodesResponse struct { func (x *ListClusterNodesResponse) Reset() { *x = ListClusterNodesResponse{} - mi := &file_master_proto_msgTypes[50] + mi := &file_master_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3617,7 +3717,7 @@ func (x *ListClusterNodesResponse) String() string { func (*ListClusterNodesResponse) ProtoMessage() {} func (x *ListClusterNodesResponse) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[50] + mi := &file_master_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3630,7 +3730,7 @@ func (x *ListClusterNodesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListClusterNodesResponse.ProtoReflect.Descriptor instead. func (*ListClusterNodesResponse) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{50} + return file_master_proto_rawDescGZIP(), []int{51} } func (x *ListClusterNodesResponse) GetClusterNodes() []*ListClusterNodesResponse_ClusterNode { @@ -3653,7 +3753,7 @@ type LeaseAdminTokenRequest struct { func (x *LeaseAdminTokenRequest) Reset() { *x = LeaseAdminTokenRequest{} - mi := &file_master_proto_msgTypes[51] + mi := &file_master_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3665,7 +3765,7 @@ func (x *LeaseAdminTokenRequest) String() string { func (*LeaseAdminTokenRequest) ProtoMessage() {} func (x *LeaseAdminTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[51] + mi := &file_master_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3678,7 +3778,7 @@ func (x *LeaseAdminTokenRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LeaseAdminTokenRequest.ProtoReflect.Descriptor instead. func (*LeaseAdminTokenRequest) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{51} + return file_master_proto_rawDescGZIP(), []int{52} } func (x *LeaseAdminTokenRequest) GetPreviousToken() int64 { @@ -3726,7 +3826,7 @@ type LeaseAdminTokenResponse struct { func (x *LeaseAdminTokenResponse) Reset() { *x = LeaseAdminTokenResponse{} - mi := &file_master_proto_msgTypes[52] + mi := &file_master_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3738,7 +3838,7 @@ func (x *LeaseAdminTokenResponse) String() string { func (*LeaseAdminTokenResponse) ProtoMessage() {} func (x *LeaseAdminTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[52] + mi := &file_master_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3751,7 +3851,7 @@ func (x *LeaseAdminTokenResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LeaseAdminTokenResponse.ProtoReflect.Descriptor instead. func (*LeaseAdminTokenResponse) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{52} + return file_master_proto_rawDescGZIP(), []int{53} } func (x *LeaseAdminTokenResponse) GetToken() int64 { @@ -3779,7 +3879,7 @@ type ReleaseAdminTokenRequest struct { func (x *ReleaseAdminTokenRequest) Reset() { *x = ReleaseAdminTokenRequest{} - mi := &file_master_proto_msgTypes[53] + mi := &file_master_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3791,7 +3891,7 @@ func (x *ReleaseAdminTokenRequest) String() string { func (*ReleaseAdminTokenRequest) ProtoMessage() {} func (x *ReleaseAdminTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[53] + mi := &file_master_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3804,7 +3904,7 @@ func (x *ReleaseAdminTokenRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReleaseAdminTokenRequest.ProtoReflect.Descriptor instead. func (*ReleaseAdminTokenRequest) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{53} + return file_master_proto_rawDescGZIP(), []int{54} } func (x *ReleaseAdminTokenRequest) GetPreviousToken() int64 { @@ -3836,7 +3936,7 @@ type ReleaseAdminTokenResponse struct { func (x *ReleaseAdminTokenResponse) Reset() { *x = ReleaseAdminTokenResponse{} - mi := &file_master_proto_msgTypes[54] + mi := &file_master_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3848,7 +3948,7 @@ func (x *ReleaseAdminTokenResponse) String() string { func (*ReleaseAdminTokenResponse) ProtoMessage() {} func (x *ReleaseAdminTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[54] + mi := &file_master_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3861,7 +3961,7 @@ func (x *ReleaseAdminTokenResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReleaseAdminTokenResponse.ProtoReflect.Descriptor instead. func (*ReleaseAdminTokenResponse) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{54} + return file_master_proto_rawDescGZIP(), []int{55} } type GetAdminLockStatusRequest struct { @@ -3873,7 +3973,7 @@ type GetAdminLockStatusRequest struct { func (x *GetAdminLockStatusRequest) Reset() { *x = GetAdminLockStatusRequest{} - mi := &file_master_proto_msgTypes[55] + mi := &file_master_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3885,7 +3985,7 @@ func (x *GetAdminLockStatusRequest) String() string { func (*GetAdminLockStatusRequest) ProtoMessage() {} func (x *GetAdminLockStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[55] + mi := &file_master_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3898,7 +3998,7 @@ func (x *GetAdminLockStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAdminLockStatusRequest.ProtoReflect.Descriptor instead. func (*GetAdminLockStatusRequest) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{55} + return file_master_proto_rawDescGZIP(), []int{56} } func (x *GetAdminLockStatusRequest) GetLockName() string { @@ -3919,7 +4019,7 @@ type GetAdminLockStatusResponse struct { func (x *GetAdminLockStatusResponse) Reset() { *x = GetAdminLockStatusResponse{} - mi := &file_master_proto_msgTypes[56] + mi := &file_master_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3931,7 +4031,7 @@ func (x *GetAdminLockStatusResponse) String() string { func (*GetAdminLockStatusResponse) ProtoMessage() {} func (x *GetAdminLockStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[56] + mi := &file_master_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3944,7 +4044,7 @@ func (x *GetAdminLockStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAdminLockStatusResponse.ProtoReflect.Descriptor instead. func (*GetAdminLockStatusResponse) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{56} + return file_master_proto_rawDescGZIP(), []int{57} } func (x *GetAdminLockStatusResponse) GetIsLocked() bool { @@ -3978,7 +4078,7 @@ type PingRequest struct { func (x *PingRequest) Reset() { *x = PingRequest{} - mi := &file_master_proto_msgTypes[57] + mi := &file_master_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3990,7 +4090,7 @@ func (x *PingRequest) String() string { func (*PingRequest) ProtoMessage() {} func (x *PingRequest) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[57] + mi := &file_master_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4003,7 +4103,7 @@ func (x *PingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. func (*PingRequest) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{57} + return file_master_proto_rawDescGZIP(), []int{58} } func (x *PingRequest) GetTarget() string { @@ -4031,7 +4131,7 @@ type PingResponse struct { func (x *PingResponse) Reset() { *x = PingResponse{} - mi := &file_master_proto_msgTypes[58] + mi := &file_master_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4043,7 +4143,7 @@ func (x *PingResponse) String() string { func (*PingResponse) ProtoMessage() {} func (x *PingResponse) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[58] + mi := &file_master_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4056,7 +4156,7 @@ func (x *PingResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PingResponse.ProtoReflect.Descriptor instead. func (*PingResponse) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{58} + return file_master_proto_rawDescGZIP(), []int{59} } func (x *PingResponse) GetStartTimeNs() int64 { @@ -4091,7 +4191,7 @@ type RaftAddServerRequest struct { func (x *RaftAddServerRequest) Reset() { *x = RaftAddServerRequest{} - mi := &file_master_proto_msgTypes[59] + mi := &file_master_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4103,7 +4203,7 @@ func (x *RaftAddServerRequest) String() string { func (*RaftAddServerRequest) ProtoMessage() {} func (x *RaftAddServerRequest) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[59] + mi := &file_master_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4116,7 +4216,7 @@ func (x *RaftAddServerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RaftAddServerRequest.ProtoReflect.Descriptor instead. func (*RaftAddServerRequest) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{59} + return file_master_proto_rawDescGZIP(), []int{60} } func (x *RaftAddServerRequest) GetId() string { @@ -4148,7 +4248,7 @@ type RaftAddServerResponse struct { func (x *RaftAddServerResponse) Reset() { *x = RaftAddServerResponse{} - mi := &file_master_proto_msgTypes[60] + mi := &file_master_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4160,7 +4260,7 @@ func (x *RaftAddServerResponse) String() string { func (*RaftAddServerResponse) ProtoMessage() {} func (x *RaftAddServerResponse) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[60] + mi := &file_master_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4173,7 +4273,7 @@ func (x *RaftAddServerResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RaftAddServerResponse.ProtoReflect.Descriptor instead. func (*RaftAddServerResponse) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{60} + return file_master_proto_rawDescGZIP(), []int{61} } type RaftRemoveServerRequest struct { @@ -4186,7 +4286,7 @@ type RaftRemoveServerRequest struct { func (x *RaftRemoveServerRequest) Reset() { *x = RaftRemoveServerRequest{} - mi := &file_master_proto_msgTypes[61] + mi := &file_master_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4198,7 +4298,7 @@ func (x *RaftRemoveServerRequest) String() string { func (*RaftRemoveServerRequest) ProtoMessage() {} func (x *RaftRemoveServerRequest) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[61] + mi := &file_master_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4211,7 +4311,7 @@ func (x *RaftRemoveServerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RaftRemoveServerRequest.ProtoReflect.Descriptor instead. func (*RaftRemoveServerRequest) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{61} + return file_master_proto_rawDescGZIP(), []int{62} } func (x *RaftRemoveServerRequest) GetId() string { @@ -4236,7 +4336,7 @@ type RaftRemoveServerResponse struct { func (x *RaftRemoveServerResponse) Reset() { *x = RaftRemoveServerResponse{} - mi := &file_master_proto_msgTypes[62] + mi := &file_master_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4248,7 +4348,7 @@ func (x *RaftRemoveServerResponse) String() string { func (*RaftRemoveServerResponse) ProtoMessage() {} func (x *RaftRemoveServerResponse) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[62] + mi := &file_master_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4261,7 +4361,7 @@ func (x *RaftRemoveServerResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RaftRemoveServerResponse.ProtoReflect.Descriptor instead. func (*RaftRemoveServerResponse) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{62} + return file_master_proto_rawDescGZIP(), []int{63} } type RaftListClusterServersRequest struct { @@ -4272,7 +4372,7 @@ type RaftListClusterServersRequest struct { func (x *RaftListClusterServersRequest) Reset() { *x = RaftListClusterServersRequest{} - mi := &file_master_proto_msgTypes[63] + mi := &file_master_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4284,7 +4384,7 @@ func (x *RaftListClusterServersRequest) String() string { func (*RaftListClusterServersRequest) ProtoMessage() {} func (x *RaftListClusterServersRequest) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[63] + mi := &file_master_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4297,7 +4397,7 @@ func (x *RaftListClusterServersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RaftListClusterServersRequest.ProtoReflect.Descriptor instead. func (*RaftListClusterServersRequest) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{63} + return file_master_proto_rawDescGZIP(), []int{64} } type RaftListClusterServersResponse struct { @@ -4309,7 +4409,7 @@ type RaftListClusterServersResponse struct { func (x *RaftListClusterServersResponse) Reset() { *x = RaftListClusterServersResponse{} - mi := &file_master_proto_msgTypes[64] + mi := &file_master_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4321,7 +4421,7 @@ func (x *RaftListClusterServersResponse) String() string { func (*RaftListClusterServersResponse) ProtoMessage() {} func (x *RaftListClusterServersResponse) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[64] + mi := &file_master_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4334,7 +4434,7 @@ func (x *RaftListClusterServersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RaftListClusterServersResponse.ProtoReflect.Descriptor instead. func (*RaftListClusterServersResponse) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{64} + return file_master_proto_rawDescGZIP(), []int{65} } func (x *RaftListClusterServersResponse) GetClusterServers() []*RaftListClusterServersResponse_ClusterServers { @@ -4354,7 +4454,7 @@ type RaftLeadershipTransferRequest struct { func (x *RaftLeadershipTransferRequest) Reset() { *x = RaftLeadershipTransferRequest{} - mi := &file_master_proto_msgTypes[65] + mi := &file_master_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4366,7 +4466,7 @@ func (x *RaftLeadershipTransferRequest) String() string { func (*RaftLeadershipTransferRequest) ProtoMessage() {} func (x *RaftLeadershipTransferRequest) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[65] + mi := &file_master_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4379,7 +4479,7 @@ func (x *RaftLeadershipTransferRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RaftLeadershipTransferRequest.ProtoReflect.Descriptor instead. func (*RaftLeadershipTransferRequest) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{65} + return file_master_proto_rawDescGZIP(), []int{66} } func (x *RaftLeadershipTransferRequest) GetTargetId() string { @@ -4406,7 +4506,7 @@ type RaftLeadershipTransferResponse struct { func (x *RaftLeadershipTransferResponse) Reset() { *x = RaftLeadershipTransferResponse{} - mi := &file_master_proto_msgTypes[66] + mi := &file_master_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4418,7 +4518,7 @@ func (x *RaftLeadershipTransferResponse) String() string { func (*RaftLeadershipTransferResponse) ProtoMessage() {} func (x *RaftLeadershipTransferResponse) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[66] + mi := &file_master_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4431,7 +4531,7 @@ func (x *RaftLeadershipTransferResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RaftLeadershipTransferResponse.ProtoReflect.Descriptor instead. func (*RaftLeadershipTransferResponse) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{66} + return file_master_proto_rawDescGZIP(), []int{67} } func (x *RaftLeadershipTransferResponse) GetPreviousLeader() string { @@ -4456,7 +4556,7 @@ type VolumeGrowResponse struct { func (x *VolumeGrowResponse) Reset() { *x = VolumeGrowResponse{} - mi := &file_master_proto_msgTypes[67] + mi := &file_master_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4468,7 +4568,7 @@ func (x *VolumeGrowResponse) String() string { func (*VolumeGrowResponse) ProtoMessage() {} func (x *VolumeGrowResponse) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[67] + mi := &file_master_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4481,7 +4581,7 @@ func (x *VolumeGrowResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VolumeGrowResponse.ProtoReflect.Descriptor instead. func (*VolumeGrowResponse) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{67} + return file_master_proto_rawDescGZIP(), []int{68} } type SuperBlockExtra_ErasureCoding struct { @@ -4495,7 +4595,7 @@ type SuperBlockExtra_ErasureCoding struct { func (x *SuperBlockExtra_ErasureCoding) Reset() { *x = SuperBlockExtra_ErasureCoding{} - mi := &file_master_proto_msgTypes[72] + mi := &file_master_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4507,7 +4607,7 @@ func (x *SuperBlockExtra_ErasureCoding) String() string { func (*SuperBlockExtra_ErasureCoding) ProtoMessage() {} func (x *SuperBlockExtra_ErasureCoding) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[72] + mi := &file_master_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4556,7 +4656,7 @@ type LookupVolumeResponse_VolumeIdLocation struct { func (x *LookupVolumeResponse_VolumeIdLocation) Reset() { *x = LookupVolumeResponse_VolumeIdLocation{} - mi := &file_master_proto_msgTypes[73] + mi := &file_master_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4568,7 +4668,7 @@ func (x *LookupVolumeResponse_VolumeIdLocation) String() string { func (*LookupVolumeResponse_VolumeIdLocation) ProtoMessage() {} func (x *LookupVolumeResponse_VolumeIdLocation) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[73] + mi := &file_master_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4622,7 +4722,7 @@ type LookupEcVolumeResponse_EcShardIdLocation struct { func (x *LookupEcVolumeResponse_EcShardIdLocation) Reset() { *x = LookupEcVolumeResponse_EcShardIdLocation{} - mi := &file_master_proto_msgTypes[79] + mi := &file_master_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4634,7 +4734,7 @@ func (x *LookupEcVolumeResponse_EcShardIdLocation) String() string { func (*LookupEcVolumeResponse_EcShardIdLocation) ProtoMessage() {} func (x *LookupEcVolumeResponse_EcShardIdLocation) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[79] + mi := &file_master_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4647,7 +4747,7 @@ func (x *LookupEcVolumeResponse_EcShardIdLocation) ProtoReflect() protoreflect.M // Deprecated: Use LookupEcVolumeResponse_EcShardIdLocation.ProtoReflect.Descriptor instead. func (*LookupEcVolumeResponse_EcShardIdLocation) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{38, 0} + return file_master_proto_rawDescGZIP(), []int{39, 0} } func (x *LookupEcVolumeResponse_EcShardIdLocation) GetShardId() uint32 { @@ -4677,7 +4777,7 @@ type ListClusterNodesResponse_ClusterNode struct { func (x *ListClusterNodesResponse_ClusterNode) Reset() { *x = ListClusterNodesResponse_ClusterNode{} - mi := &file_master_proto_msgTypes[80] + mi := &file_master_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4689,7 +4789,7 @@ func (x *ListClusterNodesResponse_ClusterNode) String() string { func (*ListClusterNodesResponse_ClusterNode) ProtoMessage() {} func (x *ListClusterNodesResponse_ClusterNode) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[80] + mi := &file_master_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4702,7 +4802,7 @@ func (x *ListClusterNodesResponse_ClusterNode) ProtoReflect() protoreflect.Messa // Deprecated: Use ListClusterNodesResponse_ClusterNode.ProtoReflect.Descriptor instead. func (*ListClusterNodesResponse_ClusterNode) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{50, 0} + return file_master_proto_rawDescGZIP(), []int{51, 0} } func (x *ListClusterNodesResponse_ClusterNode) GetAddress() string { @@ -4752,7 +4852,7 @@ type RaftListClusterServersResponse_ClusterServers struct { func (x *RaftListClusterServersResponse_ClusterServers) Reset() { *x = RaftListClusterServersResponse_ClusterServers{} - mi := &file_master_proto_msgTypes[81] + mi := &file_master_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4764,7 +4864,7 @@ func (x *RaftListClusterServersResponse_ClusterServers) String() string { func (*RaftListClusterServersResponse_ClusterServers) ProtoMessage() {} func (x *RaftListClusterServersResponse_ClusterServers) ProtoReflect() protoreflect.Message { - mi := &file_master_proto_msgTypes[81] + mi := &file_master_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4777,7 +4877,7 @@ func (x *RaftListClusterServersResponse_ClusterServers) ProtoReflect() protorefl // Deprecated: Use RaftListClusterServersResponse_ClusterServers.ProtoReflect.Descriptor instead. func (*RaftListClusterServersResponse_ClusterServers) Descriptor() ([]byte, []int) { - return file_master_proto_rawDescGZIP(), []int{64, 0} + return file_master_proto_rawDescGZIP(), []int{65, 0} } func (x *RaftListClusterServersResponse_ClusterServers) GetId() string { @@ -5124,7 +5224,16 @@ const file_master_proto_rawDesc = "" + "\x17default_collection_only\x18\x03 \x01(\bR\x15defaultCollectionOnly\"\x83\x01\n" + "\x12VolumeListResponse\x12<\n" + "\rtopology_info\x18\x01 \x01(\v2\x17.master_pb.TopologyInfoR\ftopologyInfo\x12/\n" + - "\x14volume_size_limit_mb\x18\x02 \x01(\x04R\x11volumeSizeLimitMb\"4\n" + + "\x14volume_size_limit_mb\x18\x02 \x01(\x04R\x11volumeSizeLimitMb\"\xda\x02\n" + + "\x18VolumeListStreamResponse\x125\n" + + "\x06header\x18\x01 \x01(\v2\x1d.master_pb.VolumeListResponseR\x06header\x12\x1f\n" + + "\vdata_center\x18\x02 \x01(\tR\n" + + "dataCenter\x12\x12\n" + + "\x04rack\x18\x03 \x01(\tR\x04rack\x12\x1b\n" + + "\tdata_node\x18\x04 \x01(\tR\bdataNode\x12\x1b\n" + + "\tdisk_type\x18\x05 \x01(\tR\bdiskType\x12F\n" + + "\fvolume_infos\x18\x06 \x03(\v2#.master_pb.VolumeInformationMessageR\vvolumeInfos\x12P\n" + + "\x0eec_shard_infos\x18\a \x03(\v2*.master_pb.VolumeEcShardInformationMessageR\fecShardInfos\"4\n" + "\x15LookupEcVolumeRequest\x12\x1b\n" + "\tvolume_id\x18\x01 \x01(\rR\bvolumeId\"\xfb\x01\n" + "\x16LookupEcVolumeResponse\x12\x1b\n" + @@ -5243,7 +5352,7 @@ const file_master_proto_rawDesc = "" + "\x0fprevious_leader\x18\x01 \x01(\tR\x0epreviousLeader\x12\x1d\n" + "\n" + "new_leader\x18\x02 \x01(\tR\tnewLeader\"\x14\n" + - "\x12VolumeGrowResponse2\x96\x12\n" + + "\x12VolumeGrowResponse2\xf1\x12\n" + "\aSeaweed\x12I\n" + "\rSendHeartbeat\x12\x14.master_pb.Heartbeat\x1a\x1c.master_pb.HeartbeatResponse\"\x00(\x010\x01\x12X\n" + "\rKeepConnected\x12\x1f.master_pb.KeepConnectedRequest\x1a .master_pb.KeepConnectedResponse\"\x00(\x010\x01\x12Q\n" + @@ -5255,7 +5364,8 @@ const file_master_proto_rawDesc = "" + "\x0eCollectionList\x12 .master_pb.CollectionListRequest\x1a!.master_pb.CollectionListResponse\"\x00\x12]\n" + "\x10CollectionDelete\x12\".master_pb.CollectionDeleteRequest\x1a#.master_pb.CollectionDeleteResponse\"\x00\x12K\n" + "\n" + - "VolumeList\x12\x1c.master_pb.VolumeListRequest\x1a\x1d.master_pb.VolumeListResponse\"\x00\x12W\n" + + "VolumeList\x12\x1c.master_pb.VolumeListRequest\x1a\x1d.master_pb.VolumeListResponse\"\x00\x12Y\n" + + "\x10VolumeListStream\x12\x1c.master_pb.VolumeListRequest\x1a#.master_pb.VolumeListStreamResponse\"\x000\x01\x12W\n" + "\x0eLookupEcVolume\x12 .master_pb.LookupEcVolumeRequest\x1a!.master_pb.LookupEcVolumeResponse\"\x00\x12Q\n" + "\fVacuumVolume\x12\x1e.master_pb.VacuumVolumeRequest\x1a\x1f.master_pb.VacuumVolumeResponse\"\x00\x12T\n" + "\rDisableVacuum\x12\x1f.master_pb.DisableVacuumRequest\x1a .master_pb.DisableVacuumResponse\"\x00\x12Q\n" + @@ -5287,7 +5397,7 @@ func file_master_proto_rawDescGZIP() []byte { return file_master_proto_rawDescData } -var file_master_proto_msgTypes = make([]protoimpl.MessageInfo, 82) +var file_master_proto_msgTypes = make([]protoimpl.MessageInfo, 83) var file_master_proto_goTypes = []any{ (*DiskTag)(nil), // 0: master_pb.DiskTag (*Heartbeat)(nil), // 1: master_pb.Heartbeat @@ -5326,52 +5436,53 @@ var file_master_proto_goTypes = []any{ (*TopologyInfo)(nil), // 34: master_pb.TopologyInfo (*VolumeListRequest)(nil), // 35: master_pb.VolumeListRequest (*VolumeListResponse)(nil), // 36: master_pb.VolumeListResponse - (*LookupEcVolumeRequest)(nil), // 37: master_pb.LookupEcVolumeRequest - (*LookupEcVolumeResponse)(nil), // 38: master_pb.LookupEcVolumeResponse - (*VacuumVolumeRequest)(nil), // 39: master_pb.VacuumVolumeRequest - (*VacuumVolumeResponse)(nil), // 40: master_pb.VacuumVolumeResponse - (*DisableVacuumRequest)(nil), // 41: master_pb.DisableVacuumRequest - (*DisableVacuumResponse)(nil), // 42: master_pb.DisableVacuumResponse - (*EnableVacuumRequest)(nil), // 43: master_pb.EnableVacuumRequest - (*EnableVacuumResponse)(nil), // 44: master_pb.EnableVacuumResponse - (*VolumeMarkReadonlyRequest)(nil), // 45: master_pb.VolumeMarkReadonlyRequest - (*VolumeMarkReadonlyResponse)(nil), // 46: master_pb.VolumeMarkReadonlyResponse - (*GetMasterConfigurationRequest)(nil), // 47: master_pb.GetMasterConfigurationRequest - (*GetMasterConfigurationResponse)(nil), // 48: master_pb.GetMasterConfigurationResponse - (*ListClusterNodesRequest)(nil), // 49: master_pb.ListClusterNodesRequest - (*ListClusterNodesResponse)(nil), // 50: master_pb.ListClusterNodesResponse - (*LeaseAdminTokenRequest)(nil), // 51: master_pb.LeaseAdminTokenRequest - (*LeaseAdminTokenResponse)(nil), // 52: master_pb.LeaseAdminTokenResponse - (*ReleaseAdminTokenRequest)(nil), // 53: master_pb.ReleaseAdminTokenRequest - (*ReleaseAdminTokenResponse)(nil), // 54: master_pb.ReleaseAdminTokenResponse - (*GetAdminLockStatusRequest)(nil), // 55: master_pb.GetAdminLockStatusRequest - (*GetAdminLockStatusResponse)(nil), // 56: master_pb.GetAdminLockStatusResponse - (*PingRequest)(nil), // 57: master_pb.PingRequest - (*PingResponse)(nil), // 58: master_pb.PingResponse - (*RaftAddServerRequest)(nil), // 59: master_pb.RaftAddServerRequest - (*RaftAddServerResponse)(nil), // 60: master_pb.RaftAddServerResponse - (*RaftRemoveServerRequest)(nil), // 61: master_pb.RaftRemoveServerRequest - (*RaftRemoveServerResponse)(nil), // 62: master_pb.RaftRemoveServerResponse - (*RaftListClusterServersRequest)(nil), // 63: master_pb.RaftListClusterServersRequest - (*RaftListClusterServersResponse)(nil), // 64: master_pb.RaftListClusterServersResponse - (*RaftLeadershipTransferRequest)(nil), // 65: master_pb.RaftLeadershipTransferRequest - (*RaftLeadershipTransferResponse)(nil), // 66: master_pb.RaftLeadershipTransferResponse - (*VolumeGrowResponse)(nil), // 67: master_pb.VolumeGrowResponse - nil, // 68: master_pb.Heartbeat.MaxVolumeCountsEntry - nil, // 69: master_pb.Heartbeat.DiskTotalBytesEntry - nil, // 70: master_pb.Heartbeat.DiskFreeBytesEntry - nil, // 71: master_pb.StorageBackend.PropertiesEntry - (*SuperBlockExtra_ErasureCoding)(nil), // 72: master_pb.SuperBlockExtra.ErasureCoding - (*LookupVolumeResponse_VolumeIdLocation)(nil), // 73: master_pb.LookupVolumeResponse.VolumeIdLocation - nil, // 74: master_pb.DiskInfo.MaxVolumeCountByDiskEntry - nil, // 75: master_pb.DataNodeInfo.DiskInfosEntry - nil, // 76: master_pb.RackInfo.DiskInfosEntry - nil, // 77: master_pb.DataCenterInfo.DiskInfosEntry - nil, // 78: master_pb.TopologyInfo.DiskInfosEntry - (*LookupEcVolumeResponse_EcShardIdLocation)(nil), // 79: master_pb.LookupEcVolumeResponse.EcShardIdLocation - (*ListClusterNodesResponse_ClusterNode)(nil), // 80: master_pb.ListClusterNodesResponse.ClusterNode - (*RaftListClusterServersResponse_ClusterServers)(nil), // 81: master_pb.RaftListClusterServersResponse.ClusterServers - (*volume_server_pb.VolumeServerState)(nil), // 82: volume_server_pb.VolumeServerState + (*VolumeListStreamResponse)(nil), // 37: master_pb.VolumeListStreamResponse + (*LookupEcVolumeRequest)(nil), // 38: master_pb.LookupEcVolumeRequest + (*LookupEcVolumeResponse)(nil), // 39: master_pb.LookupEcVolumeResponse + (*VacuumVolumeRequest)(nil), // 40: master_pb.VacuumVolumeRequest + (*VacuumVolumeResponse)(nil), // 41: master_pb.VacuumVolumeResponse + (*DisableVacuumRequest)(nil), // 42: master_pb.DisableVacuumRequest + (*DisableVacuumResponse)(nil), // 43: master_pb.DisableVacuumResponse + (*EnableVacuumRequest)(nil), // 44: master_pb.EnableVacuumRequest + (*EnableVacuumResponse)(nil), // 45: master_pb.EnableVacuumResponse + (*VolumeMarkReadonlyRequest)(nil), // 46: master_pb.VolumeMarkReadonlyRequest + (*VolumeMarkReadonlyResponse)(nil), // 47: master_pb.VolumeMarkReadonlyResponse + (*GetMasterConfigurationRequest)(nil), // 48: master_pb.GetMasterConfigurationRequest + (*GetMasterConfigurationResponse)(nil), // 49: master_pb.GetMasterConfigurationResponse + (*ListClusterNodesRequest)(nil), // 50: master_pb.ListClusterNodesRequest + (*ListClusterNodesResponse)(nil), // 51: master_pb.ListClusterNodesResponse + (*LeaseAdminTokenRequest)(nil), // 52: master_pb.LeaseAdminTokenRequest + (*LeaseAdminTokenResponse)(nil), // 53: master_pb.LeaseAdminTokenResponse + (*ReleaseAdminTokenRequest)(nil), // 54: master_pb.ReleaseAdminTokenRequest + (*ReleaseAdminTokenResponse)(nil), // 55: master_pb.ReleaseAdminTokenResponse + (*GetAdminLockStatusRequest)(nil), // 56: master_pb.GetAdminLockStatusRequest + (*GetAdminLockStatusResponse)(nil), // 57: master_pb.GetAdminLockStatusResponse + (*PingRequest)(nil), // 58: master_pb.PingRequest + (*PingResponse)(nil), // 59: master_pb.PingResponse + (*RaftAddServerRequest)(nil), // 60: master_pb.RaftAddServerRequest + (*RaftAddServerResponse)(nil), // 61: master_pb.RaftAddServerResponse + (*RaftRemoveServerRequest)(nil), // 62: master_pb.RaftRemoveServerRequest + (*RaftRemoveServerResponse)(nil), // 63: master_pb.RaftRemoveServerResponse + (*RaftListClusterServersRequest)(nil), // 64: master_pb.RaftListClusterServersRequest + (*RaftListClusterServersResponse)(nil), // 65: master_pb.RaftListClusterServersResponse + (*RaftLeadershipTransferRequest)(nil), // 66: master_pb.RaftLeadershipTransferRequest + (*RaftLeadershipTransferResponse)(nil), // 67: master_pb.RaftLeadershipTransferResponse + (*VolumeGrowResponse)(nil), // 68: master_pb.VolumeGrowResponse + nil, // 69: master_pb.Heartbeat.MaxVolumeCountsEntry + nil, // 70: master_pb.Heartbeat.DiskTotalBytesEntry + nil, // 71: master_pb.Heartbeat.DiskFreeBytesEntry + nil, // 72: master_pb.StorageBackend.PropertiesEntry + (*SuperBlockExtra_ErasureCoding)(nil), // 73: master_pb.SuperBlockExtra.ErasureCoding + (*LookupVolumeResponse_VolumeIdLocation)(nil), // 74: master_pb.LookupVolumeResponse.VolumeIdLocation + nil, // 75: master_pb.DiskInfo.MaxVolumeCountByDiskEntry + nil, // 76: master_pb.DataNodeInfo.DiskInfosEntry + nil, // 77: master_pb.RackInfo.DiskInfosEntry + nil, // 78: master_pb.DataCenterInfo.DiskInfosEntry + nil, // 79: master_pb.TopologyInfo.DiskInfosEntry + (*LookupEcVolumeResponse_EcShardIdLocation)(nil), // 80: master_pb.LookupEcVolumeResponse.EcShardIdLocation + (*ListClusterNodesResponse_ClusterNode)(nil), // 81: master_pb.ListClusterNodesResponse.ClusterNode + (*RaftListClusterServersResponse_ClusterServers)(nil), // 82: master_pb.RaftListClusterServersResponse.ClusterServers + (*volume_server_pb.VolumeServerState)(nil), // 83: volume_server_pb.VolumeServerState } var file_master_proto_depIdxs = []int32{ 3, // 0: master_pb.Heartbeat.volumes:type_name -> master_pb.VolumeInformationMessage @@ -5380,101 +5491,106 @@ var file_master_proto_depIdxs = []int32{ 5, // 3: master_pb.Heartbeat.ec_shards:type_name -> master_pb.VolumeEcShardInformationMessage 5, // 4: master_pb.Heartbeat.new_ec_shards:type_name -> master_pb.VolumeEcShardInformationMessage 5, // 5: master_pb.Heartbeat.deleted_ec_shards:type_name -> master_pb.VolumeEcShardInformationMessage - 68, // 6: master_pb.Heartbeat.max_volume_counts:type_name -> master_pb.Heartbeat.MaxVolumeCountsEntry - 82, // 7: master_pb.Heartbeat.state:type_name -> volume_server_pb.VolumeServerState + 69, // 6: master_pb.Heartbeat.max_volume_counts:type_name -> master_pb.Heartbeat.MaxVolumeCountsEntry + 83, // 7: master_pb.Heartbeat.state:type_name -> volume_server_pb.VolumeServerState 0, // 8: master_pb.Heartbeat.disk_tags:type_name -> master_pb.DiskTag - 69, // 9: master_pb.Heartbeat.disk_total_bytes:type_name -> master_pb.Heartbeat.DiskTotalBytesEntry - 70, // 10: master_pb.Heartbeat.disk_free_bytes:type_name -> master_pb.Heartbeat.DiskFreeBytesEntry + 70, // 9: master_pb.Heartbeat.disk_total_bytes:type_name -> master_pb.Heartbeat.DiskTotalBytesEntry + 71, // 10: master_pb.Heartbeat.disk_free_bytes:type_name -> master_pb.Heartbeat.DiskFreeBytesEntry 3, // 11: master_pb.Heartbeat.changed_volumes:type_name -> master_pb.VolumeInformationMessage 6, // 12: master_pb.HeartbeatResponse.storage_backends:type_name -> master_pb.StorageBackend - 71, // 13: master_pb.StorageBackend.properties:type_name -> master_pb.StorageBackend.PropertiesEntry - 72, // 14: master_pb.SuperBlockExtra.erasure_coding:type_name -> master_pb.SuperBlockExtra.ErasureCoding + 72, // 13: master_pb.StorageBackend.properties:type_name -> master_pb.StorageBackend.PropertiesEntry + 73, // 14: master_pb.SuperBlockExtra.erasure_coding:type_name -> master_pb.SuperBlockExtra.ErasureCoding 10, // 15: master_pb.KeepConnectedResponse.volume_location:type_name -> master_pb.VolumeLocation 11, // 16: master_pb.KeepConnectedResponse.cluster_node_update:type_name -> master_pb.ClusterNodeUpdate 13, // 17: master_pb.KeepConnectedResponse.lock_ring_update:type_name -> master_pb.LockRingUpdate - 73, // 18: master_pb.LookupVolumeResponse.volume_id_locations:type_name -> master_pb.LookupVolumeResponse.VolumeIdLocation + 74, // 18: master_pb.LookupVolumeResponse.volume_id_locations:type_name -> master_pb.LookupVolumeResponse.VolumeIdLocation 16, // 19: master_pb.AssignResponse.replicas:type_name -> master_pb.Location 16, // 20: master_pb.AssignResponse.location:type_name -> master_pb.Location 22, // 21: master_pb.CollectionListResponse.collections:type_name -> master_pb.Collection 27, // 22: master_pb.CollectionStatisticsResponse.collections:type_name -> master_pb.CollectionStatistics 3, // 23: master_pb.DiskInfo.volume_infos:type_name -> master_pb.VolumeInformationMessage 5, // 24: master_pb.DiskInfo.ec_shard_infos:type_name -> master_pb.VolumeEcShardInformationMessage - 74, // 25: master_pb.DiskInfo.max_volume_count_by_disk:type_name -> master_pb.DiskInfo.MaxVolumeCountByDiskEntry - 75, // 26: master_pb.DataNodeInfo.diskInfos:type_name -> master_pb.DataNodeInfo.DiskInfosEntry + 75, // 25: master_pb.DiskInfo.max_volume_count_by_disk:type_name -> master_pb.DiskInfo.MaxVolumeCountByDiskEntry + 76, // 26: master_pb.DataNodeInfo.diskInfos:type_name -> master_pb.DataNodeInfo.DiskInfosEntry 31, // 27: master_pb.RackInfo.data_node_infos:type_name -> master_pb.DataNodeInfo - 76, // 28: master_pb.RackInfo.diskInfos:type_name -> master_pb.RackInfo.DiskInfosEntry + 77, // 28: master_pb.RackInfo.diskInfos:type_name -> master_pb.RackInfo.DiskInfosEntry 32, // 29: master_pb.DataCenterInfo.rack_infos:type_name -> master_pb.RackInfo - 77, // 30: master_pb.DataCenterInfo.diskInfos:type_name -> master_pb.DataCenterInfo.DiskInfosEntry + 78, // 30: master_pb.DataCenterInfo.diskInfos:type_name -> master_pb.DataCenterInfo.DiskInfosEntry 33, // 31: master_pb.TopologyInfo.data_center_infos:type_name -> master_pb.DataCenterInfo - 78, // 32: master_pb.TopologyInfo.diskInfos:type_name -> master_pb.TopologyInfo.DiskInfosEntry + 79, // 32: master_pb.TopologyInfo.diskInfos:type_name -> master_pb.TopologyInfo.DiskInfosEntry 34, // 33: master_pb.VolumeListResponse.topology_info:type_name -> master_pb.TopologyInfo - 79, // 34: master_pb.LookupEcVolumeResponse.shard_id_locations:type_name -> master_pb.LookupEcVolumeResponse.EcShardIdLocation - 6, // 35: master_pb.GetMasterConfigurationResponse.storage_backends:type_name -> master_pb.StorageBackend - 80, // 36: master_pb.ListClusterNodesResponse.cluster_nodes:type_name -> master_pb.ListClusterNodesResponse.ClusterNode - 81, // 37: master_pb.RaftListClusterServersResponse.cluster_servers:type_name -> master_pb.RaftListClusterServersResponse.ClusterServers - 16, // 38: master_pb.LookupVolumeResponse.VolumeIdLocation.locations:type_name -> master_pb.Location - 30, // 39: master_pb.DataNodeInfo.DiskInfosEntry.value:type_name -> master_pb.DiskInfo - 30, // 40: master_pb.RackInfo.DiskInfosEntry.value:type_name -> master_pb.DiskInfo - 30, // 41: master_pb.DataCenterInfo.DiskInfosEntry.value:type_name -> master_pb.DiskInfo - 30, // 42: master_pb.TopologyInfo.DiskInfosEntry.value:type_name -> master_pb.DiskInfo - 16, // 43: master_pb.LookupEcVolumeResponse.EcShardIdLocation.locations:type_name -> master_pb.Location - 1, // 44: master_pb.Seaweed.SendHeartbeat:input_type -> master_pb.Heartbeat - 9, // 45: master_pb.Seaweed.KeepConnected:input_type -> master_pb.KeepConnectedRequest - 14, // 46: master_pb.Seaweed.LookupVolume:input_type -> master_pb.LookupVolumeRequest - 17, // 47: master_pb.Seaweed.Assign:input_type -> master_pb.AssignRequest - 17, // 48: master_pb.Seaweed.StreamAssign:input_type -> master_pb.AssignRequest - 20, // 49: master_pb.Seaweed.Statistics:input_type -> master_pb.StatisticsRequest - 23, // 50: master_pb.Seaweed.CollectionList:input_type -> master_pb.CollectionListRequest - 28, // 51: master_pb.Seaweed.CollectionDelete:input_type -> master_pb.CollectionDeleteRequest - 35, // 52: master_pb.Seaweed.VolumeList:input_type -> master_pb.VolumeListRequest - 37, // 53: master_pb.Seaweed.LookupEcVolume:input_type -> master_pb.LookupEcVolumeRequest - 39, // 54: master_pb.Seaweed.VacuumVolume:input_type -> master_pb.VacuumVolumeRequest - 41, // 55: master_pb.Seaweed.DisableVacuum:input_type -> master_pb.DisableVacuumRequest - 43, // 56: master_pb.Seaweed.EnableVacuum:input_type -> master_pb.EnableVacuumRequest - 45, // 57: master_pb.Seaweed.VolumeMarkReadonly:input_type -> master_pb.VolumeMarkReadonlyRequest - 47, // 58: master_pb.Seaweed.GetMasterConfiguration:input_type -> master_pb.GetMasterConfigurationRequest - 49, // 59: master_pb.Seaweed.ListClusterNodes:input_type -> master_pb.ListClusterNodesRequest - 51, // 60: master_pb.Seaweed.LeaseAdminToken:input_type -> master_pb.LeaseAdminTokenRequest - 53, // 61: master_pb.Seaweed.ReleaseAdminToken:input_type -> master_pb.ReleaseAdminTokenRequest - 55, // 62: master_pb.Seaweed.GetAdminLockStatus:input_type -> master_pb.GetAdminLockStatusRequest - 57, // 63: master_pb.Seaweed.Ping:input_type -> master_pb.PingRequest - 63, // 64: master_pb.Seaweed.RaftListClusterServers:input_type -> master_pb.RaftListClusterServersRequest - 59, // 65: master_pb.Seaweed.RaftAddServer:input_type -> master_pb.RaftAddServerRequest - 61, // 66: master_pb.Seaweed.RaftRemoveServer:input_type -> master_pb.RaftRemoveServerRequest - 65, // 67: master_pb.Seaweed.RaftLeadershipTransfer:input_type -> master_pb.RaftLeadershipTransferRequest - 18, // 68: master_pb.Seaweed.VolumeGrow:input_type -> master_pb.VolumeGrowRequest - 25, // 69: master_pb.Seaweed.CollectionStatistics:input_type -> master_pb.CollectionStatisticsRequest - 2, // 70: master_pb.Seaweed.SendHeartbeat:output_type -> master_pb.HeartbeatResponse - 12, // 71: master_pb.Seaweed.KeepConnected:output_type -> master_pb.KeepConnectedResponse - 15, // 72: master_pb.Seaweed.LookupVolume:output_type -> master_pb.LookupVolumeResponse - 19, // 73: master_pb.Seaweed.Assign:output_type -> master_pb.AssignResponse - 19, // 74: master_pb.Seaweed.StreamAssign:output_type -> master_pb.AssignResponse - 21, // 75: master_pb.Seaweed.Statistics:output_type -> master_pb.StatisticsResponse - 24, // 76: master_pb.Seaweed.CollectionList:output_type -> master_pb.CollectionListResponse - 29, // 77: master_pb.Seaweed.CollectionDelete:output_type -> master_pb.CollectionDeleteResponse - 36, // 78: master_pb.Seaweed.VolumeList:output_type -> master_pb.VolumeListResponse - 38, // 79: master_pb.Seaweed.LookupEcVolume:output_type -> master_pb.LookupEcVolumeResponse - 40, // 80: master_pb.Seaweed.VacuumVolume:output_type -> master_pb.VacuumVolumeResponse - 42, // 81: master_pb.Seaweed.DisableVacuum:output_type -> master_pb.DisableVacuumResponse - 44, // 82: master_pb.Seaweed.EnableVacuum:output_type -> master_pb.EnableVacuumResponse - 46, // 83: master_pb.Seaweed.VolumeMarkReadonly:output_type -> master_pb.VolumeMarkReadonlyResponse - 48, // 84: master_pb.Seaweed.GetMasterConfiguration:output_type -> master_pb.GetMasterConfigurationResponse - 50, // 85: master_pb.Seaweed.ListClusterNodes:output_type -> master_pb.ListClusterNodesResponse - 52, // 86: master_pb.Seaweed.LeaseAdminToken:output_type -> master_pb.LeaseAdminTokenResponse - 54, // 87: master_pb.Seaweed.ReleaseAdminToken:output_type -> master_pb.ReleaseAdminTokenResponse - 56, // 88: master_pb.Seaweed.GetAdminLockStatus:output_type -> master_pb.GetAdminLockStatusResponse - 58, // 89: master_pb.Seaweed.Ping:output_type -> master_pb.PingResponse - 64, // 90: master_pb.Seaweed.RaftListClusterServers:output_type -> master_pb.RaftListClusterServersResponse - 60, // 91: master_pb.Seaweed.RaftAddServer:output_type -> master_pb.RaftAddServerResponse - 62, // 92: master_pb.Seaweed.RaftRemoveServer:output_type -> master_pb.RaftRemoveServerResponse - 66, // 93: master_pb.Seaweed.RaftLeadershipTransfer:output_type -> master_pb.RaftLeadershipTransferResponse - 67, // 94: master_pb.Seaweed.VolumeGrow:output_type -> master_pb.VolumeGrowResponse - 26, // 95: master_pb.Seaweed.CollectionStatistics:output_type -> master_pb.CollectionStatisticsResponse - 70, // [70:96] is the sub-list for method output_type - 44, // [44:70] is the sub-list for method input_type - 44, // [44:44] is the sub-list for extension type_name - 44, // [44:44] is the sub-list for extension extendee - 0, // [0:44] is the sub-list for field type_name + 36, // 34: master_pb.VolumeListStreamResponse.header:type_name -> master_pb.VolumeListResponse + 3, // 35: master_pb.VolumeListStreamResponse.volume_infos:type_name -> master_pb.VolumeInformationMessage + 5, // 36: master_pb.VolumeListStreamResponse.ec_shard_infos:type_name -> master_pb.VolumeEcShardInformationMessage + 80, // 37: master_pb.LookupEcVolumeResponse.shard_id_locations:type_name -> master_pb.LookupEcVolumeResponse.EcShardIdLocation + 6, // 38: master_pb.GetMasterConfigurationResponse.storage_backends:type_name -> master_pb.StorageBackend + 81, // 39: master_pb.ListClusterNodesResponse.cluster_nodes:type_name -> master_pb.ListClusterNodesResponse.ClusterNode + 82, // 40: master_pb.RaftListClusterServersResponse.cluster_servers:type_name -> master_pb.RaftListClusterServersResponse.ClusterServers + 16, // 41: master_pb.LookupVolumeResponse.VolumeIdLocation.locations:type_name -> master_pb.Location + 30, // 42: master_pb.DataNodeInfo.DiskInfosEntry.value:type_name -> master_pb.DiskInfo + 30, // 43: master_pb.RackInfo.DiskInfosEntry.value:type_name -> master_pb.DiskInfo + 30, // 44: master_pb.DataCenterInfo.DiskInfosEntry.value:type_name -> master_pb.DiskInfo + 30, // 45: master_pb.TopologyInfo.DiskInfosEntry.value:type_name -> master_pb.DiskInfo + 16, // 46: master_pb.LookupEcVolumeResponse.EcShardIdLocation.locations:type_name -> master_pb.Location + 1, // 47: master_pb.Seaweed.SendHeartbeat:input_type -> master_pb.Heartbeat + 9, // 48: master_pb.Seaweed.KeepConnected:input_type -> master_pb.KeepConnectedRequest + 14, // 49: master_pb.Seaweed.LookupVolume:input_type -> master_pb.LookupVolumeRequest + 17, // 50: master_pb.Seaweed.Assign:input_type -> master_pb.AssignRequest + 17, // 51: master_pb.Seaweed.StreamAssign:input_type -> master_pb.AssignRequest + 20, // 52: master_pb.Seaweed.Statistics:input_type -> master_pb.StatisticsRequest + 23, // 53: master_pb.Seaweed.CollectionList:input_type -> master_pb.CollectionListRequest + 28, // 54: master_pb.Seaweed.CollectionDelete:input_type -> master_pb.CollectionDeleteRequest + 35, // 55: master_pb.Seaweed.VolumeList:input_type -> master_pb.VolumeListRequest + 35, // 56: master_pb.Seaweed.VolumeListStream:input_type -> master_pb.VolumeListRequest + 38, // 57: master_pb.Seaweed.LookupEcVolume:input_type -> master_pb.LookupEcVolumeRequest + 40, // 58: master_pb.Seaweed.VacuumVolume:input_type -> master_pb.VacuumVolumeRequest + 42, // 59: master_pb.Seaweed.DisableVacuum:input_type -> master_pb.DisableVacuumRequest + 44, // 60: master_pb.Seaweed.EnableVacuum:input_type -> master_pb.EnableVacuumRequest + 46, // 61: master_pb.Seaweed.VolumeMarkReadonly:input_type -> master_pb.VolumeMarkReadonlyRequest + 48, // 62: master_pb.Seaweed.GetMasterConfiguration:input_type -> master_pb.GetMasterConfigurationRequest + 50, // 63: master_pb.Seaweed.ListClusterNodes:input_type -> master_pb.ListClusterNodesRequest + 52, // 64: master_pb.Seaweed.LeaseAdminToken:input_type -> master_pb.LeaseAdminTokenRequest + 54, // 65: master_pb.Seaweed.ReleaseAdminToken:input_type -> master_pb.ReleaseAdminTokenRequest + 56, // 66: master_pb.Seaweed.GetAdminLockStatus:input_type -> master_pb.GetAdminLockStatusRequest + 58, // 67: master_pb.Seaweed.Ping:input_type -> master_pb.PingRequest + 64, // 68: master_pb.Seaweed.RaftListClusterServers:input_type -> master_pb.RaftListClusterServersRequest + 60, // 69: master_pb.Seaweed.RaftAddServer:input_type -> master_pb.RaftAddServerRequest + 62, // 70: master_pb.Seaweed.RaftRemoveServer:input_type -> master_pb.RaftRemoveServerRequest + 66, // 71: master_pb.Seaweed.RaftLeadershipTransfer:input_type -> master_pb.RaftLeadershipTransferRequest + 18, // 72: master_pb.Seaweed.VolumeGrow:input_type -> master_pb.VolumeGrowRequest + 25, // 73: master_pb.Seaweed.CollectionStatistics:input_type -> master_pb.CollectionStatisticsRequest + 2, // 74: master_pb.Seaweed.SendHeartbeat:output_type -> master_pb.HeartbeatResponse + 12, // 75: master_pb.Seaweed.KeepConnected:output_type -> master_pb.KeepConnectedResponse + 15, // 76: master_pb.Seaweed.LookupVolume:output_type -> master_pb.LookupVolumeResponse + 19, // 77: master_pb.Seaweed.Assign:output_type -> master_pb.AssignResponse + 19, // 78: master_pb.Seaweed.StreamAssign:output_type -> master_pb.AssignResponse + 21, // 79: master_pb.Seaweed.Statistics:output_type -> master_pb.StatisticsResponse + 24, // 80: master_pb.Seaweed.CollectionList:output_type -> master_pb.CollectionListResponse + 29, // 81: master_pb.Seaweed.CollectionDelete:output_type -> master_pb.CollectionDeleteResponse + 36, // 82: master_pb.Seaweed.VolumeList:output_type -> master_pb.VolumeListResponse + 37, // 83: master_pb.Seaweed.VolumeListStream:output_type -> master_pb.VolumeListStreamResponse + 39, // 84: master_pb.Seaweed.LookupEcVolume:output_type -> master_pb.LookupEcVolumeResponse + 41, // 85: master_pb.Seaweed.VacuumVolume:output_type -> master_pb.VacuumVolumeResponse + 43, // 86: master_pb.Seaweed.DisableVacuum:output_type -> master_pb.DisableVacuumResponse + 45, // 87: master_pb.Seaweed.EnableVacuum:output_type -> master_pb.EnableVacuumResponse + 47, // 88: master_pb.Seaweed.VolumeMarkReadonly:output_type -> master_pb.VolumeMarkReadonlyResponse + 49, // 89: master_pb.Seaweed.GetMasterConfiguration:output_type -> master_pb.GetMasterConfigurationResponse + 51, // 90: master_pb.Seaweed.ListClusterNodes:output_type -> master_pb.ListClusterNodesResponse + 53, // 91: master_pb.Seaweed.LeaseAdminToken:output_type -> master_pb.LeaseAdminTokenResponse + 55, // 92: master_pb.Seaweed.ReleaseAdminToken:output_type -> master_pb.ReleaseAdminTokenResponse + 57, // 93: master_pb.Seaweed.GetAdminLockStatus:output_type -> master_pb.GetAdminLockStatusResponse + 59, // 94: master_pb.Seaweed.Ping:output_type -> master_pb.PingResponse + 65, // 95: master_pb.Seaweed.RaftListClusterServers:output_type -> master_pb.RaftListClusterServersResponse + 61, // 96: master_pb.Seaweed.RaftAddServer:output_type -> master_pb.RaftAddServerResponse + 63, // 97: master_pb.Seaweed.RaftRemoveServer:output_type -> master_pb.RaftRemoveServerResponse + 67, // 98: master_pb.Seaweed.RaftLeadershipTransfer:output_type -> master_pb.RaftLeadershipTransferResponse + 68, // 99: master_pb.Seaweed.VolumeGrow:output_type -> master_pb.VolumeGrowResponse + 26, // 100: master_pb.Seaweed.CollectionStatistics:output_type -> master_pb.CollectionStatisticsResponse + 74, // [74:101] is the sub-list for method output_type + 47, // [47:74] is the sub-list for method input_type + 47, // [47:47] is the sub-list for extension type_name + 47, // [47:47] is the sub-list for extension extendee + 0, // [0:47] is the sub-list for field type_name } func init() { file_master_proto_init() } @@ -5489,7 +5605,7 @@ func file_master_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_master_proto_rawDesc), len(file_master_proto_rawDesc)), NumEnums: 0, - NumMessages: 82, + NumMessages: 83, NumExtensions: 0, NumServices: 1, }, diff --git a/weed/pb/master_pb/master_grpc.pb.go b/weed/pb/master_pb/master_grpc.pb.go index a5d026778..5a59fcae2 100644 --- a/weed/pb/master_pb/master_grpc.pb.go +++ b/weed/pb/master_pb/master_grpc.pb.go @@ -28,6 +28,7 @@ const ( Seaweed_CollectionList_FullMethodName = "/master_pb.Seaweed/CollectionList" Seaweed_CollectionDelete_FullMethodName = "/master_pb.Seaweed/CollectionDelete" Seaweed_VolumeList_FullMethodName = "/master_pb.Seaweed/VolumeList" + Seaweed_VolumeListStream_FullMethodName = "/master_pb.Seaweed/VolumeListStream" Seaweed_LookupEcVolume_FullMethodName = "/master_pb.Seaweed/LookupEcVolume" Seaweed_VacuumVolume_FullMethodName = "/master_pb.Seaweed/VacuumVolume" Seaweed_DisableVacuum_FullMethodName = "/master_pb.Seaweed/DisableVacuum" @@ -60,6 +61,7 @@ type SeaweedClient interface { CollectionList(ctx context.Context, in *CollectionListRequest, opts ...grpc.CallOption) (*CollectionListResponse, error) CollectionDelete(ctx context.Context, in *CollectionDeleteRequest, opts ...grpc.CallOption) (*CollectionDeleteResponse, error) VolumeList(ctx context.Context, in *VolumeListRequest, opts ...grpc.CallOption) (*VolumeListResponse, error) + VolumeListStream(ctx context.Context, in *VolumeListRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[VolumeListStreamResponse], error) LookupEcVolume(ctx context.Context, in *LookupEcVolumeRequest, opts ...grpc.CallOption) (*LookupEcVolumeResponse, error) VacuumVolume(ctx context.Context, in *VacuumVolumeRequest, opts ...grpc.CallOption) (*VacuumVolumeResponse, error) DisableVacuum(ctx context.Context, in *DisableVacuumRequest, opts ...grpc.CallOption) (*DisableVacuumResponse, error) @@ -186,6 +188,25 @@ func (c *seaweedClient) VolumeList(ctx context.Context, in *VolumeListRequest, o return out, nil } +func (c *seaweedClient) VolumeListStream(ctx context.Context, in *VolumeListRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[VolumeListStreamResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &Seaweed_ServiceDesc.Streams[3], Seaweed_VolumeListStream_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[VolumeListRequest, VolumeListStreamResponse]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type Seaweed_VolumeListStreamClient = grpc.ServerStreamingClient[VolumeListStreamResponse] + func (c *seaweedClient) LookupEcVolume(ctx context.Context, in *LookupEcVolumeRequest, opts ...grpc.CallOption) (*LookupEcVolumeResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(LookupEcVolumeResponse) @@ -369,6 +390,7 @@ type SeaweedServer interface { CollectionList(context.Context, *CollectionListRequest) (*CollectionListResponse, error) CollectionDelete(context.Context, *CollectionDeleteRequest) (*CollectionDeleteResponse, error) VolumeList(context.Context, *VolumeListRequest) (*VolumeListResponse, error) + VolumeListStream(*VolumeListRequest, grpc.ServerStreamingServer[VolumeListStreamResponse]) error LookupEcVolume(context.Context, *LookupEcVolumeRequest) (*LookupEcVolumeResponse, error) VacuumVolume(context.Context, *VacuumVolumeRequest) (*VacuumVolumeResponse, error) DisableVacuum(context.Context, *DisableVacuumRequest) (*DisableVacuumResponse, error) @@ -423,6 +445,9 @@ func (UnimplementedSeaweedServer) CollectionDelete(context.Context, *CollectionD func (UnimplementedSeaweedServer) VolumeList(context.Context, *VolumeListRequest) (*VolumeListResponse, error) { return nil, status.Error(codes.Unimplemented, "method VolumeList not implemented") } +func (UnimplementedSeaweedServer) VolumeListStream(*VolumeListRequest, grpc.ServerStreamingServer[VolumeListStreamResponse]) error { + return status.Error(codes.Unimplemented, "method VolumeListStream not implemented") +} func (UnimplementedSeaweedServer) LookupEcVolume(context.Context, *LookupEcVolumeRequest) (*LookupEcVolumeResponse, error) { return nil, status.Error(codes.Unimplemented, "method LookupEcVolume not implemented") } @@ -624,6 +649,17 @@ func _Seaweed_VolumeList_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } +func _Seaweed_VolumeListStream_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(VolumeListRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(SeaweedServer).VolumeListStream(m, &grpc.GenericServerStream[VolumeListRequest, VolumeListStreamResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type Seaweed_VolumeListStreamServer = grpc.ServerStreamingServer[VolumeListStreamResponse] + func _Seaweed_LookupEcVolume_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(LookupEcVolumeRequest) if err := dec(in); err != nil { @@ -1049,6 +1085,11 @@ var Seaweed_ServiceDesc = grpc.ServiceDesc{ ServerStreams: true, ClientStreams: true, }, + { + StreamName: "VolumeListStream", + Handler: _Seaweed_VolumeListStream_Handler, + ServerStreams: true, + }, }, Metadata: "master.proto", } diff --git a/weed/pb/volume_list_stream.go b/weed/pb/volume_list_stream.go new file mode 100644 index 000000000..5985c441a --- /dev/null +++ b/weed/pb/volume_list_stream.go @@ -0,0 +1,212 @@ +package pb + +import ( + "context" + "fmt" + "io" + + "github.com/seaweedfs/seaweedfs/weed/glog" + "github.com/seaweedfs/seaweedfs/weed/pb/master_pb" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// ReceiveVolumeList reads a streamed volume listing: the topology first, then +// its volumes in batches as they arrive. A caller that works a volume at a +// time never holds the cluster; one that needs it whole can use +// CollectVolumeList. +// +// onTopology is given a listing whose disks name themselves but list nothing, +// because the volumes come through onVolumes instead. That holds however the +// master answered: one too old for the stream is asked the old way and its +// reply cut into the same batches, so a caller cannot tell the difference and +// must not read volumes off the topology either way. +func ReceiveVolumeList(ctx context.Context, client master_pb.SeaweedClient, request *master_pb.VolumeListRequest, + onTopology func(*master_pb.VolumeListResponse) error, + onVolumes func(*master_pb.VolumeListStreamResponse) error) error { + + stream, err := client.VolumeListStream(ctx, request) + if err == nil { + started, streamErr := receiveVolumeListStream(stream, onTopology, onVolumes) + // Only a stream that said nothing can be asked again the old way. Past + // its first message the master plainly does have the method, and the + // error may even be the caller's own, so starting over would hand back + // what has already been handed over. + if started || status.Code(streamErr) != codes.Unimplemented { + return streamErr + } + } else if status.Code(err) != codes.Unimplemented { + return err + } + + response, err := client.VolumeList(ctx, request) + if err != nil { + return err + } + return replayVolumeList(response, onTopology, onVolumes) +} + +// receiveVolumeListStream reports whether the stream said anything at all, +// which decides whether it can be started over as an unstreamed listing. +func receiveVolumeListStream(stream master_pb.Seaweed_VolumeListStreamClient, + onTopology func(*master_pb.VolumeListResponse) error, + onVolumes func(*master_pb.VolumeListStreamResponse) error) (started bool, err error) { + + told := false + for { + batch, err := stream.Recv() + if err == io.EOF { + if !told { + return started, fmt.Errorf("volume list stream ended before its topology") + } + return started, nil + } + if err != nil { + return started, err + } + started = true + if batch.Header != nil { + if told { + return started, fmt.Errorf("volume list stream sent its topology twice") + } + told = true + if onTopology != nil { + if err := onTopology(batch.Header); err != nil { + return started, err + } + } + continue + } + if !told { + return started, fmt.Errorf("volume list stream sent volumes before its topology") + } + if onVolumes != nil { + if err := onVolumes(batch); err != nil { + return started, err + } + } + } +} + +// replayVolumeList cuts an unstreamed reply into the batches a caller expects. +// Each disk's volumes are moved out of the topology rather than shared with it, +// so the topology handed over lists nothing, exactly as a streamed one does. +func replayVolumeList(response *master_pb.VolumeListResponse, + onTopology func(*master_pb.VolumeListResponse) error, + onVolumes func(*master_pb.VolumeListStreamResponse) error) error { + + type batch struct { + key [4]string + volume []*master_pb.VolumeInformationMessage + ec []*master_pb.VolumeEcShardInformationMessage + } + var batches []batch + + if response.TopologyInfo != nil { + for _, dc := range response.TopologyInfo.DataCenterInfos { + for _, rack := range dc.RackInfos { + for _, node := range rack.DataNodeInfos { + for diskType, disk := range node.DiskInfos { + if len(disk.VolumeInfos) == 0 && len(disk.EcShardInfos) == 0 { + continue + } + batches = append(batches, batch{ + key: [4]string{dc.Id, rack.Id, node.Id, diskType}, + volume: disk.VolumeInfos, + ec: disk.EcShardInfos, + }) + disk.VolumeInfos, disk.EcShardInfos = nil, nil + } + } + } + } + } + + if onTopology != nil { + if err := onTopology(response); err != nil { + return err + } + } + if onVolumes == nil { + return nil + } + for _, b := range batches { + err := onVolumes(&master_pb.VolumeListStreamResponse{ + DataCenter: b.key[0], + Rack: b.key[1], + DataNode: b.key[2], + DiskType: b.key[3], + VolumeInfos: b.volume, + EcShardInfos: b.ec, + }) + if err != nil { + return err + } + } + return nil +} + +// CollectVolumeList streams a listing and puts it back together, for callers +// that need the whole topology. The master still never holds it all, which is +// the point; this only moves that cost to the caller. +func CollectVolumeList(ctx context.Context, client master_pb.SeaweedClient, request *master_pb.VolumeListRequest) (*master_pb.VolumeListResponse, error) { + var response *master_pb.VolumeListResponse + var skipped int + disks := make(map[[4]string]*master_pb.DiskInfo) + + err := ReceiveVolumeList(ctx, client, request, + func(topology *master_pb.VolumeListResponse) error { + response = topology + return nil + }, + func(batch *master_pb.VolumeListStreamResponse) error { + key := [4]string{batch.DataCenter, batch.Rack, batch.DataNode, batch.DiskType} + disk, known := disks[key] + if !known { + disk = findDisk(response, key) + disks[key] = disk + } + if disk == nil { + // A disk registered after the topology went out. It is not in + // the listing being rebuilt and has nowhere to go, so leave it + // to the next one rather than failing this one -- an unstreamed + // listing would not have shown it either, having read each + // node's disks once. + skipped++ + return nil + } + disk.VolumeInfos = append(disk.VolumeInfos, batch.VolumeInfos...) + disk.EcShardInfos = append(disk.EcShardInfos, batch.EcShardInfos...) + return nil + }) + if err != nil { + return nil, err + } + if skipped > 0 { + glog.V(1).Infof("volume list: %d batches were for disks added after the topology was sent", skipped) + } + return response, nil +} + +func findDisk(response *master_pb.VolumeListResponse, key [4]string) *master_pb.DiskInfo { + if response == nil || response.TopologyInfo == nil { + return nil + } + for _, dc := range response.TopologyInfo.DataCenterInfos { + if dc.Id != key[0] { + continue + } + for _, rack := range dc.RackInfos { + if rack.Id != key[1] { + continue + } + for _, node := range rack.DataNodeInfos { + if node.Id != key[2] { + continue + } + return node.DiskInfos[key[3]] + } + } + } + return nil +} diff --git a/weed/pb/volume_list_stream_test.go b/weed/pb/volume_list_stream_test.go new file mode 100644 index 000000000..9a765b664 --- /dev/null +++ b/weed/pb/volume_list_stream_test.go @@ -0,0 +1,306 @@ +package pb + +import ( + "context" + "fmt" + "net" + "testing" + + "github.com/seaweedfs/seaweedfs/weed/pb/master_pb" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + "google.golang.org/protobuf/proto" +) + +// fakeMaster answers a listing either way, so the same assertions can be made +// of a master that streams and one too old to. +type fakeMaster struct { + master_pb.UnimplementedSeaweedServer + response *master_pb.VolumeListResponse + streams bool + batch int + // extraBatch is sent after the rest, standing for a disk that registered + // once the topology had gone out. + extraBatch *master_pb.VolumeListStreamResponse + // failAfterHeader stands for a master that plainly has the method but + // gives up mid-stream, reporting the one code that means "ask the old way". + failAfterHeader bool +} + +func (m *fakeMaster) VolumeList(ctx context.Context, req *master_pb.VolumeListRequest) (*master_pb.VolumeListResponse, error) { + return cloneListing(m.response), nil +} + +func (m *fakeMaster) VolumeListStream(req *master_pb.VolumeListRequest, stream master_pb.Seaweed_VolumeListStreamServer) error { + if !m.streams { + return status_Unimplemented() + } + full := cloneListing(m.response) + header := &master_pb.VolumeListResponse{ + TopologyInfo: &master_pb.TopologyInfo{Id: full.TopologyInfo.Id}, + VolumeSizeLimitMb: full.VolumeSizeLimitMb, + } + // The header names every disk but lists nothing on it. + for _, dc := range full.TopologyInfo.DataCenterInfos { + headerDc := &master_pb.DataCenterInfo{Id: dc.Id} + for _, rack := range dc.RackInfos { + headerRack := &master_pb.RackInfo{Id: rack.Id} + for _, node := range rack.DataNodeInfos { + headerNode := &master_pb.DataNodeInfo{Id: node.Id, DiskInfos: map[string]*master_pb.DiskInfo{}} + for diskType, disk := range node.DiskInfos { + headerNode.DiskInfos[diskType] = &master_pb.DiskInfo{Type: diskType, DiskId: disk.DiskId} + } + headerRack.DataNodeInfos = append(headerRack.DataNodeInfos, headerNode) + } + headerDc.RackInfos = append(headerDc.RackInfos, headerRack) + } + header.TopologyInfo.DataCenterInfos = append(header.TopologyInfo.DataCenterInfos, headerDc) + } + if err := stream.Send(&master_pb.VolumeListStreamResponse{Header: header}); err != nil { + return err + } + if m.failAfterHeader { + return status_Unimplemented() + } + + for _, dc := range full.TopologyInfo.DataCenterInfos { + for _, rack := range dc.RackInfos { + for _, node := range rack.DataNodeInfos { + for diskType, disk := range node.DiskInfos { + for start := 0; start < len(disk.VolumeInfos); start += m.batch { + end := min(start+m.batch, len(disk.VolumeInfos)) + err := stream.Send(&master_pb.VolumeListStreamResponse{ + DataCenter: dc.Id, Rack: rack.Id, DataNode: node.Id, DiskType: diskType, + VolumeInfos: disk.VolumeInfos[start:end], + }) + if err != nil { + return err + } + } + if len(disk.EcShardInfos) > 0 { + err := stream.Send(&master_pb.VolumeListStreamResponse{ + DataCenter: dc.Id, Rack: rack.Id, DataNode: node.Id, DiskType: diskType, + EcShardInfos: disk.EcShardInfos, + }) + if err != nil { + return err + } + } + } + } + } + } + if m.extraBatch != nil { + if err := stream.Send(m.extraBatch); err != nil { + return err + } + } + return nil +} + +func testListing(volumes int) *master_pb.VolumeListResponse { + disk := &master_pb.DiskInfo{Type: "", DiskId: 2} + for i := 1; i <= volumes; i++ { + disk.VolumeInfos = append(disk.VolumeInfos, &master_pb.VolumeInformationMessage{ + Id: uint32(i), Size: uint64(i) * 100, Collection: "c", + }) + } + disk.EcShardInfos = append(disk.EcShardInfos, &master_pb.VolumeEcShardInformationMessage{ + Id: 900, Collection: "c", EcIndexBits: 0x3fff, + }) + return &master_pb.VolumeListResponse{ + VolumeSizeLimitMb: 30000, + TopologyInfo: &master_pb.TopologyInfo{ + Id: "topo", + DataCenterInfos: []*master_pb.DataCenterInfo{{ + Id: "dc1", + RackInfos: []*master_pb.RackInfo{{ + Id: "rack1", + DataNodeInfos: []*master_pb.DataNodeInfo{{ + Id: "10.0.0.1:8080", + DiskInfos: map[string]*master_pb.DiskInfo{"": disk}, + }}, + }}, + }}, + }, + } +} + +func dial(t *testing.T, master *fakeMaster) master_pb.SeaweedClient { + t.Helper() + listener := bufconn.Listen(1 << 20) + server := grpc.NewServer() + master_pb.RegisterSeaweedServer(server, master) + go server.Serve(listener) + t.Cleanup(server.Stop) + + conn, err := grpc.NewClient("passthrough://bufnet", + grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { return listener.DialContext(ctx) }), + grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { conn.Close() }) + return master_pb.NewSeaweedClient(conn) +} + +// Whichever way the master answers, a caller sees the same volumes, and sees +// none of them on the topology it is handed. +func TestReceiveVolumeListIsTheSameEitherWay(t *testing.T) { + const volumes = 250 + for _, streams := range []bool{true, false} { + t.Run(fmt.Sprintf("streaming=%v", streams), func(t *testing.T) { + client := dial(t, &fakeMaster{response: testListing(volumes), streams: streams, batch: 32}) + + var got []uint32 + var ec []uint32 + var topology *master_pb.VolumeListResponse + err := ReceiveVolumeList(context.Background(), client, &master_pb.VolumeListRequest{}, + func(header *master_pb.VolumeListResponse) error { + topology = header + return nil + }, + func(batch *master_pb.VolumeListStreamResponse) error { + for _, v := range batch.VolumeInfos { + got = append(got, v.Id) + } + for _, s := range batch.EcShardInfos { + ec = append(ec, s.Id) + } + return nil + }) + if err != nil { + t.Fatal(err) + } + if len(got) != volumes { + t.Errorf("received %d volumes, want %d", len(got), volumes) + } + if len(ec) != 1 { + t.Errorf("received %d ec shards, want 1", len(ec)) + } + if topology == nil { + t.Fatal("never told the topology") + } + if topology.VolumeSizeLimitMb != 30000 { + t.Errorf("volume size limit %d, want 30000", topology.VolumeSizeLimitMb) + } + disk := topology.TopologyInfo.DataCenterInfos[0].RackInfos[0].DataNodeInfos[0].DiskInfos[""] + if len(disk.VolumeInfos) != 0 || len(disk.EcShardInfos) != 0 { + t.Errorf("the topology handed over listed %d volumes and %d ec shards, want none", + len(disk.VolumeInfos), len(disk.EcShardInfos)) + } + }) + } +} + +// Reassembly must put back exactly what an unstreamed listing holds -- in +// particular it must not double the volumes when the master did not stream. +func TestCollectVolumeListRebuildsTheListing(t *testing.T) { + const volumes = 250 + for _, streams := range []bool{true, false} { + t.Run(fmt.Sprintf("streaming=%v", streams), func(t *testing.T) { + client := dial(t, &fakeMaster{response: testListing(volumes), streams: streams, batch: 32}) + + response, err := CollectVolumeList(context.Background(), client, &master_pb.VolumeListRequest{}) + if err != nil { + t.Fatal(err) + } + disk := response.TopologyInfo.DataCenterInfos[0].RackInfos[0].DataNodeInfos[0].DiskInfos[""] + if len(disk.VolumeInfos) != volumes { + t.Fatalf("rebuilt %d volumes, want %d", len(disk.VolumeInfos), volumes) + } + if len(disk.EcShardInfos) != 1 { + t.Fatalf("rebuilt %d ec shards, want 1", len(disk.EcShardInfos)) + } + if disk.DiskId != 2 { + t.Errorf("rebuilt disk id %d, want 2", disk.DiskId) + } + seen := make(map[uint32]int, volumes) + for _, v := range disk.VolumeInfos { + seen[v.Id]++ + } + for id, n := range seen { + if n != 1 { + t.Fatalf("volume %d rebuilt %d times", id, n) + } + } + }) + } +} + +func status_Unimplemented() error { + return status.Error(codes.Unimplemented, "this master does not stream volume listings") +} + +func cloneListing(r *master_pb.VolumeListResponse) *master_pb.VolumeListResponse { + return proto.Clone(r).(*master_pb.VolumeListResponse) +} + +// A heartbeat can register a disk between the topology going out and the +// batches following it. Those volumes have nowhere to go in the listing being +// rebuilt, but they must not fail it: the scan that reads it runs every 30 +// minutes and would lose the whole cluster over one new disk. +func TestCollectVolumeListSurvivesADiskAddedMidStream(t *testing.T) { + const volumes = 100 + master := &fakeMaster{response: testListing(volumes), streams: true, batch: 32} + master.extraBatch = &master_pb.VolumeListStreamResponse{ + DataCenter: "dc1", Rack: "rack1", DataNode: "10.0.0.1:8080", DiskType: "ssd", + VolumeInfos: []*master_pb.VolumeInformationMessage{{Id: 5000, Collection: "c"}}, + } + client := dial(t, master) + + response, err := CollectVolumeList(context.Background(), client, &master_pb.VolumeListRequest{}) + if err != nil { + t.Fatalf("a disk arriving mid-stream failed the listing: %v", err) + } + node := response.TopologyInfo.DataCenterInfos[0].RackInfos[0].DataNodeInfos[0] + if len(node.DiskInfos[""].VolumeInfos) != volumes { + t.Errorf("rebuilt %d volumes, want %d", len(node.DiskInfos[""].VolumeInfos), volumes) + } + if _, appeared := node.DiskInfos["ssd"]; appeared { + t.Error("the listing grew a disk its topology never named") + } +} + +// A stream that has already spoken cannot be started over as an unstreamed +// listing: the caller would be handed the same volumes twice. +func TestReceiveVolumeListDoesNotRestartAStreamThatBegan(t *testing.T) { + client := dial(t, &fakeMaster{response: testListing(100), streams: true, batch: 32, failAfterHeader: true}) + + topologies, batches := 0, 0 + err := ReceiveVolumeList(context.Background(), client, &master_pb.VolumeListRequest{}, + func(*master_pb.VolumeListResponse) error { topologies++; return nil }, + func(*master_pb.VolumeListStreamResponse) error { batches++; return nil }) + if err == nil { + t.Fatal("a stream that failed after its topology was quietly restarted") + } + if topologies != 1 { + t.Errorf("handed the topology %d times, want 1", topologies) + } + if batches != 0 { + t.Errorf("handed %d volume batches, want none", batches) + } +} + +// A caller's own error must reach it, even when it happens to carry the code +// that means an older master. +func TestReceiveVolumeListDoesNotRestartOnACallersError(t *testing.T) { + client := dial(t, &fakeMaster{response: testListing(100), streams: true, batch: 32}) + + batches := 0 + err := ReceiveVolumeList(context.Background(), client, &master_pb.VolumeListRequest{}, + nil, + func(*master_pb.VolumeListStreamResponse) error { + batches++ + return status.Error(codes.Unimplemented, "the caller cannot handle this") + }) + if err == nil { + t.Fatal("the caller's error was swallowed and the listing restarted") + } + if batches != 1 { + t.Errorf("called back %d times, want 1 before giving up", batches) + } +} diff --git a/weed/server/master_grpc_server_volume.go b/weed/server/master_grpc_server_volume.go index 2ec6c407d..0f4d3f6a2 100644 --- a/weed/server/master_grpc_server_volume.go +++ b/weed/server/master_grpc_server_volume.go @@ -287,6 +287,29 @@ func (ms *MasterServer) VolumeList(ctx context.Context, req *master_pb.VolumeLis return resp, nil } +// VolumeListStream answers VolumeList without building the whole reply first. +// The topology goes out on its own, then the volumes in batches, so the master +// holds one batch rather than every volume in the cluster. +func (ms *MasterServer) VolumeListStream(req *master_pb.VolumeListRequest, stream master_pb.Seaweed_VolumeListStreamServer) error { + + if !ms.Topo.IsLeader() { + return raft.NotLeaderError + } + + listed := ms.Topo.ToTopologyInfo(topology.NoVolumes()) + err := stream.Send(&master_pb.VolumeListStreamResponse{ + Header: &master_pb.VolumeListResponse{ + TopologyInfo: listed, + VolumeSizeLimitMb: uint64(ms.option.VolumeSizeLimitMB), + }, + }) + if err != nil { + return err + } + + return ms.Topo.StreamVolumes(listed, topology.NewVolumeFilter(req), 0, stream.Send) +} + func (ms *MasterServer) LookupEcVolume(ctx context.Context, req *master_pb.LookupEcVolumeRequest) (*master_pb.LookupEcVolumeResponse, error) { if !ms.Topo.IsLeader() { diff --git a/weed/topology/volume_filter.go b/weed/topology/volume_filter.go index bec87862b..edc07c9ee 100644 --- a/weed/topology/volume_filter.go +++ b/weed/topology/volume_filter.go @@ -11,6 +11,14 @@ import ( type VolumeFilter struct { Collection *string VolumeId *needle.VolumeId + // nothing selects the topology alone, for a listing whose volumes travel + // in messages of their own. + nothing bool +} + +// NoVolumes selects the topology and no volume in it. +func NoVolumes() VolumeFilter { + return VolumeFilter{nothing: true} } // NewVolumeFilter reads what a VolumeList request asked for, where empty and @@ -35,10 +43,13 @@ func NewVolumeFilter(req *master_pb.VolumeListRequest) VolumeFilter { // SelectsEverything lets a caller size its result for the whole disk up front. func (f VolumeFilter) SelectsEverything() bool { - return f.Collection == nil && f.VolumeId == nil + return !f.nothing && f.Collection == nil && f.VolumeId == nil } func (f VolumeFilter) matches(collection string, id needle.VolumeId) bool { + if f.nothing { + return false + } if f.Collection != nil && *f.Collection != collection { return false } diff --git a/weed/topology/volume_stream.go b/weed/topology/volume_stream.go new file mode 100644 index 000000000..94a753b42 --- /dev/null +++ b/weed/topology/volume_stream.go @@ -0,0 +1,137 @@ +package topology + +import ( + "github.com/seaweedfs/seaweedfs/weed/pb/master_pb" + "github.com/seaweedfs/seaweedfs/weed/storage/needle" +) + +// StreamVolumes hands every volume the filter selects to send, in batches, so +// that neither end holds the whole cluster to move it. Batches are built under +// their disk's lock and handed over outside it, so a slow reader stalls the +// stream rather than the topology. +// +// Only the disks `listed` names are streamed. That listing went out first, and +// the topology is walked again here, so without it a disk registering in +// between would have its volumes sent to a client with nowhere to put them. +// Bounding the walk by what was already announced makes the two agree by +// construction: a disk arriving mid-listing is in neither, and is reported by +// the next one. +// +// The batches still do not share one instant. Neither did a single listing, +// which takes each disk's lock in turn, so a volume that moves while either is +// running can be seen twice or not at all. +func (t *Topology) StreamVolumes(listed *master_pb.TopologyInfo, filter VolumeFilter, batchSize int, send func(*master_pb.VolumeListStreamResponse) error) error { + if batchSize <= 0 { + batchSize = defaultVolumeStreamBatch + } + announced := announcedDisks(listed) + for _, dcNode := range t.Children() { + dc := dcNode.(*DataCenter) + for _, rackNode := range dc.Children() { + rack := rackNode.(*Rack) + for _, dnNode := range rack.Children() { + dn := dnNode.(*DataNode) + for _, diskNode := range dn.Children() { + disk := diskNode.(*Disk) + if !announced[[4]string{string(dc.Id()), string(rack.Id()), string(dn.Id()), string(disk.Id())}] { + continue + } + batch := func() *master_pb.VolumeListStreamResponse { + return &master_pb.VolumeListStreamResponse{ + DataCenter: string(dc.Id()), + Rack: string(rack.Id()), + DataNode: string(dn.Id()), + DiskType: string(disk.Id()), + } + } + if err := disk.streamVolumes(filter, batchSize, batch, send); err != nil { + return err + } + if err := disk.streamEcShards(filter, batchSize, batch, send); err != nil { + return err + } + } + } + } + } + return nil +} + +const defaultVolumeStreamBatch = 10000 + +// announcedDisks names the disks a listing carried. +func announcedDisks(listed *master_pb.TopologyInfo) map[[4]string]bool { + announced := make(map[[4]string]bool) + if listed == nil { + return announced + } + for _, dc := range listed.DataCenterInfos { + for _, rack := range dc.RackInfos { + for _, node := range rack.DataNodeInfos { + for diskType := range node.DiskInfos { + announced[[4]string{dc.Id, rack.Id, node.Id, diskType}] = true + } + } + } + } + return announced +} + +// streamVolumes sends this disk's volumes a batch at a time. The ids are taken +// in one pass and the messages built in later ones, so the lock is held for a +// batch rather than for the disk, and only 4 bytes per volume are carried +// between passes. A volume that leaves in between is simply not sent. +func (d *Disk) streamVolumes(filter VolumeFilter, batchSize int, newBatch func() *master_pb.VolumeListStreamResponse, send func(*master_pb.VolumeListStreamResponse) error) error { + d.RLock() + ids := make([]needle.VolumeId, 0, len(d.volumes)) + for id := range d.volumes { + ids = append(ids, id) + } + d.RUnlock() + + for start := 0; start < len(ids); start += batchSize { + end := min(start+batchSize, len(ids)) + batch := newBatch() + + d.RLock() + for _, id := range ids[start:end] { + v, found := d.volumes[id] + if !found || !filter.matches(v.Collection, v.Id) { + continue + } + batch.VolumeInfos = append(batch.VolumeInfos, v.ToVolumeInformationMessage()) + } + d.RUnlock() + + if len(batch.VolumeInfos) == 0 { + continue + } + if err := send(batch); err != nil { + return err + } + } + return nil +} + +func (d *Disk) streamEcShards(filter VolumeFilter, batchSize int, newBatch func() *master_pb.VolumeListStreamResponse, send func(*master_pb.VolumeListStreamResponse) error) error { + // GetEcShards already copies under the lock, and a cluster holds far fewer + // ec shards than volumes, so these only need cutting into batches. + shards := d.GetEcShards() + for start := 0; start < len(shards); start += batchSize { + end := min(start+batchSize, len(shards)) + batch := newBatch() + for _, ecv := range shards[start:end] { + if !filter.matches(ecv.Collection, ecv.VolumeId) { + continue + } + batch.EcShardInfos = append(batch.EcShardInfos, ecv.ToVolumeEcShardInformationMessage()) + } + if len(batch.EcShardInfos) == 0 { + continue + } + if err := send(batch); err != nil { + return err + } + } + return nil +} diff --git a/weed/topology/volume_stream_bounds_test.go b/weed/topology/volume_stream_bounds_test.go new file mode 100644 index 000000000..d53f5924c --- /dev/null +++ b/weed/topology/volume_stream_bounds_test.go @@ -0,0 +1,45 @@ +package topology + +import ( + "testing" + + "github.com/seaweedfs/seaweedfs/weed/pb/master_pb" +) + +// A disk that registers after the listing went out has nowhere to go in it, so +// its volumes must not be streamed into a client that cannot place them. It is +// reported by the next listing instead. +func TestStreamVolumesStaysInsideTheAnnouncedTopology(t *testing.T) { + topo := NewTopology("bounds", nil, 32*1024*1024*1024, 5, false) + rack := topo.GetOrCreateDataCenter("dc1").GetOrCreateRack("rack1") + known := rack.GetOrCreateDataNode("10.0.0.1", 8080, 18080, "", "", map[string]uint32{"": 100}) + topo.SyncDataNodeRegistration([]*master_pb.VolumeInformationMessage{ + {Id: 1, Collection: "c", Size: 100, Version: 3}, + {Id: 2, Collection: "c", Size: 200, Version: 3}, + }, known) + + listed := topo.ToTopologyInfo(NoVolumes()) + + // The heartbeat that lands between the listing and the walk below. + late := rack.GetOrCreateDataNode("10.0.0.2", 8080, 18080, "", "", map[string]uint32{"": 100}) + topo.SyncDataNodeRegistration([]*master_pb.VolumeInformationMessage{ + {Id: 3, Collection: "c", Size: 300, Version: 3}, + }, late) + + var streamed []uint32 + err := topo.StreamVolumes(listed, VolumeFilter{}, 10, func(b *master_pb.VolumeListStreamResponse) error { + if b.DataNode == string(late.Id()) { + t.Errorf("streamed a batch for %s, which the listing never named", b.DataNode) + } + for _, v := range b.VolumeInfos { + streamed = append(streamed, v.Id) + } + return nil + }) + if err != nil { + t.Fatal(err) + } + if !equalIds(streamed, []uint32{1, 2}) { + t.Errorf("streamed %v, want the two volumes the listing named", streamed) + } +}