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) + } +}