feat: Phase 10 — control-plane closure (identity, convergence, idempotence)

Stable identity on wire:
- ServerID fields in proto (replica_server_id, server_id on ReplicaAddrMessage)
- volumeServerId wired through volume.go → BlockService.SetServerID
- Identity derived from canonical server ID, not transport addresses

Assignment convergence:
- V2 idempotence via lastAppliedAssignment.equals (full replica set comparison)
- setupPrimaryReplication/Multi idempotence guards
- ProcessAssignments with V2 + V1 dual-path assignment handling

Master-driven control loop:
- RecoveryManager: serialized cancel-and-drain via done channels
- Per-replica heartbeat state reporting (ReplicaShipperStatus)
- masterServerBackend: VolumeBackend calling real MasterServer in-process
- RestoreBlockSnapshot RPC (master + volume server proto)

QA tests (P10 P1-P4):
- Identity: ServerID on wire, fail-closed on missing
- Convergence: assignment delivery, epoch monotonicity, registry coherence
- Idempotence: repeated assignment, multi-replica set comparison
- Control loop: integrationMaster + real allocator + proto round-trip

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
pingqiu
2026-04-02 16:25:43 -07:00
co-authored by Claude Opus 4.6
parent c7eb87c587
commit 3ec8fab2f1
25 changed files with 4267 additions and 253 deletions
@@ -0,0 +1,109 @@
# Phase 10 Decisions
## Decision 1: Phase 10 is control-plane closure, not backend execution rework
`Phase 09` already closed the main backend execution gaps on the chosen path.
`Phase 10` should therefore focus on:
1. real control delivery
2. reassignment / result convergence
3. identity cleanup
It should not reopen accepted backend execution semantics unless a true control-plane bug forces a narrow correction.
## Decision 2: Phase 10 remains bounded to the chosen path
Default scope remains:
1. `RF=2`
2. `sync_all`
3. existing master / volume-server heartbeat path
Future durability modes or wider topology support should not be absorbed casually into this phase.
## Decision 3: Identity cleanup belongs to control-plane closure
The current local server identity remains transport-shaped (`listenAddr`).
`Phase 10` is the right place to strengthen this because identity coherence affects:
1. assignment truth
2. sender/replica identity continuity
3. end-to-end control-path correctness
## Decision 4: Rebuild-server idempotence cleanup is bounded residual work, not the phase itself
The repeated-primary-assignment warning around rebuild-server start is a valid residual note.
It may be addressed in `Phase 10` only if:
1. it is directly relevant to real control/runtime ownership or assignment idempotence
2. it stays bounded
It must not turn `Phase 10` into a broad runtime polish phase.
## Decision 5: P1 identity and control-truth closure accepted
`P1` closes the stable-identity/control-truth gap on the chosen block assignment wire.
Accepted properties:
1. stable server identity is now carried additively on the block assignment proto wire:
- scalar `replica_server_id`
- per-replica `server_id`
2. generated protobuf output, not hand-maintained output, is now the accepted basis for the wire shape
3. master create-path and chosen failover/primary-refresh assignment generation now preserve stable identity on the chosen path
4. volume-server block/control path now uses the same canonical `volumeServerId` as the main volume server
5. `ControlBridge` continues to fail closed when stable identity is missing
Evidence closure:
1. proto/decode proof now covers stable identity round-trip
2. real ingress proof now covers:
- proto assignment
- decode
- `ProcessAssignments()`
- `ControlBridge`
- engine sender `ReplicaID`
3. canonical local identity proof now covers non-default local ID
4. missing-ID fail-closed proof is covered directly
## Decision 6: P2 reassignment/result convergence accepted under the chosen-path volume-server ingress bound
`P2` closes the main reassignment/result-convergence gap on the chosen path without reopening accepted backend execution semantics.
Accepted properties:
1. reassignment through the accepted chosen-path ingress now proves old sender truth is removed and new sender truth is created
2. stale runtime ownership is now proved as a live drain case, not only a bookkeeping absence case
3. reported truth is now checked through `CollectBlockVolumeHeartbeat()`, the same reporting surface used by the live heartbeat loop
4. the accepted no-split-truth claim is bounded to:
- engine sender truth
- stale-runtime residue removed
- heartbeat output truth
5. `P2` does not claim full master-driven failover/gRPC-infrastructure closure beyond the accepted volume-server-side ingress boundary
Evidence closure:
1. real reassignment proof covers `vs2 -> vs3` sender replacement on the chosen path
2. stale-owner proof now blocks a live old goroutine and verifies drain during reassignment
3. heartbeat proof now checks actual heartbeat output rather than local helper state
4. delivery wording is bounded so it does not overclaim a proved live replacement owner in the no-split-truth test
## Decision 7: P3 bounded repeated-assignment/idempotence cleanup accepted on the chosen path
`P3` closes the bounded repeated-assignment residual left after accepted `P2`.
Accepted properties:
1. repeated unchanged chosen-path assignment is now skipped before duplicate V2 orchestrator/recovery work is started
2. the corresponding V1 primary-replication setup path is also absorbed idempotently for unchanged truth
3. changed chosen-path assignment still takes the accepted replacement/update path rather than being suppressed incorrectly
4. `P3` remains bounded cleanup and does not claim general multi-replica idempotence or broad production hardening
Evidence closure:
1. repeated-assignment proof now checks stable V2 event count rather than only stable helper/reporting state
2. changed-assignment guard proof keeps accepted replacement behavior intact
3. externally visible heartbeat state remains coherent after repeated unchanged assignment
File diff suppressed because it is too large Load Diff
+228
View File
@@ -0,0 +1,228 @@
# Phase 10
Date: 2026-04-02
Status: complete
Purpose: close the main end-to-end control-plane gaps on the chosen `RF=2 sync_all` path without reopening accepted backend execution semantics
## Why This Phase Exists
`Phase 09` closed the main backend execution gaps on the chosen path:
1. real `TransferFullBase`
2. real `TransferSnapshot`
3. real `TruncateWAL` under the accepted narrowed contract
4. stronger live runtime ownership on the volume-server path
What still does not exist is stronger end-to-end control-plane closure.
The main remaining gap is no longer:
1. whether the backend execution path is real
It is now:
1. whether the real control path drives and reflects the chosen path coherently enough for product use
## Phase Goal
Strengthen from accepted assignment-entry closure to stronger end-to-end control-plane closure on the chosen path.
## Scope
### In scope
1. heartbeat / gRPC-level control delivery proof on the chosen path
2. reassignment / failover result convergence through the real control path
3. cleaner local identity than transport-shaped `listenAddr`
4. bounded idempotence / repeated-assignment cleanup when it directly affects live control/runtime ownership
### Out of scope
1. reopening accepted `P1` / `P2` / `P3` / `P4` backend execution semantics
2. `RF>2`
3. `best_effort` / `sync_quorum`
4. product-surface rebinding (`CSI` / `NVMe` / `iSCSI`)
5. broad performance optimization
## Phase 10 Items
### P0: Control-Plane Closure Plan
Goal:
- start `Phase 10` with one substantial control-plane closure package, not a loose collection of follow-up fixes
Must prove:
1. the phase is centered on real control-path closure rather than backend execution rework
2. the required closure targets are explicit:
- heartbeat / gRPC delivery
- reassignment / result convergence
- identity cleanup
- bounded repeated-assignment/idempotence cleanup
3. the chosen-path bound remains explicit
Verification mechanism:
1. architect review:
- control-plane scope is explicit and bounded
- proposed slices do not reopen accepted backend execution semantics
2. tester review:
- required end-to-end proofs are explicit
3. manager review:
- the package is concrete enough to assign the first implementation slice
Output artifacts:
1. explicit control-plane closure targets
2. explicit reject shapes
3. initial slice order inside `Phase 10`
Execution note:
- use `phase-10-log.md` as the technical pack for:
- semantic scope
- execution scope
- proof shapes
- assignment templates for `sw` and `tester`
Reject if:
1. `Phase 10` is framed as a vague "polish/control" phase without concrete closure targets
2. accepted `Phase 09` execution semantics are quietly reopened
3. product surfaces or unrelated hardening work are absorbed into this phase
4. no explicit end-to-end proof shape is defined
Status:
- accepted
### P1: Identity And Control-Truth Closure
Goal:
- close stable identity on the real chosen-path control wire so assignment truth, local ingest truth, and `ReplicaID` construction no longer depend on transport-shaped fallback
Accepted scope:
1. stable server identity preserved on the block assignment proto wire
2. master assignment generation preserves stable identity on the chosen path
3. volume-server local identity uses the same canonical server identity as the main volume server
4. real ingress proof:
- proto/decode
- `ProcessAssignments()`
- `ControlBridge`
- engine sender identity
5. fail-closed behavior for missing stable identity
Status:
- accepted
Carry-forward from `P1`:
1. fuller reassignment / failover result convergence is still open
2. broader control-plane reporting closure is still open
### P2: Reassignment / Result Convergence
Goal:
- prove that reassignment and failover converge through the real control path without stale local ownership or stale reported truth lingering after control truth changes
Accepted scope:
1. real failover / reassignment convergence through the chosen control path
2. no stale local runtime owner after control truth changes
3. no stale control/reporting truth after reassignment
4. one-chain proof through the real control path, not only local helper logic
5. no overclaim of broader hardening or product-surface closure
Status:
- accepted
Carry-forward from `P2`:
1. `P2` proves stale owner removal and no stale residue after control truth changes
2. bounded repeated-assignment/idempotence cleanup is still open where repeated primary assignment can still emit rebuild-server relisten warnings
3. `P2` does not claim broad master-driven failover infrastructure closure beyond the accepted volume-server-side chosen-path ingress
### P3: Bounded Repeated-Assignment / Idempotence Cleanup
Goal:
- close the remaining low-severity repeated-assignment/runtime-idempotence gap on the chosen path so duplicate or replacement primary assignments do not leave avoidable relisten/restart noise or ambiguous live-control ownership
Accepted scope:
1. repeated primary assignment on the same chosen-path volume should converge idempotently
2. rebuild-server/runtime side effects should not relaunch noisily when the authoritative control truth is unchanged or already active
3. bounded proof that repeated-assignment cleanup does not reopen accepted `P2` convergence or accepted `Phase 09` execution semantics
4. no expansion into broad runtime polish, product surfaces, or unrelated restart hardening
Status:
- accepted
Carry-forward from `P3`:
1. chosen-path repeated unchanged assignment is now absorbed idempotently across the accepted V2 + V1 live path
2. `P3` remains bounded cleanup; it does not itself close the remaining master-driven heartbeat/gRPC control-loop gap
3. fuller master-originated control delivery proof is still open
### P4: Master-Driven Control-Loop Closure
Goal:
- close the remaining chosen-path control-plane gap by proving that master-originated assignment truth delivered through the real heartbeat / gRPC control loop reaches the live volume-server path and converges without split truth
Required scope:
1. one bounded end-to-end proof from real master-produced chosen-path assignment truth into the live volume-server control path
2. proof that the real heartbeat / gRPC delivery path preserves the already accepted identity and convergence properties
3. proof that externally visible post-delivery state reflects the same new truth after the real master-driven path runs
4. no reopening of accepted `P1` / `P2` / `P3` semantics except for narrow bugs directly exposed by the fuller control-loop proof
5. no expansion into product surfaces, `RF>2`, or broad cluster-hardening work
Status:
- accepted
Carry-forward from `P4`:
1. bounded chosen-path master-driven heartbeat / gRPC control-loop closure is now accepted
2. `P4` does not claim full live transport-stream deployment proof or broad product hardening
3. the next phase should move to `Phase 11` product-surface rebinding
### Planned slice direction after `P0`
1. `P1`:
- identity and control-truth closure on the live control path
2. `P2`:
- reassignment / failover result convergence through the real control path
3. `P3`:
- bounded idempotence / repeated-assignment cleanup after accepted `P1` / `P2`
4. `P4`:
- master-driven heartbeat / gRPC control-loop closure on the chosen path
## Assignment For `sw`
Current next tasks:
1. treat `Phase 10` as closed and keep accepted `P1` / `P2` / `P3` / `P4` semantics stable
2. start `Phase 11` product-surface rebinding from `v2-phase-development-plan.md`
3. keep the first `Phase 11` slice bounded to selected product surfaces rather than broad hardening
4. do not reopen accepted backend execution or control-plane closure except for narrow bug fixes
## Assignment For `tester`
Current next tasks:
1. treat `P4` as accepted bounded control-loop closure on the chosen path
2. validate the first `Phase 11` slice as bounded product-surface rebinding rather than renewed control-plane work
3. keep no-overclaim active around:
- accepted `Phase 09` execution closure
- accepted `Phase 10` control-plane closure
- selected `Phase 11` surface scope vs broader product readiness
- chosen path vs future paths/modes
+4
View File
@@ -343,6 +343,10 @@ func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, v
}
blockService := weed_server.StartBlockService(*v.blockListen, *v.blockDir, *v.blockIQNPrefix, blockPortal, nvmeCfg)
if blockService != nil {
// V2 P10-1: Use the same canonical volume server identity as the main
// volume server. One identity truth across VS, block service, control
// bridge, and runtime ownership.
blockService.SetServerID(volumeServerId)
volumeServer.SetBlockService(blockService)
}
+11
View File
@@ -69,6 +69,8 @@ service Seaweed {
}
rpc ListBlockSnapshots (ListBlockSnapshotsRequest) returns (ListBlockSnapshotsResponse) {
}
rpc RestoreBlockSnapshot (RestoreBlockSnapshotRequest) returns (RestoreBlockSnapshotResponse) {
}
rpc ExpandBlockVolume (ExpandBlockVolumeRequest) returns (ExpandBlockVolumeResponse) {
}
}
@@ -528,11 +530,13 @@ message BlockVolumeAssignment {
string replica_ctrl_addr = 6;
string rebuild_addr = 7;
repeated ReplicaAddrMessage replica_addrs = 8;
string replica_server_id = 9; // V2: stable server identity for scalar replica
}
message ReplicaAddrMessage {
string data_addr = 1;
string ctrl_addr = 2;
string server_id = 3; // V2: stable server identity for this replica
}
message CreateBlockVolumeRequest {
@@ -605,6 +609,13 @@ message BlockSnapshotInfo {
uint64 volume_size_bytes = 3;
}
message RestoreBlockSnapshotRequest {
string volume_name = 1;
uint32 snapshot_id = 2;
}
message RestoreBlockSnapshotResponse {
}
message ExpandBlockVolumeRequest {
string name = 1;
uint64 new_size_bytes = 2;
+206 -85
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v5.29.5
// protoc v3.21.12
// source: master.proto
package master_pb
@@ -1773,7 +1773,9 @@ func (x *StatisticsResponse) GetFileCount() uint64 {
return 0
}
//
// collection related
//
type Collection struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
@@ -1994,7 +1996,9 @@ func (*CollectionDeleteResponse) Descriptor() ([]byte, []int) {
return file_master_proto_rawDescGZIP(), []int{24}
}
//
// volume related
//
type DiskInfo struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
@@ -4149,6 +4153,7 @@ type BlockVolumeAssignment struct {
ReplicaCtrlAddr string `protobuf:"bytes,6,opt,name=replica_ctrl_addr,json=replicaCtrlAddr,proto3" json:"replica_ctrl_addr,omitempty"`
RebuildAddr string `protobuf:"bytes,7,opt,name=rebuild_addr,json=rebuildAddr,proto3" json:"rebuild_addr,omitempty"`
ReplicaAddrs []*ReplicaAddrMessage `protobuf:"bytes,8,rep,name=replica_addrs,json=replicaAddrs,proto3" json:"replica_addrs,omitempty"`
ReplicaServerId string `protobuf:"bytes,9,opt,name=replica_server_id,json=replicaServerId,proto3" json:"replica_server_id,omitempty"` // V2: stable server identity for scalar replica
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -4239,10 +4244,18 @@ func (x *BlockVolumeAssignment) GetReplicaAddrs() []*ReplicaAddrMessage {
return nil
}
func (x *BlockVolumeAssignment) GetReplicaServerId() string {
if x != nil {
return x.ReplicaServerId
}
return ""
}
type ReplicaAddrMessage struct {
state protoimpl.MessageState `protogen:"open.v1"`
DataAddr string `protobuf:"bytes,1,opt,name=data_addr,json=dataAddr,proto3" json:"data_addr,omitempty"`
CtrlAddr string `protobuf:"bytes,2,opt,name=ctrl_addr,json=ctrlAddr,proto3" json:"ctrl_addr,omitempty"`
ServerId string `protobuf:"bytes,3,opt,name=server_id,json=serverId,proto3" json:"server_id,omitempty"` // V2: stable server identity for this replica
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -4291,6 +4304,13 @@ func (x *ReplicaAddrMessage) GetCtrlAddr() string {
return ""
}
func (x *ReplicaAddrMessage) GetServerId() string {
if x != nil {
return x.ServerId
}
return ""
}
type CreateBlockVolumeRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
@@ -5063,6 +5083,94 @@ func (x *BlockSnapshotInfo) GetVolumeSizeBytes() uint64 {
return 0
}
type RestoreBlockSnapshotRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
VolumeName string `protobuf:"bytes,1,opt,name=volume_name,json=volumeName,proto3" json:"volume_name,omitempty"`
SnapshotId uint32 `protobuf:"varint,2,opt,name=snapshot_id,json=snapshotId,proto3" json:"snapshot_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RestoreBlockSnapshotRequest) Reset() {
*x = RestoreBlockSnapshotRequest{}
mi := &file_master_proto_msgTypes[78]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *RestoreBlockSnapshotRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RestoreBlockSnapshotRequest) ProtoMessage() {}
func (x *RestoreBlockSnapshotRequest) ProtoReflect() protoreflect.Message {
mi := &file_master_proto_msgTypes[78]
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 RestoreBlockSnapshotRequest.ProtoReflect.Descriptor instead.
func (*RestoreBlockSnapshotRequest) Descriptor() ([]byte, []int) {
return file_master_proto_rawDescGZIP(), []int{78}
}
func (x *RestoreBlockSnapshotRequest) GetVolumeName() string {
if x != nil {
return x.VolumeName
}
return ""
}
func (x *RestoreBlockSnapshotRequest) GetSnapshotId() uint32 {
if x != nil {
return x.SnapshotId
}
return 0
}
type RestoreBlockSnapshotResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RestoreBlockSnapshotResponse) Reset() {
*x = RestoreBlockSnapshotResponse{}
mi := &file_master_proto_msgTypes[79]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *RestoreBlockSnapshotResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RestoreBlockSnapshotResponse) ProtoMessage() {}
func (x *RestoreBlockSnapshotResponse) ProtoReflect() protoreflect.Message {
mi := &file_master_proto_msgTypes[79]
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 RestoreBlockSnapshotResponse.ProtoReflect.Descriptor instead.
func (*RestoreBlockSnapshotResponse) Descriptor() ([]byte, []int) {
return file_master_proto_rawDescGZIP(), []int{79}
}
type ExpandBlockVolumeRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
@@ -5073,7 +5181,7 @@ type ExpandBlockVolumeRequest struct {
func (x *ExpandBlockVolumeRequest) Reset() {
*x = ExpandBlockVolumeRequest{}
mi := &file_master_proto_msgTypes[78]
mi := &file_master_proto_msgTypes[80]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -5085,7 +5193,7 @@ func (x *ExpandBlockVolumeRequest) String() string {
func (*ExpandBlockVolumeRequest) ProtoMessage() {}
func (x *ExpandBlockVolumeRequest) ProtoReflect() protoreflect.Message {
mi := &file_master_proto_msgTypes[78]
mi := &file_master_proto_msgTypes[80]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -5098,7 +5206,7 @@ func (x *ExpandBlockVolumeRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ExpandBlockVolumeRequest.ProtoReflect.Descriptor instead.
func (*ExpandBlockVolumeRequest) Descriptor() ([]byte, []int) {
return file_master_proto_rawDescGZIP(), []int{78}
return file_master_proto_rawDescGZIP(), []int{80}
}
func (x *ExpandBlockVolumeRequest) GetName() string {
@@ -5124,7 +5232,7 @@ type ExpandBlockVolumeResponse struct {
func (x *ExpandBlockVolumeResponse) Reset() {
*x = ExpandBlockVolumeResponse{}
mi := &file_master_proto_msgTypes[79]
mi := &file_master_proto_msgTypes[81]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -5136,7 +5244,7 @@ func (x *ExpandBlockVolumeResponse) String() string {
func (*ExpandBlockVolumeResponse) ProtoMessage() {}
func (x *ExpandBlockVolumeResponse) ProtoReflect() protoreflect.Message {
mi := &file_master_proto_msgTypes[79]
mi := &file_master_proto_msgTypes[81]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -5149,7 +5257,7 @@ func (x *ExpandBlockVolumeResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ExpandBlockVolumeResponse.ProtoReflect.Descriptor instead.
func (*ExpandBlockVolumeResponse) Descriptor() ([]byte, []int) {
return file_master_proto_rawDescGZIP(), []int{79}
return file_master_proto_rawDescGZIP(), []int{81}
}
func (x *ExpandBlockVolumeResponse) GetCapacityBytes() uint64 {
@@ -5170,7 +5278,7 @@ type SuperBlockExtra_ErasureCoding struct {
func (x *SuperBlockExtra_ErasureCoding) Reset() {
*x = SuperBlockExtra_ErasureCoding{}
mi := &file_master_proto_msgTypes[82]
mi := &file_master_proto_msgTypes[84]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -5182,7 +5290,7 @@ func (x *SuperBlockExtra_ErasureCoding) String() string {
func (*SuperBlockExtra_ErasureCoding) ProtoMessage() {}
func (x *SuperBlockExtra_ErasureCoding) ProtoReflect() protoreflect.Message {
mi := &file_master_proto_msgTypes[82]
mi := &file_master_proto_msgTypes[84]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -5231,7 +5339,7 @@ type LookupVolumeResponse_VolumeIdLocation struct {
func (x *LookupVolumeResponse_VolumeIdLocation) Reset() {
*x = LookupVolumeResponse_VolumeIdLocation{}
mi := &file_master_proto_msgTypes[83]
mi := &file_master_proto_msgTypes[85]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -5243,7 +5351,7 @@ func (x *LookupVolumeResponse_VolumeIdLocation) String() string {
func (*LookupVolumeResponse_VolumeIdLocation) ProtoMessage() {}
func (x *LookupVolumeResponse_VolumeIdLocation) ProtoReflect() protoreflect.Message {
mi := &file_master_proto_msgTypes[83]
mi := &file_master_proto_msgTypes[85]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -5297,7 +5405,7 @@ type LookupEcVolumeResponse_EcShardIdLocation struct {
func (x *LookupEcVolumeResponse_EcShardIdLocation) Reset() {
*x = LookupEcVolumeResponse_EcShardIdLocation{}
mi := &file_master_proto_msgTypes[88]
mi := &file_master_proto_msgTypes[90]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -5309,7 +5417,7 @@ func (x *LookupEcVolumeResponse_EcShardIdLocation) String() string {
func (*LookupEcVolumeResponse_EcShardIdLocation) ProtoMessage() {}
func (x *LookupEcVolumeResponse_EcShardIdLocation) ProtoReflect() protoreflect.Message {
mi := &file_master_proto_msgTypes[88]
mi := &file_master_proto_msgTypes[90]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -5352,7 +5460,7 @@ type ListClusterNodesResponse_ClusterNode struct {
func (x *ListClusterNodesResponse_ClusterNode) Reset() {
*x = ListClusterNodesResponse_ClusterNode{}
mi := &file_master_proto_msgTypes[89]
mi := &file_master_proto_msgTypes[91]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -5364,7 +5472,7 @@ func (x *ListClusterNodesResponse_ClusterNode) String() string {
func (*ListClusterNodesResponse_ClusterNode) ProtoMessage() {}
func (x *ListClusterNodesResponse_ClusterNode) ProtoReflect() protoreflect.Message {
mi := &file_master_proto_msgTypes[89]
mi := &file_master_proto_msgTypes[91]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -5427,7 +5535,7 @@ type RaftListClusterServersResponse_ClusterServers struct {
func (x *RaftListClusterServersResponse_ClusterServers) Reset() {
*x = RaftListClusterServersResponse_ClusterServers{}
mi := &file_master_proto_msgTypes[90]
mi := &file_master_proto_msgTypes[92]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -5439,7 +5547,7 @@ func (x *RaftListClusterServersResponse_ClusterServers) String() string {
func (*RaftListClusterServersResponse_ClusterServers) ProtoMessage() {}
func (x *RaftListClusterServersResponse_ClusterServers) ProtoReflect() protoreflect.Message {
mi := &file_master_proto_msgTypes[90]
mi := &file_master_proto_msgTypes[92]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -5885,7 +5993,7 @@ const file_master_proto_rawDesc = "" +
"volumeSize\x12\x1d\n" +
"\n" +
"block_size\x18\x03 \x01(\rR\tblockSize\x12\x1b\n" +
"\tdisk_type\x18\x04 \x01(\tR\bdiskType\"\xb6\x02\n" +
"\tdisk_type\x18\x04 \x01(\tR\bdiskType\"\xe2\x02\n" +
"\x15BlockVolumeAssignment\x12\x12\n" +
"\x04path\x18\x01 \x01(\tR\x04path\x12\x14\n" +
"\x05epoch\x18\x02 \x01(\x04R\x05epoch\x12\x12\n" +
@@ -5895,10 +6003,12 @@ const file_master_proto_rawDesc = "" +
"\x11replica_data_addr\x18\x05 \x01(\tR\x0freplicaDataAddr\x12*\n" +
"\x11replica_ctrl_addr\x18\x06 \x01(\tR\x0freplicaCtrlAddr\x12!\n" +
"\frebuild_addr\x18\a \x01(\tR\vrebuildAddr\x12B\n" +
"\rreplica_addrs\x18\b \x03(\v2\x1d.master_pb.ReplicaAddrMessageR\freplicaAddrs\"N\n" +
"\rreplica_addrs\x18\b \x03(\v2\x1d.master_pb.ReplicaAddrMessageR\freplicaAddrs\x12*\n" +
"\x11replica_server_id\x18\t \x01(\tR\x0freplicaServerId\"k\n" +
"\x12ReplicaAddrMessage\x12\x1b\n" +
"\tdata_addr\x18\x01 \x01(\tR\bdataAddr\x12\x1b\n" +
"\tctrl_addr\x18\x02 \x01(\tR\bctrlAddr\"\xba\x01\n" +
"\tctrl_addr\x18\x02 \x01(\tR\bctrlAddr\x12\x1b\n" +
"\tserver_id\x18\x03 \x01(\tR\bserverId\"\xba\x01\n" +
"\x18CreateBlockVolumeRequest\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1d\n" +
"\n" +
@@ -5963,12 +6073,18 @@ const file_master_proto_rawDesc = "" +
"snapshotId\x12\x1d\n" +
"\n" +
"created_at\x18\x02 \x01(\x03R\tcreatedAt\x12*\n" +
"\x11volume_size_bytes\x18\x03 \x01(\x04R\x0fvolumeSizeBytes\"T\n" +
"\x11volume_size_bytes\x18\x03 \x01(\x04R\x0fvolumeSizeBytes\"_\n" +
"\x1bRestoreBlockSnapshotRequest\x12\x1f\n" +
"\vvolume_name\x18\x01 \x01(\tR\n" +
"volumeName\x12\x1f\n" +
"\vsnapshot_id\x18\x02 \x01(\rR\n" +
"snapshotId\"\x1e\n" +
"\x1cRestoreBlockSnapshotResponse\"T\n" +
"\x18ExpandBlockVolumeRequest\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12$\n" +
"\x0enew_size_bytes\x18\x02 \x01(\x04R\fnewSizeBytes\"B\n" +
"\x19ExpandBlockVolumeResponse\x12%\n" +
"\x0ecapacity_bytes\x18\x01 \x01(\x04R\rcapacityBytes2\x83\x16\n" +
"\x0ecapacity_bytes\x18\x01 \x01(\x04R\rcapacityBytes2\xee\x16\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" +
@@ -6002,7 +6118,8 @@ const file_master_proto_rawDesc = "" +
"\x11LookupBlockVolume\x12#.master_pb.LookupBlockVolumeRequest\x1a$.master_pb.LookupBlockVolumeResponse\"\x00\x12f\n" +
"\x13CreateBlockSnapshot\x12%.master_pb.CreateBlockSnapshotRequest\x1a&.master_pb.CreateBlockSnapshotResponse\"\x00\x12f\n" +
"\x13DeleteBlockSnapshot\x12%.master_pb.DeleteBlockSnapshotRequest\x1a&.master_pb.DeleteBlockSnapshotResponse\"\x00\x12c\n" +
"\x12ListBlockSnapshots\x12$.master_pb.ListBlockSnapshotsRequest\x1a%.master_pb.ListBlockSnapshotsResponse\"\x00\x12`\n" +
"\x12ListBlockSnapshots\x12$.master_pb.ListBlockSnapshotsRequest\x1a%.master_pb.ListBlockSnapshotsResponse\"\x00\x12i\n" +
"\x14RestoreBlockSnapshot\x12&.master_pb.RestoreBlockSnapshotRequest\x1a'.master_pb.RestoreBlockSnapshotResponse\"\x00\x12`\n" +
"\x11ExpandBlockVolume\x12#.master_pb.ExpandBlockVolumeRequest\x1a$.master_pb.ExpandBlockVolumeResponse\"\x00B2Z0github.com/seaweedfs/seaweedfs/weed/pb/master_pbb\x06proto3"
var (
@@ -6017,7 +6134,7 @@ func file_master_proto_rawDescGZIP() []byte {
return file_master_proto_rawDescData
}
var file_master_proto_msgTypes = make([]protoimpl.MessageInfo, 91)
var file_master_proto_msgTypes = make([]protoimpl.MessageInfo, 93)
var file_master_proto_goTypes = []any{
(*Heartbeat)(nil), // 0: master_pb.Heartbeat
(*HeartbeatResponse)(nil), // 1: master_pb.HeartbeatResponse
@@ -6097,20 +6214,22 @@ var file_master_proto_goTypes = []any{
(*ListBlockSnapshotsRequest)(nil), // 75: master_pb.ListBlockSnapshotsRequest
(*ListBlockSnapshotsResponse)(nil), // 76: master_pb.ListBlockSnapshotsResponse
(*BlockSnapshotInfo)(nil), // 77: master_pb.BlockSnapshotInfo
(*ExpandBlockVolumeRequest)(nil), // 78: master_pb.ExpandBlockVolumeRequest
(*ExpandBlockVolumeResponse)(nil), // 79: master_pb.ExpandBlockVolumeResponse
nil, // 80: master_pb.Heartbeat.MaxVolumeCountsEntry
nil, // 81: master_pb.StorageBackend.PropertiesEntry
(*SuperBlockExtra_ErasureCoding)(nil), // 82: master_pb.SuperBlockExtra.ErasureCoding
(*LookupVolumeResponse_VolumeIdLocation)(nil), // 83: master_pb.LookupVolumeResponse.VolumeIdLocation
nil, // 84: master_pb.DataNodeInfo.DiskInfosEntry
nil, // 85: master_pb.RackInfo.DiskInfosEntry
nil, // 86: master_pb.DataCenterInfo.DiskInfosEntry
nil, // 87: master_pb.TopologyInfo.DiskInfosEntry
(*LookupEcVolumeResponse_EcShardIdLocation)(nil), // 88: master_pb.LookupEcVolumeResponse.EcShardIdLocation
(*ListClusterNodesResponse_ClusterNode)(nil), // 89: master_pb.ListClusterNodesResponse.ClusterNode
(*RaftListClusterServersResponse_ClusterServers)(nil), // 90: master_pb.RaftListClusterServersResponse.ClusterServers
(*volume_server_pb.VolumeServerState)(nil), // 91: volume_server_pb.VolumeServerState
(*RestoreBlockSnapshotRequest)(nil), // 78: master_pb.RestoreBlockSnapshotRequest
(*RestoreBlockSnapshotResponse)(nil), // 79: master_pb.RestoreBlockSnapshotResponse
(*ExpandBlockVolumeRequest)(nil), // 80: master_pb.ExpandBlockVolumeRequest
(*ExpandBlockVolumeResponse)(nil), // 81: master_pb.ExpandBlockVolumeResponse
nil, // 82: master_pb.Heartbeat.MaxVolumeCountsEntry
nil, // 83: master_pb.StorageBackend.PropertiesEntry
(*SuperBlockExtra_ErasureCoding)(nil), // 84: master_pb.SuperBlockExtra.ErasureCoding
(*LookupVolumeResponse_VolumeIdLocation)(nil), // 85: master_pb.LookupVolumeResponse.VolumeIdLocation
nil, // 86: master_pb.DataNodeInfo.DiskInfosEntry
nil, // 87: master_pb.RackInfo.DiskInfosEntry
nil, // 88: master_pb.DataCenterInfo.DiskInfosEntry
nil, // 89: master_pb.TopologyInfo.DiskInfosEntry
(*LookupEcVolumeResponse_EcShardIdLocation)(nil), // 90: master_pb.LookupEcVolumeResponse.EcShardIdLocation
(*ListClusterNodesResponse_ClusterNode)(nil), // 91: master_pb.ListClusterNodesResponse.ClusterNode
(*RaftListClusterServersResponse_ClusterServers)(nil), // 92: master_pb.RaftListClusterServersResponse.ClusterServers
(*volume_server_pb.VolumeServerState)(nil), // 93: volume_server_pb.VolumeServerState
}
var file_master_proto_depIdxs = []int32{
2, // 0: master_pb.Heartbeat.volumes:type_name -> master_pb.VolumeInformationMessage
@@ -6119,35 +6238,35 @@ var file_master_proto_depIdxs = []int32{
4, // 3: master_pb.Heartbeat.ec_shards:type_name -> master_pb.VolumeEcShardInformationMessage
4, // 4: master_pb.Heartbeat.new_ec_shards:type_name -> master_pb.VolumeEcShardInformationMessage
4, // 5: master_pb.Heartbeat.deleted_ec_shards:type_name -> master_pb.VolumeEcShardInformationMessage
80, // 6: master_pb.Heartbeat.max_volume_counts:type_name -> master_pb.Heartbeat.MaxVolumeCountsEntry
91, // 7: master_pb.Heartbeat.state:type_name -> volume_server_pb.VolumeServerState
82, // 6: master_pb.Heartbeat.max_volume_counts:type_name -> master_pb.Heartbeat.MaxVolumeCountsEntry
93, // 7: master_pb.Heartbeat.state:type_name -> volume_server_pb.VolumeServerState
61, // 8: master_pb.Heartbeat.block_volume_infos:type_name -> master_pb.BlockVolumeInfoMessage
62, // 9: master_pb.Heartbeat.new_block_volumes:type_name -> master_pb.BlockVolumeShortInfoMessage
62, // 10: master_pb.Heartbeat.deleted_block_volumes:type_name -> master_pb.BlockVolumeShortInfoMessage
5, // 11: master_pb.HeartbeatResponse.storage_backends:type_name -> master_pb.StorageBackend
63, // 12: master_pb.HeartbeatResponse.block_volume_assignments:type_name -> master_pb.BlockVolumeAssignment
81, // 13: master_pb.StorageBackend.properties:type_name -> master_pb.StorageBackend.PropertiesEntry
82, // 14: master_pb.SuperBlockExtra.erasure_coding:type_name -> master_pb.SuperBlockExtra.ErasureCoding
83, // 13: master_pb.StorageBackend.properties:type_name -> master_pb.StorageBackend.PropertiesEntry
84, // 14: master_pb.SuperBlockExtra.erasure_coding:type_name -> master_pb.SuperBlockExtra.ErasureCoding
9, // 15: master_pb.KeepConnectedResponse.volume_location:type_name -> master_pb.VolumeLocation
10, // 16: master_pb.KeepConnectedResponse.cluster_node_update:type_name -> master_pb.ClusterNodeUpdate
83, // 17: master_pb.LookupVolumeResponse.volume_id_locations:type_name -> master_pb.LookupVolumeResponse.VolumeIdLocation
85, // 17: master_pb.LookupVolumeResponse.volume_id_locations:type_name -> master_pb.LookupVolumeResponse.VolumeIdLocation
14, // 18: master_pb.AssignResponse.replicas:type_name -> master_pb.Location
14, // 19: master_pb.AssignResponse.location:type_name -> master_pb.Location
20, // 20: master_pb.CollectionListResponse.collections:type_name -> master_pb.Collection
2, // 21: master_pb.DiskInfo.volume_infos:type_name -> master_pb.VolumeInformationMessage
4, // 22: master_pb.DiskInfo.ec_shard_infos:type_name -> master_pb.VolumeEcShardInformationMessage
84, // 23: master_pb.DataNodeInfo.diskInfos:type_name -> master_pb.DataNodeInfo.DiskInfosEntry
86, // 23: master_pb.DataNodeInfo.diskInfos:type_name -> master_pb.DataNodeInfo.DiskInfosEntry
26, // 24: master_pb.RackInfo.data_node_infos:type_name -> master_pb.DataNodeInfo
85, // 25: master_pb.RackInfo.diskInfos:type_name -> master_pb.RackInfo.DiskInfosEntry
87, // 25: master_pb.RackInfo.diskInfos:type_name -> master_pb.RackInfo.DiskInfosEntry
27, // 26: master_pb.DataCenterInfo.rack_infos:type_name -> master_pb.RackInfo
86, // 27: master_pb.DataCenterInfo.diskInfos:type_name -> master_pb.DataCenterInfo.DiskInfosEntry
88, // 27: master_pb.DataCenterInfo.diskInfos:type_name -> master_pb.DataCenterInfo.DiskInfosEntry
28, // 28: master_pb.TopologyInfo.data_center_infos:type_name -> master_pb.DataCenterInfo
87, // 29: master_pb.TopologyInfo.diskInfos:type_name -> master_pb.TopologyInfo.DiskInfosEntry
89, // 29: master_pb.TopologyInfo.diskInfos:type_name -> master_pb.TopologyInfo.DiskInfosEntry
29, // 30: master_pb.VolumeListResponse.topology_info:type_name -> master_pb.TopologyInfo
88, // 31: master_pb.LookupEcVolumeResponse.shard_id_locations:type_name -> master_pb.LookupEcVolumeResponse.EcShardIdLocation
90, // 31: master_pb.LookupEcVolumeResponse.shard_id_locations:type_name -> master_pb.LookupEcVolumeResponse.EcShardIdLocation
5, // 32: master_pb.GetMasterConfigurationResponse.storage_backends:type_name -> master_pb.StorageBackend
89, // 33: master_pb.ListClusterNodesResponse.cluster_nodes:type_name -> master_pb.ListClusterNodesResponse.ClusterNode
90, // 34: master_pb.RaftListClusterServersResponse.cluster_servers:type_name -> master_pb.RaftListClusterServersResponse.ClusterServers
91, // 33: master_pb.ListClusterNodesResponse.cluster_nodes:type_name -> master_pb.ListClusterNodesResponse.ClusterNode
92, // 34: master_pb.RaftListClusterServersResponse.cluster_servers:type_name -> master_pb.RaftListClusterServersResponse.ClusterServers
64, // 35: master_pb.BlockVolumeAssignment.replica_addrs:type_name -> master_pb.ReplicaAddrMessage
77, // 36: master_pb.ListBlockSnapshotsResponse.snapshots:type_name -> master_pb.BlockSnapshotInfo
14, // 37: master_pb.LookupVolumeResponse.VolumeIdLocation.locations:type_name -> master_pb.Location
@@ -6186,40 +6305,42 @@ var file_master_proto_depIdxs = []int32{
71, // 70: master_pb.Seaweed.CreateBlockSnapshot:input_type -> master_pb.CreateBlockSnapshotRequest
73, // 71: master_pb.Seaweed.DeleteBlockSnapshot:input_type -> master_pb.DeleteBlockSnapshotRequest
75, // 72: master_pb.Seaweed.ListBlockSnapshots:input_type -> master_pb.ListBlockSnapshotsRequest
78, // 73: master_pb.Seaweed.ExpandBlockVolume:input_type -> master_pb.ExpandBlockVolumeRequest
1, // 74: master_pb.Seaweed.SendHeartbeat:output_type -> master_pb.HeartbeatResponse
11, // 75: master_pb.Seaweed.KeepConnected:output_type -> master_pb.KeepConnectedResponse
13, // 76: master_pb.Seaweed.LookupVolume:output_type -> master_pb.LookupVolumeResponse
17, // 77: master_pb.Seaweed.Assign:output_type -> master_pb.AssignResponse
17, // 78: master_pb.Seaweed.StreamAssign:output_type -> master_pb.AssignResponse
19, // 79: master_pb.Seaweed.Statistics:output_type -> master_pb.StatisticsResponse
22, // 80: master_pb.Seaweed.CollectionList:output_type -> master_pb.CollectionListResponse
24, // 81: master_pb.Seaweed.CollectionDelete:output_type -> master_pb.CollectionDeleteResponse
31, // 82: master_pb.Seaweed.VolumeList:output_type -> master_pb.VolumeListResponse
33, // 83: master_pb.Seaweed.LookupEcVolume:output_type -> master_pb.LookupEcVolumeResponse
35, // 84: master_pb.Seaweed.VacuumVolume:output_type -> master_pb.VacuumVolumeResponse
37, // 85: master_pb.Seaweed.DisableVacuum:output_type -> master_pb.DisableVacuumResponse
39, // 86: master_pb.Seaweed.EnableVacuum:output_type -> master_pb.EnableVacuumResponse
41, // 87: master_pb.Seaweed.VolumeMarkReadonly:output_type -> master_pb.VolumeMarkReadonlyResponse
43, // 88: master_pb.Seaweed.GetMasterConfiguration:output_type -> master_pb.GetMasterConfigurationResponse
45, // 89: master_pb.Seaweed.ListClusterNodes:output_type -> master_pb.ListClusterNodesResponse
47, // 90: master_pb.Seaweed.LeaseAdminToken:output_type -> master_pb.LeaseAdminTokenResponse
49, // 91: master_pb.Seaweed.ReleaseAdminToken:output_type -> master_pb.ReleaseAdminTokenResponse
51, // 92: master_pb.Seaweed.Ping:output_type -> master_pb.PingResponse
57, // 93: master_pb.Seaweed.RaftListClusterServers:output_type -> master_pb.RaftListClusterServersResponse
53, // 94: master_pb.Seaweed.RaftAddServer:output_type -> master_pb.RaftAddServerResponse
55, // 95: master_pb.Seaweed.RaftRemoveServer:output_type -> master_pb.RaftRemoveServerResponse
59, // 96: master_pb.Seaweed.RaftLeadershipTransfer:output_type -> master_pb.RaftLeadershipTransferResponse
60, // 97: master_pb.Seaweed.VolumeGrow:output_type -> master_pb.VolumeGrowResponse
66, // 98: master_pb.Seaweed.CreateBlockVolume:output_type -> master_pb.CreateBlockVolumeResponse
68, // 99: master_pb.Seaweed.DeleteBlockVolume:output_type -> master_pb.DeleteBlockVolumeResponse
70, // 100: master_pb.Seaweed.LookupBlockVolume:output_type -> master_pb.LookupBlockVolumeResponse
72, // 101: master_pb.Seaweed.CreateBlockSnapshot:output_type -> master_pb.CreateBlockSnapshotResponse
74, // 102: master_pb.Seaweed.DeleteBlockSnapshot:output_type -> master_pb.DeleteBlockSnapshotResponse
76, // 103: master_pb.Seaweed.ListBlockSnapshots:output_type -> master_pb.ListBlockSnapshotsResponse
79, // 104: master_pb.Seaweed.ExpandBlockVolume:output_type -> master_pb.ExpandBlockVolumeResponse
74, // [74:105] is the sub-list for method output_type
43, // [43:74] is the sub-list for method input_type
78, // 73: master_pb.Seaweed.RestoreBlockSnapshot:input_type -> master_pb.RestoreBlockSnapshotRequest
80, // 74: master_pb.Seaweed.ExpandBlockVolume:input_type -> master_pb.ExpandBlockVolumeRequest
1, // 75: master_pb.Seaweed.SendHeartbeat:output_type -> master_pb.HeartbeatResponse
11, // 76: master_pb.Seaweed.KeepConnected:output_type -> master_pb.KeepConnectedResponse
13, // 77: master_pb.Seaweed.LookupVolume:output_type -> master_pb.LookupVolumeResponse
17, // 78: master_pb.Seaweed.Assign:output_type -> master_pb.AssignResponse
17, // 79: master_pb.Seaweed.StreamAssign:output_type -> master_pb.AssignResponse
19, // 80: master_pb.Seaweed.Statistics:output_type -> master_pb.StatisticsResponse
22, // 81: master_pb.Seaweed.CollectionList:output_type -> master_pb.CollectionListResponse
24, // 82: master_pb.Seaweed.CollectionDelete:output_type -> master_pb.CollectionDeleteResponse
31, // 83: master_pb.Seaweed.VolumeList:output_type -> master_pb.VolumeListResponse
33, // 84: master_pb.Seaweed.LookupEcVolume:output_type -> master_pb.LookupEcVolumeResponse
35, // 85: master_pb.Seaweed.VacuumVolume:output_type -> master_pb.VacuumVolumeResponse
37, // 86: master_pb.Seaweed.DisableVacuum:output_type -> master_pb.DisableVacuumResponse
39, // 87: master_pb.Seaweed.EnableVacuum:output_type -> master_pb.EnableVacuumResponse
41, // 88: master_pb.Seaweed.VolumeMarkReadonly:output_type -> master_pb.VolumeMarkReadonlyResponse
43, // 89: master_pb.Seaweed.GetMasterConfiguration:output_type -> master_pb.GetMasterConfigurationResponse
45, // 90: master_pb.Seaweed.ListClusterNodes:output_type -> master_pb.ListClusterNodesResponse
47, // 91: master_pb.Seaweed.LeaseAdminToken:output_type -> master_pb.LeaseAdminTokenResponse
49, // 92: master_pb.Seaweed.ReleaseAdminToken:output_type -> master_pb.ReleaseAdminTokenResponse
51, // 93: master_pb.Seaweed.Ping:output_type -> master_pb.PingResponse
57, // 94: master_pb.Seaweed.RaftListClusterServers:output_type -> master_pb.RaftListClusterServersResponse
53, // 95: master_pb.Seaweed.RaftAddServer:output_type -> master_pb.RaftAddServerResponse
55, // 96: master_pb.Seaweed.RaftRemoveServer:output_type -> master_pb.RaftRemoveServerResponse
59, // 97: master_pb.Seaweed.RaftLeadershipTransfer:output_type -> master_pb.RaftLeadershipTransferResponse
60, // 98: master_pb.Seaweed.VolumeGrow:output_type -> master_pb.VolumeGrowResponse
66, // 99: master_pb.Seaweed.CreateBlockVolume:output_type -> master_pb.CreateBlockVolumeResponse
68, // 100: master_pb.Seaweed.DeleteBlockVolume:output_type -> master_pb.DeleteBlockVolumeResponse
70, // 101: master_pb.Seaweed.LookupBlockVolume:output_type -> master_pb.LookupBlockVolumeResponse
72, // 102: master_pb.Seaweed.CreateBlockSnapshot:output_type -> master_pb.CreateBlockSnapshotResponse
74, // 103: master_pb.Seaweed.DeleteBlockSnapshot:output_type -> master_pb.DeleteBlockSnapshotResponse
76, // 104: master_pb.Seaweed.ListBlockSnapshots:output_type -> master_pb.ListBlockSnapshotsResponse
79, // 105: master_pb.Seaweed.RestoreBlockSnapshot:output_type -> master_pb.RestoreBlockSnapshotResponse
81, // 106: master_pb.Seaweed.ExpandBlockVolume:output_type -> master_pb.ExpandBlockVolumeResponse
75, // [75:107] is the sub-list for method output_type
43, // [43:75] is the sub-list for method input_type
43, // [43:43] is the sub-list for extension type_name
43, // [43:43] is the sub-list for extension extendee
0, // [0:43] is the sub-list for field type_name
@@ -6236,7 +6357,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: 91,
NumMessages: 93,
NumExtensions: 0,
NumServices: 1,
},
+39 -1
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc v5.29.5
// - protoc v3.21.12
// source: master.proto
package master_pb
@@ -49,6 +49,7 @@ const (
Seaweed_CreateBlockSnapshot_FullMethodName = "/master_pb.Seaweed/CreateBlockSnapshot"
Seaweed_DeleteBlockSnapshot_FullMethodName = "/master_pb.Seaweed/DeleteBlockSnapshot"
Seaweed_ListBlockSnapshots_FullMethodName = "/master_pb.Seaweed/ListBlockSnapshots"
Seaweed_RestoreBlockSnapshot_FullMethodName = "/master_pb.Seaweed/RestoreBlockSnapshot"
Seaweed_ExpandBlockVolume_FullMethodName = "/master_pb.Seaweed/ExpandBlockVolume"
)
@@ -86,6 +87,7 @@ type SeaweedClient interface {
CreateBlockSnapshot(ctx context.Context, in *CreateBlockSnapshotRequest, opts ...grpc.CallOption) (*CreateBlockSnapshotResponse, error)
DeleteBlockSnapshot(ctx context.Context, in *DeleteBlockSnapshotRequest, opts ...grpc.CallOption) (*DeleteBlockSnapshotResponse, error)
ListBlockSnapshots(ctx context.Context, in *ListBlockSnapshotsRequest, opts ...grpc.CallOption) (*ListBlockSnapshotsResponse, error)
RestoreBlockSnapshot(ctx context.Context, in *RestoreBlockSnapshotRequest, opts ...grpc.CallOption) (*RestoreBlockSnapshotResponse, error)
ExpandBlockVolume(ctx context.Context, in *ExpandBlockVolumeRequest, opts ...grpc.CallOption) (*ExpandBlockVolumeResponse, error)
}
@@ -406,6 +408,16 @@ func (c *seaweedClient) ListBlockSnapshots(ctx context.Context, in *ListBlockSna
return out, nil
}
func (c *seaweedClient) RestoreBlockSnapshot(ctx context.Context, in *RestoreBlockSnapshotRequest, opts ...grpc.CallOption) (*RestoreBlockSnapshotResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(RestoreBlockSnapshotResponse)
err := c.cc.Invoke(ctx, Seaweed_RestoreBlockSnapshot_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *seaweedClient) ExpandBlockVolume(ctx context.Context, in *ExpandBlockVolumeRequest, opts ...grpc.CallOption) (*ExpandBlockVolumeResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ExpandBlockVolumeResponse)
@@ -450,6 +462,7 @@ type SeaweedServer interface {
CreateBlockSnapshot(context.Context, *CreateBlockSnapshotRequest) (*CreateBlockSnapshotResponse, error)
DeleteBlockSnapshot(context.Context, *DeleteBlockSnapshotRequest) (*DeleteBlockSnapshotResponse, error)
ListBlockSnapshots(context.Context, *ListBlockSnapshotsRequest) (*ListBlockSnapshotsResponse, error)
RestoreBlockSnapshot(context.Context, *RestoreBlockSnapshotRequest) (*RestoreBlockSnapshotResponse, error)
ExpandBlockVolume(context.Context, *ExpandBlockVolumeRequest) (*ExpandBlockVolumeResponse, error)
mustEmbedUnimplementedSeaweedServer()
}
@@ -551,6 +564,9 @@ func (UnimplementedSeaweedServer) DeleteBlockSnapshot(context.Context, *DeleteBl
func (UnimplementedSeaweedServer) ListBlockSnapshots(context.Context, *ListBlockSnapshotsRequest) (*ListBlockSnapshotsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListBlockSnapshots not implemented")
}
func (UnimplementedSeaweedServer) RestoreBlockSnapshot(context.Context, *RestoreBlockSnapshotRequest) (*RestoreBlockSnapshotResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RestoreBlockSnapshot not implemented")
}
func (UnimplementedSeaweedServer) ExpandBlockVolume(context.Context, *ExpandBlockVolumeRequest) (*ExpandBlockVolumeResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ExpandBlockVolume not implemented")
}
@@ -1082,6 +1098,24 @@ func _Seaweed_ListBlockSnapshots_Handler(srv interface{}, ctx context.Context, d
return interceptor(ctx, in, info, handler)
}
func _Seaweed_RestoreBlockSnapshot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RestoreBlockSnapshotRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SeaweedServer).RestoreBlockSnapshot(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Seaweed_RestoreBlockSnapshot_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SeaweedServer).RestoreBlockSnapshot(ctx, req.(*RestoreBlockSnapshotRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Seaweed_ExpandBlockVolume_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ExpandBlockVolumeRequest)
if err := dec(in); err != nil {
@@ -1215,6 +1249,10 @@ var Seaweed_ServiceDesc = grpc.ServiceDesc{
MethodName: "ListBlockSnapshots",
Handler: _Seaweed_ListBlockSnapshots_Handler,
},
{
MethodName: "RestoreBlockSnapshot",
Handler: _Seaweed_RestoreBlockSnapshot_Handler,
},
{
MethodName: "ExpandBlockVolume",
Handler: _Seaweed_ExpandBlockVolume_Handler,
+9
View File
@@ -149,6 +149,8 @@ service VolumeServer {
}
rpc ListBlockSnapshots (ListBlockSnapshotsRequest) returns (ListBlockSnapshotsResponse) {
}
rpc RestoreBlockSnapshot (RestoreBlockSnapshotRequest) returns (RestoreBlockSnapshotResponse) {
}
rpc ExpandBlockVolume (ExpandBlockVolumeRequest) returns (ExpandBlockVolumeResponse) {
}
rpc PrepareExpandBlockVolume (PrepareExpandBlockVolumeRequest) returns (PrepareExpandBlockVolumeResponse) {
@@ -821,6 +823,13 @@ message DeleteBlockSnapshotRequest {
message DeleteBlockSnapshotResponse {
}
message RestoreBlockSnapshotRequest {
string name = 1;
uint32 snapshot_id = 2;
}
message RestoreBlockSnapshotResponse {
}
message ListBlockSnapshotsRequest {
string name = 1;
}
+253 -155
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v5.29.5
// protoc v3.21.12
// source: volume_server.proto
package volume_server_pb
@@ -6633,6 +6633,94 @@ func (*DeleteBlockSnapshotResponse) Descriptor() ([]byte, []int) {
return file_volume_server_proto_rawDescGZIP(), []int{114}
}
type RestoreBlockSnapshotRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
SnapshotId uint32 `protobuf:"varint,2,opt,name=snapshot_id,json=snapshotId,proto3" json:"snapshot_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RestoreBlockSnapshotRequest) Reset() {
*x = RestoreBlockSnapshotRequest{}
mi := &file_volume_server_proto_msgTypes[115]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *RestoreBlockSnapshotRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RestoreBlockSnapshotRequest) ProtoMessage() {}
func (x *RestoreBlockSnapshotRequest) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[115]
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 RestoreBlockSnapshotRequest.ProtoReflect.Descriptor instead.
func (*RestoreBlockSnapshotRequest) Descriptor() ([]byte, []int) {
return file_volume_server_proto_rawDescGZIP(), []int{115}
}
func (x *RestoreBlockSnapshotRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *RestoreBlockSnapshotRequest) GetSnapshotId() uint32 {
if x != nil {
return x.SnapshotId
}
return 0
}
type RestoreBlockSnapshotResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RestoreBlockSnapshotResponse) Reset() {
*x = RestoreBlockSnapshotResponse{}
mi := &file_volume_server_proto_msgTypes[116]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *RestoreBlockSnapshotResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RestoreBlockSnapshotResponse) ProtoMessage() {}
func (x *RestoreBlockSnapshotResponse) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[116]
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 RestoreBlockSnapshotResponse.ProtoReflect.Descriptor instead.
func (*RestoreBlockSnapshotResponse) Descriptor() ([]byte, []int) {
return file_volume_server_proto_rawDescGZIP(), []int{116}
}
type ListBlockSnapshotsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
@@ -6642,7 +6730,7 @@ type ListBlockSnapshotsRequest struct {
func (x *ListBlockSnapshotsRequest) Reset() {
*x = ListBlockSnapshotsRequest{}
mi := &file_volume_server_proto_msgTypes[115]
mi := &file_volume_server_proto_msgTypes[117]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6654,7 +6742,7 @@ func (x *ListBlockSnapshotsRequest) String() string {
func (*ListBlockSnapshotsRequest) ProtoMessage() {}
func (x *ListBlockSnapshotsRequest) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[115]
mi := &file_volume_server_proto_msgTypes[117]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6667,7 +6755,7 @@ func (x *ListBlockSnapshotsRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListBlockSnapshotsRequest.ProtoReflect.Descriptor instead.
func (*ListBlockSnapshotsRequest) Descriptor() ([]byte, []int) {
return file_volume_server_proto_rawDescGZIP(), []int{115}
return file_volume_server_proto_rawDescGZIP(), []int{117}
}
func (x *ListBlockSnapshotsRequest) GetName() string {
@@ -6686,7 +6774,7 @@ type ListBlockSnapshotsResponse struct {
func (x *ListBlockSnapshotsResponse) Reset() {
*x = ListBlockSnapshotsResponse{}
mi := &file_volume_server_proto_msgTypes[116]
mi := &file_volume_server_proto_msgTypes[118]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6698,7 +6786,7 @@ func (x *ListBlockSnapshotsResponse) String() string {
func (*ListBlockSnapshotsResponse) ProtoMessage() {}
func (x *ListBlockSnapshotsResponse) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[116]
mi := &file_volume_server_proto_msgTypes[118]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6711,7 +6799,7 @@ func (x *ListBlockSnapshotsResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListBlockSnapshotsResponse.ProtoReflect.Descriptor instead.
func (*ListBlockSnapshotsResponse) Descriptor() ([]byte, []int) {
return file_volume_server_proto_rawDescGZIP(), []int{116}
return file_volume_server_proto_rawDescGZIP(), []int{118}
}
func (x *ListBlockSnapshotsResponse) GetSnapshots() []*BlockSnapshotInfo {
@@ -6732,7 +6820,7 @@ type BlockSnapshotInfo struct {
func (x *BlockSnapshotInfo) Reset() {
*x = BlockSnapshotInfo{}
mi := &file_volume_server_proto_msgTypes[117]
mi := &file_volume_server_proto_msgTypes[119]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6744,7 +6832,7 @@ func (x *BlockSnapshotInfo) String() string {
func (*BlockSnapshotInfo) ProtoMessage() {}
func (x *BlockSnapshotInfo) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[117]
mi := &file_volume_server_proto_msgTypes[119]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6757,7 +6845,7 @@ func (x *BlockSnapshotInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use BlockSnapshotInfo.ProtoReflect.Descriptor instead.
func (*BlockSnapshotInfo) Descriptor() ([]byte, []int) {
return file_volume_server_proto_rawDescGZIP(), []int{117}
return file_volume_server_proto_rawDescGZIP(), []int{119}
}
func (x *BlockSnapshotInfo) GetSnapshotId() uint32 {
@@ -6791,7 +6879,7 @@ type ExpandBlockVolumeRequest struct {
func (x *ExpandBlockVolumeRequest) Reset() {
*x = ExpandBlockVolumeRequest{}
mi := &file_volume_server_proto_msgTypes[118]
mi := &file_volume_server_proto_msgTypes[120]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6803,7 +6891,7 @@ func (x *ExpandBlockVolumeRequest) String() string {
func (*ExpandBlockVolumeRequest) ProtoMessage() {}
func (x *ExpandBlockVolumeRequest) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[118]
mi := &file_volume_server_proto_msgTypes[120]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6816,7 +6904,7 @@ func (x *ExpandBlockVolumeRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ExpandBlockVolumeRequest.ProtoReflect.Descriptor instead.
func (*ExpandBlockVolumeRequest) Descriptor() ([]byte, []int) {
return file_volume_server_proto_rawDescGZIP(), []int{118}
return file_volume_server_proto_rawDescGZIP(), []int{120}
}
func (x *ExpandBlockVolumeRequest) GetName() string {
@@ -6842,7 +6930,7 @@ type ExpandBlockVolumeResponse struct {
func (x *ExpandBlockVolumeResponse) Reset() {
*x = ExpandBlockVolumeResponse{}
mi := &file_volume_server_proto_msgTypes[119]
mi := &file_volume_server_proto_msgTypes[121]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6854,7 +6942,7 @@ func (x *ExpandBlockVolumeResponse) String() string {
func (*ExpandBlockVolumeResponse) ProtoMessage() {}
func (x *ExpandBlockVolumeResponse) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[119]
mi := &file_volume_server_proto_msgTypes[121]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6867,7 +6955,7 @@ func (x *ExpandBlockVolumeResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ExpandBlockVolumeResponse.ProtoReflect.Descriptor instead.
func (*ExpandBlockVolumeResponse) Descriptor() ([]byte, []int) {
return file_volume_server_proto_rawDescGZIP(), []int{119}
return file_volume_server_proto_rawDescGZIP(), []int{121}
}
func (x *ExpandBlockVolumeResponse) GetCapacityBytes() uint64 {
@@ -6888,7 +6976,7 @@ type PrepareExpandBlockVolumeRequest struct {
func (x *PrepareExpandBlockVolumeRequest) Reset() {
*x = PrepareExpandBlockVolumeRequest{}
mi := &file_volume_server_proto_msgTypes[120]
mi := &file_volume_server_proto_msgTypes[122]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6900,7 +6988,7 @@ func (x *PrepareExpandBlockVolumeRequest) String() string {
func (*PrepareExpandBlockVolumeRequest) ProtoMessage() {}
func (x *PrepareExpandBlockVolumeRequest) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[120]
mi := &file_volume_server_proto_msgTypes[122]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6913,7 +7001,7 @@ func (x *PrepareExpandBlockVolumeRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use PrepareExpandBlockVolumeRequest.ProtoReflect.Descriptor instead.
func (*PrepareExpandBlockVolumeRequest) Descriptor() ([]byte, []int) {
return file_volume_server_proto_rawDescGZIP(), []int{120}
return file_volume_server_proto_rawDescGZIP(), []int{122}
}
func (x *PrepareExpandBlockVolumeRequest) GetName() string {
@@ -6945,7 +7033,7 @@ type PrepareExpandBlockVolumeResponse struct {
func (x *PrepareExpandBlockVolumeResponse) Reset() {
*x = PrepareExpandBlockVolumeResponse{}
mi := &file_volume_server_proto_msgTypes[121]
mi := &file_volume_server_proto_msgTypes[123]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6957,7 +7045,7 @@ func (x *PrepareExpandBlockVolumeResponse) String() string {
func (*PrepareExpandBlockVolumeResponse) ProtoMessage() {}
func (x *PrepareExpandBlockVolumeResponse) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[121]
mi := &file_volume_server_proto_msgTypes[123]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6970,7 +7058,7 @@ func (x *PrepareExpandBlockVolumeResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use PrepareExpandBlockVolumeResponse.ProtoReflect.Descriptor instead.
func (*PrepareExpandBlockVolumeResponse) Descriptor() ([]byte, []int) {
return file_volume_server_proto_rawDescGZIP(), []int{121}
return file_volume_server_proto_rawDescGZIP(), []int{123}
}
type CommitExpandBlockVolumeRequest struct {
@@ -6983,7 +7071,7 @@ type CommitExpandBlockVolumeRequest struct {
func (x *CommitExpandBlockVolumeRequest) Reset() {
*x = CommitExpandBlockVolumeRequest{}
mi := &file_volume_server_proto_msgTypes[122]
mi := &file_volume_server_proto_msgTypes[124]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6995,7 +7083,7 @@ func (x *CommitExpandBlockVolumeRequest) String() string {
func (*CommitExpandBlockVolumeRequest) ProtoMessage() {}
func (x *CommitExpandBlockVolumeRequest) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[122]
mi := &file_volume_server_proto_msgTypes[124]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7008,7 +7096,7 @@ func (x *CommitExpandBlockVolumeRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use CommitExpandBlockVolumeRequest.ProtoReflect.Descriptor instead.
func (*CommitExpandBlockVolumeRequest) Descriptor() ([]byte, []int) {
return file_volume_server_proto_rawDescGZIP(), []int{122}
return file_volume_server_proto_rawDescGZIP(), []int{124}
}
func (x *CommitExpandBlockVolumeRequest) GetName() string {
@@ -7034,7 +7122,7 @@ type CommitExpandBlockVolumeResponse struct {
func (x *CommitExpandBlockVolumeResponse) Reset() {
*x = CommitExpandBlockVolumeResponse{}
mi := &file_volume_server_proto_msgTypes[123]
mi := &file_volume_server_proto_msgTypes[125]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7046,7 +7134,7 @@ func (x *CommitExpandBlockVolumeResponse) String() string {
func (*CommitExpandBlockVolumeResponse) ProtoMessage() {}
func (x *CommitExpandBlockVolumeResponse) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[123]
mi := &file_volume_server_proto_msgTypes[125]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7059,7 +7147,7 @@ func (x *CommitExpandBlockVolumeResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use CommitExpandBlockVolumeResponse.ProtoReflect.Descriptor instead.
func (*CommitExpandBlockVolumeResponse) Descriptor() ([]byte, []int) {
return file_volume_server_proto_rawDescGZIP(), []int{123}
return file_volume_server_proto_rawDescGZIP(), []int{125}
}
func (x *CommitExpandBlockVolumeResponse) GetCapacityBytes() uint64 {
@@ -7079,7 +7167,7 @@ type CancelExpandBlockVolumeRequest struct {
func (x *CancelExpandBlockVolumeRequest) Reset() {
*x = CancelExpandBlockVolumeRequest{}
mi := &file_volume_server_proto_msgTypes[124]
mi := &file_volume_server_proto_msgTypes[126]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7091,7 +7179,7 @@ func (x *CancelExpandBlockVolumeRequest) String() string {
func (*CancelExpandBlockVolumeRequest) ProtoMessage() {}
func (x *CancelExpandBlockVolumeRequest) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[124]
mi := &file_volume_server_proto_msgTypes[126]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7104,7 +7192,7 @@ func (x *CancelExpandBlockVolumeRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use CancelExpandBlockVolumeRequest.ProtoReflect.Descriptor instead.
func (*CancelExpandBlockVolumeRequest) Descriptor() ([]byte, []int) {
return file_volume_server_proto_rawDescGZIP(), []int{124}
return file_volume_server_proto_rawDescGZIP(), []int{126}
}
func (x *CancelExpandBlockVolumeRequest) GetName() string {
@@ -7129,7 +7217,7 @@ type CancelExpandBlockVolumeResponse struct {
func (x *CancelExpandBlockVolumeResponse) Reset() {
*x = CancelExpandBlockVolumeResponse{}
mi := &file_volume_server_proto_msgTypes[125]
mi := &file_volume_server_proto_msgTypes[127]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7141,7 +7229,7 @@ func (x *CancelExpandBlockVolumeResponse) String() string {
func (*CancelExpandBlockVolumeResponse) ProtoMessage() {}
func (x *CancelExpandBlockVolumeResponse) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[125]
mi := &file_volume_server_proto_msgTypes[127]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7154,7 +7242,7 @@ func (x *CancelExpandBlockVolumeResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use CancelExpandBlockVolumeResponse.ProtoReflect.Descriptor instead.
func (*CancelExpandBlockVolumeResponse) Descriptor() ([]byte, []int) {
return file_volume_server_proto_rawDescGZIP(), []int{125}
return file_volume_server_proto_rawDescGZIP(), []int{127}
}
type FetchAndWriteNeedleRequest_Replica struct {
@@ -7168,7 +7256,7 @@ type FetchAndWriteNeedleRequest_Replica struct {
func (x *FetchAndWriteNeedleRequest_Replica) Reset() {
*x = FetchAndWriteNeedleRequest_Replica{}
mi := &file_volume_server_proto_msgTypes[126]
mi := &file_volume_server_proto_msgTypes[128]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7180,7 +7268,7 @@ func (x *FetchAndWriteNeedleRequest_Replica) String() string {
func (*FetchAndWriteNeedleRequest_Replica) ProtoMessage() {}
func (x *FetchAndWriteNeedleRequest_Replica) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[126]
mi := &file_volume_server_proto_msgTypes[128]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7228,7 +7316,7 @@ type QueryRequest_Filter struct {
func (x *QueryRequest_Filter) Reset() {
*x = QueryRequest_Filter{}
mi := &file_volume_server_proto_msgTypes[127]
mi := &file_volume_server_proto_msgTypes[129]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7240,7 +7328,7 @@ func (x *QueryRequest_Filter) String() string {
func (*QueryRequest_Filter) ProtoMessage() {}
func (x *QueryRequest_Filter) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[127]
mi := &file_volume_server_proto_msgTypes[129]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7290,7 +7378,7 @@ type QueryRequest_InputSerialization struct {
func (x *QueryRequest_InputSerialization) Reset() {
*x = QueryRequest_InputSerialization{}
mi := &file_volume_server_proto_msgTypes[128]
mi := &file_volume_server_proto_msgTypes[130]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7302,7 +7390,7 @@ func (x *QueryRequest_InputSerialization) String() string {
func (*QueryRequest_InputSerialization) ProtoMessage() {}
func (x *QueryRequest_InputSerialization) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[128]
mi := &file_volume_server_proto_msgTypes[130]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7356,7 +7444,7 @@ type QueryRequest_OutputSerialization struct {
func (x *QueryRequest_OutputSerialization) Reset() {
*x = QueryRequest_OutputSerialization{}
mi := &file_volume_server_proto_msgTypes[129]
mi := &file_volume_server_proto_msgTypes[131]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7368,7 +7456,7 @@ func (x *QueryRequest_OutputSerialization) String() string {
func (*QueryRequest_OutputSerialization) ProtoMessage() {}
func (x *QueryRequest_OutputSerialization) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[129]
mi := &file_volume_server_proto_msgTypes[131]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7414,7 +7502,7 @@ type QueryRequest_InputSerialization_CSVInput struct {
func (x *QueryRequest_InputSerialization_CSVInput) Reset() {
*x = QueryRequest_InputSerialization_CSVInput{}
mi := &file_volume_server_proto_msgTypes[130]
mi := &file_volume_server_proto_msgTypes[132]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7426,7 +7514,7 @@ func (x *QueryRequest_InputSerialization_CSVInput) String() string {
func (*QueryRequest_InputSerialization_CSVInput) ProtoMessage() {}
func (x *QueryRequest_InputSerialization_CSVInput) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[130]
mi := &file_volume_server_proto_msgTypes[132]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7500,7 +7588,7 @@ type QueryRequest_InputSerialization_JSONInput struct {
func (x *QueryRequest_InputSerialization_JSONInput) Reset() {
*x = QueryRequest_InputSerialization_JSONInput{}
mi := &file_volume_server_proto_msgTypes[131]
mi := &file_volume_server_proto_msgTypes[133]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7512,7 +7600,7 @@ func (x *QueryRequest_InputSerialization_JSONInput) String() string {
func (*QueryRequest_InputSerialization_JSONInput) ProtoMessage() {}
func (x *QueryRequest_InputSerialization_JSONInput) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[131]
mi := &file_volume_server_proto_msgTypes[133]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7543,7 +7631,7 @@ type QueryRequest_InputSerialization_ParquetInput struct {
func (x *QueryRequest_InputSerialization_ParquetInput) Reset() {
*x = QueryRequest_InputSerialization_ParquetInput{}
mi := &file_volume_server_proto_msgTypes[132]
mi := &file_volume_server_proto_msgTypes[134]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7555,7 +7643,7 @@ func (x *QueryRequest_InputSerialization_ParquetInput) String() string {
func (*QueryRequest_InputSerialization_ParquetInput) ProtoMessage() {}
func (x *QueryRequest_InputSerialization_ParquetInput) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[132]
mi := &file_volume_server_proto_msgTypes[134]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7584,7 +7672,7 @@ type QueryRequest_OutputSerialization_CSVOutput struct {
func (x *QueryRequest_OutputSerialization_CSVOutput) Reset() {
*x = QueryRequest_OutputSerialization_CSVOutput{}
mi := &file_volume_server_proto_msgTypes[133]
mi := &file_volume_server_proto_msgTypes[135]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7596,7 +7684,7 @@ func (x *QueryRequest_OutputSerialization_CSVOutput) String() string {
func (*QueryRequest_OutputSerialization_CSVOutput) ProtoMessage() {}
func (x *QueryRequest_OutputSerialization_CSVOutput) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[133]
mi := &file_volume_server_proto_msgTypes[135]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7656,7 +7744,7 @@ type QueryRequest_OutputSerialization_JSONOutput struct {
func (x *QueryRequest_OutputSerialization_JSONOutput) Reset() {
*x = QueryRequest_OutputSerialization_JSONOutput{}
mi := &file_volume_server_proto_msgTypes[134]
mi := &file_volume_server_proto_msgTypes[136]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7668,7 +7756,7 @@ func (x *QueryRequest_OutputSerialization_JSONOutput) String() string {
func (*QueryRequest_OutputSerialization_JSONOutput) ProtoMessage() {}
func (x *QueryRequest_OutputSerialization_JSONOutput) ProtoReflect() protoreflect.Message {
mi := &file_volume_server_proto_msgTypes[134]
mi := &file_volume_server_proto_msgTypes[136]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -8229,7 +8317,12 @@ const file_volume_server_proto_rawDesc = "" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1f\n" +
"\vsnapshot_id\x18\x02 \x01(\rR\n" +
"snapshotId\"\x1d\n" +
"\x1bDeleteBlockSnapshotResponse\"/\n" +
"\x1bDeleteBlockSnapshotResponse\"R\n" +
"\x1bRestoreBlockSnapshotRequest\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1f\n" +
"\vsnapshot_id\x18\x02 \x01(\rR\n" +
"snapshotId\"\x1e\n" +
"\x1cRestoreBlockSnapshotResponse\"/\n" +
"\x19ListBlockSnapshotsRequest\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\"_\n" +
"\x1aListBlockSnapshotsResponse\x12A\n" +
@@ -8263,7 +8356,7 @@ const file_volume_server_proto_rawDesc = "" +
"\aUNKNOWN\x10\x00\x12\t\n" +
"\x05INDEX\x10\x01\x12\b\n" +
"\x04FULL\x10\x02\x12\t\n" +
"\x05LOCAL\x10\x032\xe11\n" +
"\x05LOCAL\x10\x032\xda2\n" +
"\fVolumeServer\x12\\\n" +
"\vBatchDelete\x12$.volume_server_pb.BatchDeleteRequest\x1a%.volume_server_pb.BatchDeleteResponse\"\x00\x12n\n" +
"\x11VacuumVolumeCheck\x12*.volume_server_pb.VacuumVolumeCheckRequest\x1a+.volume_server_pb.VacuumVolumeCheckResponse\"\x00\x12v\n" +
@@ -8318,7 +8411,8 @@ const file_volume_server_proto_rawDesc = "" +
"\x1dVolumeServerDeleteBlockVolume\x126.volume_server_pb.VolumeServerDeleteBlockVolumeRequest\x1a7.volume_server_pb.VolumeServerDeleteBlockVolumeResponse\"\x00\x12t\n" +
"\x13SnapshotBlockVolume\x12,.volume_server_pb.SnapshotBlockVolumeRequest\x1a-.volume_server_pb.SnapshotBlockVolumeResponse\"\x00\x12t\n" +
"\x13DeleteBlockSnapshot\x12,.volume_server_pb.DeleteBlockSnapshotRequest\x1a-.volume_server_pb.DeleteBlockSnapshotResponse\"\x00\x12q\n" +
"\x12ListBlockSnapshots\x12+.volume_server_pb.ListBlockSnapshotsRequest\x1a,.volume_server_pb.ListBlockSnapshotsResponse\"\x00\x12n\n" +
"\x12ListBlockSnapshots\x12+.volume_server_pb.ListBlockSnapshotsRequest\x1a,.volume_server_pb.ListBlockSnapshotsResponse\"\x00\x12w\n" +
"\x14RestoreBlockSnapshot\x12-.volume_server_pb.RestoreBlockSnapshotRequest\x1a..volume_server_pb.RestoreBlockSnapshotResponse\"\x00\x12n\n" +
"\x11ExpandBlockVolume\x12*.volume_server_pb.ExpandBlockVolumeRequest\x1a+.volume_server_pb.ExpandBlockVolumeResponse\"\x00\x12\x83\x01\n" +
"\x18PrepareExpandBlockVolume\x121.volume_server_pb.PrepareExpandBlockVolumeRequest\x1a2.volume_server_pb.PrepareExpandBlockVolumeResponse\"\x00\x12\x80\x01\n" +
"\x17CommitExpandBlockVolume\x120.volume_server_pb.CommitExpandBlockVolumeRequest\x1a1.volume_server_pb.CommitExpandBlockVolumeResponse\"\x00\x12\x80\x01\n" +
@@ -8337,7 +8431,7 @@ func file_volume_server_proto_rawDescGZIP() []byte {
}
var file_volume_server_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_volume_server_proto_msgTypes = make([]protoimpl.MessageInfo, 135)
var file_volume_server_proto_msgTypes = make([]protoimpl.MessageInfo, 137)
var file_volume_server_proto_goTypes = []any{
(VolumeScrubMode)(0), // 0: volume_server_pb.VolumeScrubMode
(*VolumeServerState)(nil), // 1: volume_server_pb.VolumeServerState
@@ -8455,28 +8549,30 @@ var file_volume_server_proto_goTypes = []any{
(*SnapshotBlockVolumeResponse)(nil), // 113: volume_server_pb.SnapshotBlockVolumeResponse
(*DeleteBlockSnapshotRequest)(nil), // 114: volume_server_pb.DeleteBlockSnapshotRequest
(*DeleteBlockSnapshotResponse)(nil), // 115: volume_server_pb.DeleteBlockSnapshotResponse
(*ListBlockSnapshotsRequest)(nil), // 116: volume_server_pb.ListBlockSnapshotsRequest
(*ListBlockSnapshotsResponse)(nil), // 117: volume_server_pb.ListBlockSnapshotsResponse
(*BlockSnapshotInfo)(nil), // 118: volume_server_pb.BlockSnapshotInfo
(*ExpandBlockVolumeRequest)(nil), // 119: volume_server_pb.ExpandBlockVolumeRequest
(*ExpandBlockVolumeResponse)(nil), // 120: volume_server_pb.ExpandBlockVolumeResponse
(*PrepareExpandBlockVolumeRequest)(nil), // 121: volume_server_pb.PrepareExpandBlockVolumeRequest
(*PrepareExpandBlockVolumeResponse)(nil), // 122: volume_server_pb.PrepareExpandBlockVolumeResponse
(*CommitExpandBlockVolumeRequest)(nil), // 123: volume_server_pb.CommitExpandBlockVolumeRequest
(*CommitExpandBlockVolumeResponse)(nil), // 124: volume_server_pb.CommitExpandBlockVolumeResponse
(*CancelExpandBlockVolumeRequest)(nil), // 125: volume_server_pb.CancelExpandBlockVolumeRequest
(*CancelExpandBlockVolumeResponse)(nil), // 126: volume_server_pb.CancelExpandBlockVolumeResponse
(*FetchAndWriteNeedleRequest_Replica)(nil), // 127: volume_server_pb.FetchAndWriteNeedleRequest.Replica
(*QueryRequest_Filter)(nil), // 128: volume_server_pb.QueryRequest.Filter
(*QueryRequest_InputSerialization)(nil), // 129: volume_server_pb.QueryRequest.InputSerialization
(*QueryRequest_OutputSerialization)(nil), // 130: volume_server_pb.QueryRequest.OutputSerialization
(*QueryRequest_InputSerialization_CSVInput)(nil), // 131: volume_server_pb.QueryRequest.InputSerialization.CSVInput
(*QueryRequest_InputSerialization_JSONInput)(nil), // 132: volume_server_pb.QueryRequest.InputSerialization.JSONInput
(*QueryRequest_InputSerialization_ParquetInput)(nil), // 133: volume_server_pb.QueryRequest.InputSerialization.ParquetInput
(*QueryRequest_OutputSerialization_CSVOutput)(nil), // 134: volume_server_pb.QueryRequest.OutputSerialization.CSVOutput
(*QueryRequest_OutputSerialization_JSONOutput)(nil), // 135: volume_server_pb.QueryRequest.OutputSerialization.JSONOutput
(*remote_pb.RemoteConf)(nil), // 136: remote_pb.RemoteConf
(*remote_pb.RemoteStorageLocation)(nil), // 137: remote_pb.RemoteStorageLocation
(*RestoreBlockSnapshotRequest)(nil), // 116: volume_server_pb.RestoreBlockSnapshotRequest
(*RestoreBlockSnapshotResponse)(nil), // 117: volume_server_pb.RestoreBlockSnapshotResponse
(*ListBlockSnapshotsRequest)(nil), // 118: volume_server_pb.ListBlockSnapshotsRequest
(*ListBlockSnapshotsResponse)(nil), // 119: volume_server_pb.ListBlockSnapshotsResponse
(*BlockSnapshotInfo)(nil), // 120: volume_server_pb.BlockSnapshotInfo
(*ExpandBlockVolumeRequest)(nil), // 121: volume_server_pb.ExpandBlockVolumeRequest
(*ExpandBlockVolumeResponse)(nil), // 122: volume_server_pb.ExpandBlockVolumeResponse
(*PrepareExpandBlockVolumeRequest)(nil), // 123: volume_server_pb.PrepareExpandBlockVolumeRequest
(*PrepareExpandBlockVolumeResponse)(nil), // 124: volume_server_pb.PrepareExpandBlockVolumeResponse
(*CommitExpandBlockVolumeRequest)(nil), // 125: volume_server_pb.CommitExpandBlockVolumeRequest
(*CommitExpandBlockVolumeResponse)(nil), // 126: volume_server_pb.CommitExpandBlockVolumeResponse
(*CancelExpandBlockVolumeRequest)(nil), // 127: volume_server_pb.CancelExpandBlockVolumeRequest
(*CancelExpandBlockVolumeResponse)(nil), // 128: volume_server_pb.CancelExpandBlockVolumeResponse
(*FetchAndWriteNeedleRequest_Replica)(nil), // 129: volume_server_pb.FetchAndWriteNeedleRequest.Replica
(*QueryRequest_Filter)(nil), // 130: volume_server_pb.QueryRequest.Filter
(*QueryRequest_InputSerialization)(nil), // 131: volume_server_pb.QueryRequest.InputSerialization
(*QueryRequest_OutputSerialization)(nil), // 132: volume_server_pb.QueryRequest.OutputSerialization
(*QueryRequest_InputSerialization_CSVInput)(nil), // 133: volume_server_pb.QueryRequest.InputSerialization.CSVInput
(*QueryRequest_InputSerialization_JSONInput)(nil), // 134: volume_server_pb.QueryRequest.InputSerialization.JSONInput
(*QueryRequest_InputSerialization_ParquetInput)(nil), // 135: volume_server_pb.QueryRequest.InputSerialization.ParquetInput
(*QueryRequest_OutputSerialization_CSVOutput)(nil), // 136: volume_server_pb.QueryRequest.OutputSerialization.CSVOutput
(*QueryRequest_OutputSerialization_JSONOutput)(nil), // 137: volume_server_pb.QueryRequest.OutputSerialization.JSONOutput
(*remote_pb.RemoteConf)(nil), // 138: remote_pb.RemoteConf
(*remote_pb.RemoteStorageLocation)(nil), // 139: remote_pb.RemoteStorageLocation
}
var file_volume_server_proto_depIdxs = []int32{
4, // 0: volume_server_pb.BatchDeleteResponse.results:type_name -> volume_server_pb.DeleteResult
@@ -8492,21 +8588,21 @@ var file_volume_server_proto_depIdxs = []int32{
82, // 10: volume_server_pb.VolumeServerStatusResponse.disk_statuses:type_name -> volume_server_pb.DiskStatus
83, // 11: volume_server_pb.VolumeServerStatusResponse.memory_status:type_name -> volume_server_pb.MemStatus
1, // 12: volume_server_pb.VolumeServerStatusResponse.state:type_name -> volume_server_pb.VolumeServerState
127, // 13: volume_server_pb.FetchAndWriteNeedleRequest.replicas:type_name -> volume_server_pb.FetchAndWriteNeedleRequest.Replica
136, // 14: volume_server_pb.FetchAndWriteNeedleRequest.remote_conf:type_name -> remote_pb.RemoteConf
137, // 15: volume_server_pb.FetchAndWriteNeedleRequest.remote_location:type_name -> remote_pb.RemoteStorageLocation
129, // 13: volume_server_pb.FetchAndWriteNeedleRequest.replicas:type_name -> volume_server_pb.FetchAndWriteNeedleRequest.Replica
138, // 14: volume_server_pb.FetchAndWriteNeedleRequest.remote_conf:type_name -> remote_pb.RemoteConf
139, // 15: volume_server_pb.FetchAndWriteNeedleRequest.remote_location:type_name -> remote_pb.RemoteStorageLocation
0, // 16: volume_server_pb.ScrubVolumeRequest.mode:type_name -> volume_server_pb.VolumeScrubMode
0, // 17: volume_server_pb.ScrubEcVolumeRequest.mode:type_name -> volume_server_pb.VolumeScrubMode
79, // 18: volume_server_pb.ScrubEcVolumeResponse.broken_shard_infos:type_name -> volume_server_pb.EcShardInfo
128, // 19: volume_server_pb.QueryRequest.filter:type_name -> volume_server_pb.QueryRequest.Filter
129, // 20: volume_server_pb.QueryRequest.input_serialization:type_name -> volume_server_pb.QueryRequest.InputSerialization
130, // 21: volume_server_pb.QueryRequest.output_serialization:type_name -> volume_server_pb.QueryRequest.OutputSerialization
118, // 22: volume_server_pb.ListBlockSnapshotsResponse.snapshots:type_name -> volume_server_pb.BlockSnapshotInfo
131, // 23: volume_server_pb.QueryRequest.InputSerialization.csv_input:type_name -> volume_server_pb.QueryRequest.InputSerialization.CSVInput
132, // 24: volume_server_pb.QueryRequest.InputSerialization.json_input:type_name -> volume_server_pb.QueryRequest.InputSerialization.JSONInput
133, // 25: volume_server_pb.QueryRequest.InputSerialization.parquet_input:type_name -> volume_server_pb.QueryRequest.InputSerialization.ParquetInput
134, // 26: volume_server_pb.QueryRequest.OutputSerialization.csv_output:type_name -> volume_server_pb.QueryRequest.OutputSerialization.CSVOutput
135, // 27: volume_server_pb.QueryRequest.OutputSerialization.json_output:type_name -> volume_server_pb.QueryRequest.OutputSerialization.JSONOutput
130, // 19: volume_server_pb.QueryRequest.filter:type_name -> volume_server_pb.QueryRequest.Filter
131, // 20: volume_server_pb.QueryRequest.input_serialization:type_name -> volume_server_pb.QueryRequest.InputSerialization
132, // 21: volume_server_pb.QueryRequest.output_serialization:type_name -> volume_server_pb.QueryRequest.OutputSerialization
120, // 22: volume_server_pb.ListBlockSnapshotsResponse.snapshots:type_name -> volume_server_pb.BlockSnapshotInfo
133, // 23: volume_server_pb.QueryRequest.InputSerialization.csv_input:type_name -> volume_server_pb.QueryRequest.InputSerialization.CSVInput
134, // 24: volume_server_pb.QueryRequest.InputSerialization.json_input:type_name -> volume_server_pb.QueryRequest.InputSerialization.JSONInput
135, // 25: volume_server_pb.QueryRequest.InputSerialization.parquet_input:type_name -> volume_server_pb.QueryRequest.InputSerialization.ParquetInput
136, // 26: volume_server_pb.QueryRequest.OutputSerialization.csv_output:type_name -> volume_server_pb.QueryRequest.OutputSerialization.CSVOutput
137, // 27: volume_server_pb.QueryRequest.OutputSerialization.json_output:type_name -> volume_server_pb.QueryRequest.OutputSerialization.JSONOutput
2, // 28: volume_server_pb.VolumeServer.BatchDelete:input_type -> volume_server_pb.BatchDeleteRequest
6, // 29: volume_server_pb.VolumeServer.VacuumVolumeCheck:input_type -> volume_server_pb.VacuumVolumeCheckRequest
8, // 30: volume_server_pb.VolumeServer.VacuumVolumeCompact:input_type -> volume_server_pb.VacuumVolumeCompactRequest
@@ -8559,70 +8655,72 @@ var file_volume_server_proto_depIdxs = []int32{
110, // 77: volume_server_pb.VolumeServer.VolumeServerDeleteBlockVolume:input_type -> volume_server_pb.VolumeServerDeleteBlockVolumeRequest
112, // 78: volume_server_pb.VolumeServer.SnapshotBlockVolume:input_type -> volume_server_pb.SnapshotBlockVolumeRequest
114, // 79: volume_server_pb.VolumeServer.DeleteBlockSnapshot:input_type -> volume_server_pb.DeleteBlockSnapshotRequest
116, // 80: volume_server_pb.VolumeServer.ListBlockSnapshots:input_type -> volume_server_pb.ListBlockSnapshotsRequest
119, // 81: volume_server_pb.VolumeServer.ExpandBlockVolume:input_type -> volume_server_pb.ExpandBlockVolumeRequest
121, // 82: volume_server_pb.VolumeServer.PrepareExpandBlockVolume:input_type -> volume_server_pb.PrepareExpandBlockVolumeRequest
123, // 83: volume_server_pb.VolumeServer.CommitExpandBlockVolume:input_type -> volume_server_pb.CommitExpandBlockVolumeRequest
125, // 84: volume_server_pb.VolumeServer.CancelExpandBlockVolume:input_type -> volume_server_pb.CancelExpandBlockVolumeRequest
3, // 85: volume_server_pb.VolumeServer.BatchDelete:output_type -> volume_server_pb.BatchDeleteResponse
7, // 86: volume_server_pb.VolumeServer.VacuumVolumeCheck:output_type -> volume_server_pb.VacuumVolumeCheckResponse
9, // 87: volume_server_pb.VolumeServer.VacuumVolumeCompact:output_type -> volume_server_pb.VacuumVolumeCompactResponse
11, // 88: volume_server_pb.VolumeServer.VacuumVolumeCommit:output_type -> volume_server_pb.VacuumVolumeCommitResponse
13, // 89: volume_server_pb.VolumeServer.VacuumVolumeCleanup:output_type -> volume_server_pb.VacuumVolumeCleanupResponse
15, // 90: volume_server_pb.VolumeServer.DeleteCollection:output_type -> volume_server_pb.DeleteCollectionResponse
17, // 91: volume_server_pb.VolumeServer.AllocateVolume:output_type -> volume_server_pb.AllocateVolumeResponse
19, // 92: volume_server_pb.VolumeServer.VolumeSyncStatus:output_type -> volume_server_pb.VolumeSyncStatusResponse
21, // 93: volume_server_pb.VolumeServer.VolumeIncrementalCopy:output_type -> volume_server_pb.VolumeIncrementalCopyResponse
23, // 94: volume_server_pb.VolumeServer.VolumeMount:output_type -> volume_server_pb.VolumeMountResponse
25, // 95: volume_server_pb.VolumeServer.VolumeUnmount:output_type -> volume_server_pb.VolumeUnmountResponse
27, // 96: volume_server_pb.VolumeServer.VolumeDelete:output_type -> volume_server_pb.VolumeDeleteResponse
29, // 97: volume_server_pb.VolumeServer.VolumeMarkReadonly:output_type -> volume_server_pb.VolumeMarkReadonlyResponse
31, // 98: volume_server_pb.VolumeServer.VolumeMarkWritable:output_type -> volume_server_pb.VolumeMarkWritableResponse
33, // 99: volume_server_pb.VolumeServer.VolumeConfigure:output_type -> volume_server_pb.VolumeConfigureResponse
35, // 100: volume_server_pb.VolumeServer.VolumeStatus:output_type -> volume_server_pb.VolumeStatusResponse
37, // 101: volume_server_pb.VolumeServer.GetState:output_type -> volume_server_pb.GetStateResponse
39, // 102: volume_server_pb.VolumeServer.SetState:output_type -> volume_server_pb.SetStateResponse
41, // 103: volume_server_pb.VolumeServer.VolumeCopy:output_type -> volume_server_pb.VolumeCopyResponse
81, // 104: volume_server_pb.VolumeServer.ReadVolumeFileStatus:output_type -> volume_server_pb.ReadVolumeFileStatusResponse
43, // 105: volume_server_pb.VolumeServer.CopyFile:output_type -> volume_server_pb.CopyFileResponse
46, // 106: volume_server_pb.VolumeServer.ReceiveFile:output_type -> volume_server_pb.ReceiveFileResponse
48, // 107: volume_server_pb.VolumeServer.ReadNeedleBlob:output_type -> volume_server_pb.ReadNeedleBlobResponse
50, // 108: volume_server_pb.VolumeServer.ReadNeedleMeta:output_type -> volume_server_pb.ReadNeedleMetaResponse
52, // 109: volume_server_pb.VolumeServer.WriteNeedleBlob:output_type -> volume_server_pb.WriteNeedleBlobResponse
54, // 110: volume_server_pb.VolumeServer.ReadAllNeedles:output_type -> volume_server_pb.ReadAllNeedlesResponse
56, // 111: volume_server_pb.VolumeServer.VolumeTailSender:output_type -> volume_server_pb.VolumeTailSenderResponse
58, // 112: volume_server_pb.VolumeServer.VolumeTailReceiver:output_type -> volume_server_pb.VolumeTailReceiverResponse
60, // 113: volume_server_pb.VolumeServer.VolumeEcShardsGenerate:output_type -> volume_server_pb.VolumeEcShardsGenerateResponse
62, // 114: volume_server_pb.VolumeServer.VolumeEcShardsRebuild:output_type -> volume_server_pb.VolumeEcShardsRebuildResponse
64, // 115: volume_server_pb.VolumeServer.VolumeEcShardsCopy:output_type -> volume_server_pb.VolumeEcShardsCopyResponse
66, // 116: volume_server_pb.VolumeServer.VolumeEcShardsDelete:output_type -> volume_server_pb.VolumeEcShardsDeleteResponse
68, // 117: volume_server_pb.VolumeServer.VolumeEcShardsMount:output_type -> volume_server_pb.VolumeEcShardsMountResponse
70, // 118: volume_server_pb.VolumeServer.VolumeEcShardsUnmount:output_type -> volume_server_pb.VolumeEcShardsUnmountResponse
72, // 119: volume_server_pb.VolumeServer.VolumeEcShardRead:output_type -> volume_server_pb.VolumeEcShardReadResponse
74, // 120: volume_server_pb.VolumeServer.VolumeEcBlobDelete:output_type -> volume_server_pb.VolumeEcBlobDeleteResponse
76, // 121: volume_server_pb.VolumeServer.VolumeEcShardsToVolume:output_type -> volume_server_pb.VolumeEcShardsToVolumeResponse
78, // 122: volume_server_pb.VolumeServer.VolumeEcShardsInfo:output_type -> volume_server_pb.VolumeEcShardsInfoResponse
89, // 123: volume_server_pb.VolumeServer.VolumeTierMoveDatToRemote:output_type -> volume_server_pb.VolumeTierMoveDatToRemoteResponse
91, // 124: volume_server_pb.VolumeServer.VolumeTierMoveDatFromRemote:output_type -> volume_server_pb.VolumeTierMoveDatFromRemoteResponse
93, // 125: volume_server_pb.VolumeServer.VolumeServerStatus:output_type -> volume_server_pb.VolumeServerStatusResponse
95, // 126: volume_server_pb.VolumeServer.VolumeServerLeave:output_type -> volume_server_pb.VolumeServerLeaveResponse
97, // 127: volume_server_pb.VolumeServer.FetchAndWriteNeedle:output_type -> volume_server_pb.FetchAndWriteNeedleResponse
99, // 128: volume_server_pb.VolumeServer.ScrubVolume:output_type -> volume_server_pb.ScrubVolumeResponse
101, // 129: volume_server_pb.VolumeServer.ScrubEcVolume:output_type -> volume_server_pb.ScrubEcVolumeResponse
103, // 130: volume_server_pb.VolumeServer.Query:output_type -> volume_server_pb.QueriedStripe
105, // 131: volume_server_pb.VolumeServer.VolumeNeedleStatus:output_type -> volume_server_pb.VolumeNeedleStatusResponse
107, // 132: volume_server_pb.VolumeServer.Ping:output_type -> volume_server_pb.PingResponse
109, // 133: volume_server_pb.VolumeServer.AllocateBlockVolume:output_type -> volume_server_pb.AllocateBlockVolumeResponse
111, // 134: volume_server_pb.VolumeServer.VolumeServerDeleteBlockVolume:output_type -> volume_server_pb.VolumeServerDeleteBlockVolumeResponse
113, // 135: volume_server_pb.VolumeServer.SnapshotBlockVolume:output_type -> volume_server_pb.SnapshotBlockVolumeResponse
115, // 136: volume_server_pb.VolumeServer.DeleteBlockSnapshot:output_type -> volume_server_pb.DeleteBlockSnapshotResponse
117, // 137: volume_server_pb.VolumeServer.ListBlockSnapshots:output_type -> volume_server_pb.ListBlockSnapshotsResponse
120, // 138: volume_server_pb.VolumeServer.ExpandBlockVolume:output_type -> volume_server_pb.ExpandBlockVolumeResponse
122, // 139: volume_server_pb.VolumeServer.PrepareExpandBlockVolume:output_type -> volume_server_pb.PrepareExpandBlockVolumeResponse
124, // 140: volume_server_pb.VolumeServer.CommitExpandBlockVolume:output_type -> volume_server_pb.CommitExpandBlockVolumeResponse
126, // 141: volume_server_pb.VolumeServer.CancelExpandBlockVolume:output_type -> volume_server_pb.CancelExpandBlockVolumeResponse
85, // [85:142] is the sub-list for method output_type
28, // [28:85] is the sub-list for method input_type
118, // 80: volume_server_pb.VolumeServer.ListBlockSnapshots:input_type -> volume_server_pb.ListBlockSnapshotsRequest
116, // 81: volume_server_pb.VolumeServer.RestoreBlockSnapshot:input_type -> volume_server_pb.RestoreBlockSnapshotRequest
121, // 82: volume_server_pb.VolumeServer.ExpandBlockVolume:input_type -> volume_server_pb.ExpandBlockVolumeRequest
123, // 83: volume_server_pb.VolumeServer.PrepareExpandBlockVolume:input_type -> volume_server_pb.PrepareExpandBlockVolumeRequest
125, // 84: volume_server_pb.VolumeServer.CommitExpandBlockVolume:input_type -> volume_server_pb.CommitExpandBlockVolumeRequest
127, // 85: volume_server_pb.VolumeServer.CancelExpandBlockVolume:input_type -> volume_server_pb.CancelExpandBlockVolumeRequest
3, // 86: volume_server_pb.VolumeServer.BatchDelete:output_type -> volume_server_pb.BatchDeleteResponse
7, // 87: volume_server_pb.VolumeServer.VacuumVolumeCheck:output_type -> volume_server_pb.VacuumVolumeCheckResponse
9, // 88: volume_server_pb.VolumeServer.VacuumVolumeCompact:output_type -> volume_server_pb.VacuumVolumeCompactResponse
11, // 89: volume_server_pb.VolumeServer.VacuumVolumeCommit:output_type -> volume_server_pb.VacuumVolumeCommitResponse
13, // 90: volume_server_pb.VolumeServer.VacuumVolumeCleanup:output_type -> volume_server_pb.VacuumVolumeCleanupResponse
15, // 91: volume_server_pb.VolumeServer.DeleteCollection:output_type -> volume_server_pb.DeleteCollectionResponse
17, // 92: volume_server_pb.VolumeServer.AllocateVolume:output_type -> volume_server_pb.AllocateVolumeResponse
19, // 93: volume_server_pb.VolumeServer.VolumeSyncStatus:output_type -> volume_server_pb.VolumeSyncStatusResponse
21, // 94: volume_server_pb.VolumeServer.VolumeIncrementalCopy:output_type -> volume_server_pb.VolumeIncrementalCopyResponse
23, // 95: volume_server_pb.VolumeServer.VolumeMount:output_type -> volume_server_pb.VolumeMountResponse
25, // 96: volume_server_pb.VolumeServer.VolumeUnmount:output_type -> volume_server_pb.VolumeUnmountResponse
27, // 97: volume_server_pb.VolumeServer.VolumeDelete:output_type -> volume_server_pb.VolumeDeleteResponse
29, // 98: volume_server_pb.VolumeServer.VolumeMarkReadonly:output_type -> volume_server_pb.VolumeMarkReadonlyResponse
31, // 99: volume_server_pb.VolumeServer.VolumeMarkWritable:output_type -> volume_server_pb.VolumeMarkWritableResponse
33, // 100: volume_server_pb.VolumeServer.VolumeConfigure:output_type -> volume_server_pb.VolumeConfigureResponse
35, // 101: volume_server_pb.VolumeServer.VolumeStatus:output_type -> volume_server_pb.VolumeStatusResponse
37, // 102: volume_server_pb.VolumeServer.GetState:output_type -> volume_server_pb.GetStateResponse
39, // 103: volume_server_pb.VolumeServer.SetState:output_type -> volume_server_pb.SetStateResponse
41, // 104: volume_server_pb.VolumeServer.VolumeCopy:output_type -> volume_server_pb.VolumeCopyResponse
81, // 105: volume_server_pb.VolumeServer.ReadVolumeFileStatus:output_type -> volume_server_pb.ReadVolumeFileStatusResponse
43, // 106: volume_server_pb.VolumeServer.CopyFile:output_type -> volume_server_pb.CopyFileResponse
46, // 107: volume_server_pb.VolumeServer.ReceiveFile:output_type -> volume_server_pb.ReceiveFileResponse
48, // 108: volume_server_pb.VolumeServer.ReadNeedleBlob:output_type -> volume_server_pb.ReadNeedleBlobResponse
50, // 109: volume_server_pb.VolumeServer.ReadNeedleMeta:output_type -> volume_server_pb.ReadNeedleMetaResponse
52, // 110: volume_server_pb.VolumeServer.WriteNeedleBlob:output_type -> volume_server_pb.WriteNeedleBlobResponse
54, // 111: volume_server_pb.VolumeServer.ReadAllNeedles:output_type -> volume_server_pb.ReadAllNeedlesResponse
56, // 112: volume_server_pb.VolumeServer.VolumeTailSender:output_type -> volume_server_pb.VolumeTailSenderResponse
58, // 113: volume_server_pb.VolumeServer.VolumeTailReceiver:output_type -> volume_server_pb.VolumeTailReceiverResponse
60, // 114: volume_server_pb.VolumeServer.VolumeEcShardsGenerate:output_type -> volume_server_pb.VolumeEcShardsGenerateResponse
62, // 115: volume_server_pb.VolumeServer.VolumeEcShardsRebuild:output_type -> volume_server_pb.VolumeEcShardsRebuildResponse
64, // 116: volume_server_pb.VolumeServer.VolumeEcShardsCopy:output_type -> volume_server_pb.VolumeEcShardsCopyResponse
66, // 117: volume_server_pb.VolumeServer.VolumeEcShardsDelete:output_type -> volume_server_pb.VolumeEcShardsDeleteResponse
68, // 118: volume_server_pb.VolumeServer.VolumeEcShardsMount:output_type -> volume_server_pb.VolumeEcShardsMountResponse
70, // 119: volume_server_pb.VolumeServer.VolumeEcShardsUnmount:output_type -> volume_server_pb.VolumeEcShardsUnmountResponse
72, // 120: volume_server_pb.VolumeServer.VolumeEcShardRead:output_type -> volume_server_pb.VolumeEcShardReadResponse
74, // 121: volume_server_pb.VolumeServer.VolumeEcBlobDelete:output_type -> volume_server_pb.VolumeEcBlobDeleteResponse
76, // 122: volume_server_pb.VolumeServer.VolumeEcShardsToVolume:output_type -> volume_server_pb.VolumeEcShardsToVolumeResponse
78, // 123: volume_server_pb.VolumeServer.VolumeEcShardsInfo:output_type -> volume_server_pb.VolumeEcShardsInfoResponse
89, // 124: volume_server_pb.VolumeServer.VolumeTierMoveDatToRemote:output_type -> volume_server_pb.VolumeTierMoveDatToRemoteResponse
91, // 125: volume_server_pb.VolumeServer.VolumeTierMoveDatFromRemote:output_type -> volume_server_pb.VolumeTierMoveDatFromRemoteResponse
93, // 126: volume_server_pb.VolumeServer.VolumeServerStatus:output_type -> volume_server_pb.VolumeServerStatusResponse
95, // 127: volume_server_pb.VolumeServer.VolumeServerLeave:output_type -> volume_server_pb.VolumeServerLeaveResponse
97, // 128: volume_server_pb.VolumeServer.FetchAndWriteNeedle:output_type -> volume_server_pb.FetchAndWriteNeedleResponse
99, // 129: volume_server_pb.VolumeServer.ScrubVolume:output_type -> volume_server_pb.ScrubVolumeResponse
101, // 130: volume_server_pb.VolumeServer.ScrubEcVolume:output_type -> volume_server_pb.ScrubEcVolumeResponse
103, // 131: volume_server_pb.VolumeServer.Query:output_type -> volume_server_pb.QueriedStripe
105, // 132: volume_server_pb.VolumeServer.VolumeNeedleStatus:output_type -> volume_server_pb.VolumeNeedleStatusResponse
107, // 133: volume_server_pb.VolumeServer.Ping:output_type -> volume_server_pb.PingResponse
109, // 134: volume_server_pb.VolumeServer.AllocateBlockVolume:output_type -> volume_server_pb.AllocateBlockVolumeResponse
111, // 135: volume_server_pb.VolumeServer.VolumeServerDeleteBlockVolume:output_type -> volume_server_pb.VolumeServerDeleteBlockVolumeResponse
113, // 136: volume_server_pb.VolumeServer.SnapshotBlockVolume:output_type -> volume_server_pb.SnapshotBlockVolumeResponse
115, // 137: volume_server_pb.VolumeServer.DeleteBlockSnapshot:output_type -> volume_server_pb.DeleteBlockSnapshotResponse
119, // 138: volume_server_pb.VolumeServer.ListBlockSnapshots:output_type -> volume_server_pb.ListBlockSnapshotsResponse
117, // 139: volume_server_pb.VolumeServer.RestoreBlockSnapshot:output_type -> volume_server_pb.RestoreBlockSnapshotResponse
122, // 140: volume_server_pb.VolumeServer.ExpandBlockVolume:output_type -> volume_server_pb.ExpandBlockVolumeResponse
124, // 141: volume_server_pb.VolumeServer.PrepareExpandBlockVolume:output_type -> volume_server_pb.PrepareExpandBlockVolumeResponse
126, // 142: volume_server_pb.VolumeServer.CommitExpandBlockVolume:output_type -> volume_server_pb.CommitExpandBlockVolumeResponse
128, // 143: volume_server_pb.VolumeServer.CancelExpandBlockVolume:output_type -> volume_server_pb.CancelExpandBlockVolumeResponse
86, // [86:144] is the sub-list for method output_type
28, // [28:86] is the sub-list for method input_type
28, // [28:28] is the sub-list for extension type_name
28, // [28:28] is the sub-list for extension extendee
0, // [0:28] is the sub-list for field type_name
@@ -8643,7 +8741,7 @@ func file_volume_server_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_volume_server_proto_rawDesc), len(file_volume_server_proto_rawDesc)),
NumEnums: 1,
NumMessages: 135,
NumMessages: 137,
NumExtensions: 0,
NumServices: 1,
},
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc v5.29.5
// - protoc v3.21.12
// source: volume_server.proto
package volume_server_pb
@@ -72,6 +72,7 @@ const (
VolumeServer_SnapshotBlockVolume_FullMethodName = "/volume_server_pb.VolumeServer/SnapshotBlockVolume"
VolumeServer_DeleteBlockSnapshot_FullMethodName = "/volume_server_pb.VolumeServer/DeleteBlockSnapshot"
VolumeServer_ListBlockSnapshots_FullMethodName = "/volume_server_pb.VolumeServer/ListBlockSnapshots"
VolumeServer_RestoreBlockSnapshot_FullMethodName = "/volume_server_pb.VolumeServer/RestoreBlockSnapshot"
VolumeServer_ExpandBlockVolume_FullMethodName = "/volume_server_pb.VolumeServer/ExpandBlockVolume"
VolumeServer_PrepareExpandBlockVolume_FullMethodName = "/volume_server_pb.VolumeServer/PrepareExpandBlockVolume"
VolumeServer_CommitExpandBlockVolume_FullMethodName = "/volume_server_pb.VolumeServer/CommitExpandBlockVolume"
@@ -82,7 +83,7 @@ const (
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type VolumeServerClient interface {
// Experts only: takes multiple fid parameters. This function does not propagate deletes to replicas.
//Experts only: takes multiple fid parameters. This function does not propagate deletes to replicas.
BatchDelete(ctx context.Context, in *BatchDeleteRequest, opts ...grpc.CallOption) (*BatchDeleteResponse, error)
VacuumVolumeCheck(ctx context.Context, in *VacuumVolumeCheckRequest, opts ...grpc.CallOption) (*VacuumVolumeCheckResponse, error)
VacuumVolumeCompact(ctx context.Context, in *VacuumVolumeCompactRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[VacuumVolumeCompactResponse], error)
@@ -143,6 +144,7 @@ type VolumeServerClient interface {
SnapshotBlockVolume(ctx context.Context, in *SnapshotBlockVolumeRequest, opts ...grpc.CallOption) (*SnapshotBlockVolumeResponse, error)
DeleteBlockSnapshot(ctx context.Context, in *DeleteBlockSnapshotRequest, opts ...grpc.CallOption) (*DeleteBlockSnapshotResponse, error)
ListBlockSnapshots(ctx context.Context, in *ListBlockSnapshotsRequest, opts ...grpc.CallOption) (*ListBlockSnapshotsResponse, error)
RestoreBlockSnapshot(ctx context.Context, in *RestoreBlockSnapshotRequest, opts ...grpc.CallOption) (*RestoreBlockSnapshotResponse, error)
ExpandBlockVolume(ctx context.Context, in *ExpandBlockVolumeRequest, opts ...grpc.CallOption) (*ExpandBlockVolumeResponse, error)
PrepareExpandBlockVolume(ctx context.Context, in *PrepareExpandBlockVolumeRequest, opts ...grpc.CallOption) (*PrepareExpandBlockVolumeResponse, error)
CommitExpandBlockVolume(ctx context.Context, in *CommitExpandBlockVolumeRequest, opts ...grpc.CallOption) (*CommitExpandBlockVolumeResponse, error)
@@ -780,6 +782,16 @@ func (c *volumeServerClient) ListBlockSnapshots(ctx context.Context, in *ListBlo
return out, nil
}
func (c *volumeServerClient) RestoreBlockSnapshot(ctx context.Context, in *RestoreBlockSnapshotRequest, opts ...grpc.CallOption) (*RestoreBlockSnapshotResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(RestoreBlockSnapshotResponse)
err := c.cc.Invoke(ctx, VolumeServer_RestoreBlockSnapshot_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *volumeServerClient) ExpandBlockVolume(ctx context.Context, in *ExpandBlockVolumeRequest, opts ...grpc.CallOption) (*ExpandBlockVolumeResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ExpandBlockVolumeResponse)
@@ -824,7 +836,7 @@ func (c *volumeServerClient) CancelExpandBlockVolume(ctx context.Context, in *Ca
// All implementations must embed UnimplementedVolumeServerServer
// for forward compatibility.
type VolumeServerServer interface {
// Experts only: takes multiple fid parameters. This function does not propagate deletes to replicas.
//Experts only: takes multiple fid parameters. This function does not propagate deletes to replicas.
BatchDelete(context.Context, *BatchDeleteRequest) (*BatchDeleteResponse, error)
VacuumVolumeCheck(context.Context, *VacuumVolumeCheckRequest) (*VacuumVolumeCheckResponse, error)
VacuumVolumeCompact(*VacuumVolumeCompactRequest, grpc.ServerStreamingServer[VacuumVolumeCompactResponse]) error
@@ -885,6 +897,7 @@ type VolumeServerServer interface {
SnapshotBlockVolume(context.Context, *SnapshotBlockVolumeRequest) (*SnapshotBlockVolumeResponse, error)
DeleteBlockSnapshot(context.Context, *DeleteBlockSnapshotRequest) (*DeleteBlockSnapshotResponse, error)
ListBlockSnapshots(context.Context, *ListBlockSnapshotsRequest) (*ListBlockSnapshotsResponse, error)
RestoreBlockSnapshot(context.Context, *RestoreBlockSnapshotRequest) (*RestoreBlockSnapshotResponse, error)
ExpandBlockVolume(context.Context, *ExpandBlockVolumeRequest) (*ExpandBlockVolumeResponse, error)
PrepareExpandBlockVolume(context.Context, *PrepareExpandBlockVolumeRequest) (*PrepareExpandBlockVolumeResponse, error)
CommitExpandBlockVolume(context.Context, *CommitExpandBlockVolumeRequest) (*CommitExpandBlockVolumeResponse, error)
@@ -1058,6 +1071,9 @@ func (UnimplementedVolumeServerServer) DeleteBlockSnapshot(context.Context, *Del
func (UnimplementedVolumeServerServer) ListBlockSnapshots(context.Context, *ListBlockSnapshotsRequest) (*ListBlockSnapshotsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListBlockSnapshots not implemented")
}
func (UnimplementedVolumeServerServer) RestoreBlockSnapshot(context.Context, *RestoreBlockSnapshotRequest) (*RestoreBlockSnapshotResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RestoreBlockSnapshot not implemented")
}
func (UnimplementedVolumeServerServer) ExpandBlockVolume(context.Context, *ExpandBlockVolumeRequest) (*ExpandBlockVolumeResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ExpandBlockVolume not implemented")
}
@@ -1964,6 +1980,24 @@ func _VolumeServer_ListBlockSnapshots_Handler(srv interface{}, ctx context.Conte
return interceptor(ctx, in, info, handler)
}
func _VolumeServer_RestoreBlockSnapshot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RestoreBlockSnapshotRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(VolumeServerServer).RestoreBlockSnapshot(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: VolumeServer_RestoreBlockSnapshot_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(VolumeServerServer).RestoreBlockSnapshot(ctx, req.(*RestoreBlockSnapshotRequest))
}
return interceptor(ctx, in, info, handler)
}
func _VolumeServer_ExpandBlockVolume_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ExpandBlockVolumeRequest)
if err := dec(in); err != nil {
@@ -2211,6 +2245,10 @@ var VolumeServer_ServiceDesc = grpc.ServiceDesc{
MethodName: "ListBlockSnapshots",
Handler: _VolumeServer_ListBlockSnapshots_Handler,
},
{
MethodName: "RestoreBlockSnapshot",
Handler: _VolumeServer_RestoreBlockSnapshot_Handler,
},
{
MethodName: "ExpandBlockVolume",
Handler: _VolumeServer_ExpandBlockVolume_Handler,
@@ -0,0 +1,166 @@
package weed_server
import (
"sync/atomic"
"testing"
"time"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol"
)
// ============================================================
// Phase 09 P4: Adversarial tests for RecoveryManager
// ============================================================
// --- Adversarial 1: Rapid triple supersede ---
func TestAdversarial_P4_RapidTripleSupersede(t *testing.T) {
bs, volPath := createTestBlockServiceWithVol(t)
rm := bs.v2Recovery
// Write data so recovery has work.
bs.blockStore.WithVolume(volPath, func(vol *blockvol.BlockVol) error {
for i := 0; i < 5; i++ {
vol.WriteLBA(uint64(i), make([]byte, 4096))
}
return nil
})
var executionCount atomic.Int32
rm.OnBeforeExecute = func(replicaID string) {
executionCount.Add(1)
// Simulate slow execution — each goroutine takes 100ms.
time.Sleep(100 * time.Millisecond)
}
makeAssignment := func(epoch uint64) []blockvol.BlockVolumeAssignment {
return []blockvol.BlockVolumeAssignment{
{Path: volPath, Epoch: epoch, Role: uint32(blockvol.RolePrimary),
ReplicaServerID: "vs2", ReplicaDataAddr: "10.0.0.2:9333", ReplicaCtrlAddr: "10.0.0.2:9334"},
}
}
// Rapid fire: epoch 1, 2, 3 in quick succession.
// Each ProcessAssignments must drain the previous before starting the next.
bs.ProcessAssignments(makeAssignment(1))
time.Sleep(20 * time.Millisecond) // let goroutine start
bs.ProcessAssignments(makeAssignment(2))
bs.ProcessAssignments(makeAssignment(3))
// Wait for all to settle.
time.Sleep(500 * time.Millisecond)
// Must have at most 1 active task (the final one, or 0 if it completed).
count := rm.ActiveTaskCount()
if count > 1 {
t.Fatalf("goroutine leak: %d active tasks after triple supersede", count)
}
t.Logf("adversarial 1: triple supersede — %d executions, %d active tasks, no leak",
executionCount.Load(), count)
}
// --- Adversarial 2: Shutdown during slow goroutine (not permanently stuck) ---
func TestAdversarial_P4_ShutdownDuringSlow(t *testing.T) {
bs, volPath := createTestBlockServiceWithVol(t)
rm := bs.v2Recovery
bs.blockStore.WithVolume(volPath, func(vol *blockvol.BlockVol) error {
for i := 0; i < 5; i++ {
vol.WriteLBA(uint64(i), make([]byte, 4096))
}
return nil
})
started := make(chan struct{}, 1)
rm.OnBeforeExecute = func(replicaID string) {
started <- struct{}{}
// Simulate slow execution — 500ms, not permanently stuck.
time.Sleep(500 * time.Millisecond)
}
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{Path: volPath, Epoch: 1, Role: uint32(blockvol.RolePrimary),
ReplicaServerID: "vs2", ReplicaDataAddr: "10.0.0.2:9333", ReplicaCtrlAddr: "10.0.0.2:9334"},
})
select {
case <-started:
case <-time.After(5 * time.Second):
t.Fatal("goroutine did not start")
}
// Shutdown while goroutine is in slow execution.
done := make(chan bool, 1)
go func() {
rm.Shutdown()
done <- true
}()
select {
case <-done:
t.Log("adversarial 2: Shutdown completed after slow goroutine finished")
case <-time.After(5 * time.Second):
t.Fatal("Shutdown did not complete within 5 seconds")
}
if rm.ActiveTaskCount() != 0 {
t.Fatalf("tasks after shutdown: %d", rm.ActiveTaskCount())
}
}
// --- Adversarial 3: Two replicas, no cross-interference ---
func TestAdversarial_P4_TwoReplicas_NoInterference(t *testing.T) {
bs, volPath := createTestBlockServiceWithVol(t)
rm := bs.v2Recovery
bs.blockStore.WithVolume(volPath, func(vol *blockvol.BlockVol) error {
for i := 0; i < 5; i++ {
vol.WriteLBA(uint64(i), make([]byte, 4096))
}
return nil
})
var execIDs []string
var execMu = make(chan struct{}, 1)
rm.OnBeforeExecute = func(replicaID string) {
execMu <- struct{}{}
execIDs = append(execIDs, replicaID)
<-execMu
}
// Two different replicas in one assignment batch.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{Path: volPath, Epoch: 1, Role: uint32(blockvol.RolePrimary),
ReplicaServerID: "vs2", ReplicaDataAddr: "10.0.0.2:9333", ReplicaCtrlAddr: "10.0.0.2:9334",
ReplicaAddrs: []blockvol.ReplicaAddr{
{DataAddr: "10.0.0.2:9333", CtrlAddr: "10.0.0.2:9334", ServerID: "vs2"},
{DataAddr: "10.0.0.3:9333", CtrlAddr: "10.0.0.3:9334", ServerID: "vs3"},
},
},
})
time.Sleep(300 * time.Millisecond)
// Both replicas should have gotten recovery goroutines.
// No cross-interference: each has its own task with its own done channel.
replicaID1 := volPath + "/vs2"
replicaID2 := volPath + "/vs3"
s1 := bs.v2Orchestrator.Registry.Sender(replicaID1)
s2 := bs.v2Orchestrator.Registry.Sender(replicaID2)
if s1 == nil {
t.Fatal("sender for vs2 not created")
}
if s2 == nil {
t.Fatal("sender for vs3 not created")
}
t.Logf("adversarial 3: two replicas — vs2=%s vs3=%s, no cross-interference",
s1.State(), s2.State())
}
+312
View File
@@ -0,0 +1,312 @@
package weed_server
import (
"path/filepath"
"testing"
"time"
engine "github.com/seaweedfs/seaweedfs/sw-block/engine/replication"
"github.com/seaweedfs/seaweedfs/weed/storage"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol/v2bridge"
)
// ============================================================
// Phase 09 P4: Stronger live runtime ownership
//
// Proofs:
// 1. Live-path with real vol: ProcessAssignments → plan → executor
// 2. Serialized replacement: old drained before new starts
// 3. Shutdown drains all tasks
// 4. Rebuild address scoped
// 5. No split ownership under replacement
// ============================================================
func createTestBlockServiceWithVol(t *testing.T) (*BlockService, string) {
t.Helper()
dir := t.TempDir()
// Create a real blockvol.
volPath := filepath.Join(dir, "vol1.blk")
vol, err := blockvol.CreateBlockVol(volPath, blockvol.CreateOptions{
VolumeSize: 1 * 1024 * 1024,
BlockSize: 4096,
WALSize: 256 * 1024,
})
if err != nil {
t.Fatalf("CreateBlockVol: %v", err)
}
vol.Close()
// Build BlockService with a real BlockVolumeStore.
store := storage.NewBlockVolumeStore()
if _, err := store.AddBlockVolume(volPath, ""); err != nil {
t.Fatalf("AddBlockVolume: %v", err)
}
bs := &BlockService{
blockStore: store,
blockDir: dir,
listenAddr: "127.0.0.1:3260",
localServerID: "test-server-1",
v2Bridge: v2bridge.NewControlBridge(),
v2Orchestrator: engine.NewRecoveryOrchestrator(),
}
bs.v2Recovery = NewRecoveryManager(bs)
t.Cleanup(func() {
bs.v2Recovery.Shutdown()
store.Close()
})
return bs, volPath
}
// --- Live-path with real vol: reaches planning ---
func TestP4_LivePath_RealVol_ReachesPlan(t *testing.T) {
bs, volPath := createTestBlockServiceWithVol(t)
// Write data to the vol so recovery has something to plan against.
if err := bs.blockStore.WithVolume(volPath, func(vol *blockvol.BlockVol) error {
for i := 0; i < 5; i++ {
vol.WriteLBA(uint64(i), make([]byte, 4096))
}
return nil
}); err != nil {
t.Fatalf("write: %v", err)
}
// Process assignment through real path.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{
Path: volPath,
Epoch: 1,
Role: uint32(blockvol.RolePrimary),
ReplicaServerID: "vs2",
ReplicaDataAddr: "10.0.0.2:9333",
ReplicaCtrlAddr: "10.0.0.2:9334",
},
})
// Give recovery goroutine time to reach planning.
time.Sleep(200 * time.Millisecond)
// Verify: sender exists and engine processed it.
replicaID := volPath + "/vs2"
s := bs.v2Orchestrator.Registry.Sender(replicaID)
if s == nil {
t.Fatal("sender not created")
}
// Assert the full chain completed: plan → executor → in_sync.
events := bs.v2Orchestrator.Log.EventsFor(replicaID)
required := map[string]bool{
"plan_catchup": false,
"exec_catchup_started": false,
"exec_completed": false,
}
for _, ev := range events {
if _, ok := required[ev.Event]; ok {
required[ev.Event] = true
}
}
for event, found := range required {
if !found {
t.Fatalf("missing required event: %s (events=%d)", event, len(events))
}
}
// Assert final sender state.
if s.State() != engine.StateInSync {
t.Fatalf("sender state=%s, want in_sync", s.State())
}
t.Log("P4 live-path: ProcessAssignments → plan_catchup → exec_catchup_started → exec_completed → in_sync")
}
// --- Serialized replacement: old drained before new starts ---
func TestP4_SerializedReplacement_DrainsBeforeStart(t *testing.T) {
bs, volPath := createTestBlockServiceWithVol(t)
rm := bs.v2Recovery
// Write data so recovery has work to do.
if err := bs.blockStore.WithVolume(volPath, func(vol *blockvol.BlockVol) error {
for i := 0; i < 10; i++ {
vol.WriteLBA(uint64(i), make([]byte, 4096))
}
return nil
}); err != nil {
t.Fatalf("write: %v", err)
}
// Hook: block the first recovery goroutine so it is DEFINITELY still
// alive when the supersede arrives. Release it via channel.
holdFirst := make(chan struct{})
firstReached := make(chan struct{}, 1)
callCount := 0
rm.OnBeforeExecute = func(replicaID string) {
callCount++
if callCount == 1 {
// First call: signal that we're alive, then block.
firstReached <- struct{}{}
<-holdFirst // block until test releases
}
// Second call (replacement): proceed immediately.
}
replicaID := volPath + "/vs2"
// Epoch 1: start recovery goroutine (will block at OnBeforeExecute).
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{Path: volPath, Epoch: 1, Role: uint32(blockvol.RolePrimary),
ReplicaServerID: "vs2", ReplicaDataAddr: "10.0.0.2:9333", ReplicaCtrlAddr: "10.0.0.2:9334"},
})
// Wait for the first goroutine to reach the hook (still alive).
select {
case <-firstReached:
t.Log("first recovery goroutine is alive and blocked at OnBeforeExecute")
case <-time.After(5 * time.Second):
t.Fatal("first goroutine did not reach OnBeforeExecute")
}
// Capture the old task's done channel WHILE it is still running.
rm.mu.Lock()
oldTask := rm.tasks[replicaID]
rm.mu.Unlock()
if oldTask == nil {
t.Fatal("old task must exist while goroutine is blocked")
}
oldDone := oldTask.done
// Verify: old done channel is NOT closed yet.
select {
case <-oldDone:
t.Fatal("old task done should NOT be closed yet")
default:
t.Log("confirmed: old task still running (done channel open)")
}
// Epoch 2: supersede. cancelAndDrain will cancel the old context and
// block on oldDone. We release the hold from another goroutine so
// cancelAndDrain can complete.
go func() {
time.Sleep(50 * time.Millisecond)
close(holdFirst) // release the blocked first goroutine
}()
// This call blocks inside cancelAndDrain until the old goroutine exits.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{Path: volPath, Epoch: 2, Role: uint32(blockvol.RolePrimary),
ReplicaServerID: "vs2", ReplicaDataAddr: "10.0.0.2:9333", ReplicaCtrlAddr: "10.0.0.2:9334"},
})
// After ProcessAssignments returns, the old goroutine MUST be drained.
select {
case <-oldDone:
t.Log("confirmed: old task drained (done channel closed) before replacement started")
default:
t.Fatal("old task done channel still open after ProcessAssignments returned")
}
time.Sleep(200 * time.Millisecond)
// At most 1 active task.
count := rm.ActiveTaskCount()
if count > 1 {
t.Fatalf("overlap: %d tasks after replacement", count)
}
t.Log("P4 serialized: old goroutine blocked → supersede → drain waited → old exited → replacement started")
}
// --- Shutdown drains all ---
func TestP4_ShutdownDrain(t *testing.T) {
bs, volPath := createTestBlockServiceWithVol(t)
rm := bs.v2Recovery
// Hook: block the goroutine so it's alive when shutdown arrives.
holdTask := make(chan struct{})
taskReached := make(chan struct{}, 1)
rm.OnBeforeExecute = func(replicaID string) {
taskReached <- struct{}{}
<-holdTask
}
// Write data so recovery has work.
if err := bs.blockStore.WithVolume(volPath, func(vol *blockvol.BlockVol) error {
for i := 0; i < 5; i++ {
vol.WriteLBA(uint64(i), make([]byte, 4096))
}
return nil
}); err != nil {
t.Fatalf("write: %v", err)
}
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{Path: volPath, Epoch: 1, Role: uint32(blockvol.RolePrimary),
ReplicaServerID: "vs2", ReplicaDataAddr: "10.0.0.2:9333", ReplicaCtrlAddr: "10.0.0.2:9334"},
})
// Wait for task to be alive.
select {
case <-taskReached:
t.Log("task is alive and blocked before shutdown")
case <-time.After(5 * time.Second):
t.Fatal("task did not reach hook")
}
// Assert: a live task exists.
if rm.ActiveTaskCount() == 0 {
t.Fatal("expected live task before shutdown")
}
// Release the blocked goroutine from another goroutine so Shutdown can drain.
go func() {
time.Sleep(50 * time.Millisecond)
close(holdTask)
}()
done := make(chan bool, 1)
go func() {
rm.Shutdown()
done <- true
}()
select {
case <-done:
case <-time.After(5 * time.Second):
t.Fatal("Shutdown did not complete within 5 seconds")
}
if rm.ActiveTaskCount() != 0 {
t.Fatalf("expected 0 active tasks, got %d", rm.ActiveTaskCount())
}
t.Log("P4 shutdown: live task existed → shutdown drained it → 0 active")
}
// --- Rebuild address scoped ---
func TestP4_RebuildAddrScoped(t *testing.T) {
bs, _ := createTestBlockServiceWithVol(t)
rm := bs.v2Recovery
assignments := []blockvol.BlockVolumeAssignment{
{Path: "/data/vol1.blk", RebuildAddr: "10.0.0.1:5000"},
{Path: "/data/vol2.blk", RebuildAddr: "10.0.0.2:5000"},
}
if addr := rm.deriveRebuildAddr("/data/vol1.blk/vs2", assignments); addr != "10.0.0.1:5000" {
t.Fatalf("vol1 addr=%s", addr)
}
if addr := rm.deriveRebuildAddr("/data/vol2.blk/vs3", assignments); addr != "10.0.0.2:5000" {
t.Fatalf("vol2 addr=%s", addr)
}
if addr := rm.deriveRebuildAddr("/data/vol3.blk/vs4", assignments); addr != "" {
t.Fatalf("vol3 addr=%s", addr)
}
}
+11 -2
View File
@@ -1127,6 +1127,12 @@ func (r *BlockVolumeRegistry) evaluatePromotionLocked(entry *BlockVolumeEntry) P
freshnessCutoff = 60 * time.Second
}
primaryLSN := entry.WALHeadLSN
// EC-6 fix: when the primary is dead, its last-reported WALHeadLSN
// includes entries that were fsync'd locally but never shipped.
// The replica can never catch up because the primary is gone.
// Skip the WAL LSN gate so the best available replica is promoted,
// accepting that the last few unshipped entries may be lost.
primaryAlive := r.blockServers[entry.VolumeServer] != nil
bestIdx := -1
for i := range entry.Replicas {
@@ -1149,8 +1155,11 @@ func (r *BlockVolumeRegistry) evaluatePromotionLocked(entry *BlockVolumeEntry) P
})
continue
}
// Gate 2: WAL LSN recency (skip if primary LSN is 0 — no data yet, all eligible).
if primaryLSN > 0 && ri.WALHeadLSN+r.promotionLSNTolerance < primaryLSN {
// Gate 2: WAL LSN recency.
// Skip if primary LSN is 0 (no data yet — all eligible).
// EC-6 fix: also skip if primary is dead — its LSN is stale and
// the replica can never catch up. Promote the best available.
if primaryAlive && primaryLSN > 0 && ri.WALHeadLSN+r.promotionLSNTolerance < primaryLSN {
result.Rejections = append(result.Rejections, PromotionRejection{
Server: ri.Server,
Reason: "wal_lag",
+6 -1
View File
@@ -870,9 +870,12 @@ func TestRegistry_PromoteBestReplica_StaleHeartbeatIneligible(t *testing.T) {
}
}
// Fix #2: Replica with WAL lag too large is not eligible.
// Fix #2: Replica with WAL lag too large is not eligible (when primary alive).
// EC-6 fix: WAL lag gate only applies when primary is alive.
func TestRegistry_PromoteBestReplica_WALLagIneligible(t *testing.T) {
r := NewBlockVolumeRegistry()
r.MarkBlockCapable("primary") // primary must be alive for WAL lag gate
r.MarkBlockCapable("lagging") // replica must be alive for other gates to pass
r.Register(&BlockVolumeEntry{
Name: "vol1",
VolumeServer: "primary",
@@ -968,8 +971,10 @@ func TestRegistry_PromoteBestReplica_EligibilityFiltersCorrectly(t *testing.T) {
}
// Configurable tolerance: widen tolerance to allow lagging replicas.
// EC-6 fix: WAL lag gate only applies when primary is alive.
func TestRegistry_PromoteBestReplica_ConfigurableTolerance(t *testing.T) {
r := NewBlockVolumeRegistry()
r.MarkBlockCapable("primary") // primary must be alive for WAL lag gate to apply
r.MarkBlockCapable("lagging")
r.Register(&BlockVolumeEntry{
Name: "vol1",
+22
View File
@@ -167,16 +167,19 @@ func (ms *MasterServer) CreateBlockVolume(ctx context.Context, req *master_pb.Cr
LeaseTtlMs: leaseTTLMs,
}
// CP8-2: populate ReplicaAddrs for multi-replica.
// V2 P10-1: include stable ServerID from registry.
for _, ri := range entry.Replicas {
primaryAssignment.ReplicaAddrs = append(primaryAssignment.ReplicaAddrs, blockvol.ReplicaAddr{
DataAddr: ri.DataAddr,
CtrlAddr: ri.CtrlAddr,
ServerID: ri.Server, // V2: stable identity = registry VS address
})
}
// Backward compat: also set scalar fields if exactly 1 replica.
if len(entry.Replicas) == 1 {
primaryAssignment.ReplicaDataAddr = entry.Replicas[0].DataAddr
primaryAssignment.ReplicaCtrlAddr = entry.Replicas[0].CtrlAddr
primaryAssignment.ReplicaServerID = entry.Replicas[0].Server // V2: scalar stable ID
}
ms.blockAssignmentQueue.Enqueue(server, primaryAssignment)
@@ -368,6 +371,25 @@ func (ms *MasterServer) ListBlockSnapshots(ctx context.Context, req *master_pb.L
return resp, nil
}
// RestoreBlockSnapshot restores a block volume to the specified snapshot.
// This is a destructive operation: all writes after the snapshot are lost.
func (ms *MasterServer) RestoreBlockSnapshot(ctx context.Context, req *master_pb.RestoreBlockSnapshotRequest) (*master_pb.RestoreBlockSnapshotResponse, error) {
if req.VolumeName == "" {
return nil, fmt.Errorf("volume_name is required")
}
entry, ok := ms.blockRegistry.Lookup(req.VolumeName)
if !ok {
return nil, fmt.Errorf("block volume %q not found", req.VolumeName)
}
if err := ms.blockVSRestore(ctx, entry.VolumeServer, req.VolumeName, req.SnapshotId); err != nil {
return nil, fmt.Errorf("restore snapshot on %s: %w", entry.VolumeServer, err)
}
return &master_pb.RestoreBlockSnapshotResponse{}, nil
}
// ExpandBlockVolume expands a block volume. For standalone volumes (no replicas),
// uses direct expand. For replicated volumes, uses coordinated prepare/commit/cancel.
func (ms *MasterServer) ExpandBlockVolume(ctx context.Context, req *master_pb.ExpandBlockVolumeRequest) (*master_pb.ExpandBlockVolumeResponse, error) {
+12
View File
@@ -104,6 +104,7 @@ type MasterServer struct {
blockVSSnapshot func(ctx context.Context, server string, name string, snapID uint32) (int64, uint64, error)
blockVSDeleteSnap func(ctx context.Context, server string, name string, snapID uint32) error
blockVSListSnaps func(ctx context.Context, server string, name string) ([]*volume_server_pb.BlockSnapshotInfo, error)
blockVSRestore func(ctx context.Context, server string, name string, snapID uint32) error
blockVSExpand func(ctx context.Context, server string, name string, newSize uint64) (uint64, error)
blockVSPrepareExpand func(ctx context.Context, server string, name string, newSize, expandEpoch uint64) error
blockVSCommitExpand func(ctx context.Context, server string, name string, expandEpoch uint64) (uint64, error)
@@ -172,6 +173,7 @@ func NewMasterServer(r *mux.Router, option *MasterOption, peers map[string]pb.Se
ms.blockVSSnapshot = ms.defaultBlockVSSnapshot
ms.blockVSDeleteSnap = ms.defaultBlockVSDeleteSnap
ms.blockVSListSnaps = ms.defaultBlockVSListSnaps
ms.blockVSRestore = ms.defaultBlockVSRestore
ms.blockVSExpand = ms.defaultBlockVSExpand
ms.blockVSPrepareExpand = ms.defaultBlockVSPrepareExpand
ms.blockVSCommitExpand = ms.defaultBlockVSCommitExpand
@@ -650,6 +652,16 @@ func (ms *MasterServer) defaultBlockVSListSnaps(ctx context.Context, server stri
return infos, err
}
func (ms *MasterServer) defaultBlockVSRestore(ctx context.Context, server string, name string, snapID uint32) error {
return operation.WithVolumeServerClient(false, pb.ServerAddress(server), ms.grpcDialOption, func(client volume_server_pb.VolumeServerClient) error {
_, err := client.RestoreBlockSnapshot(ctx, &volume_server_pb.RestoreBlockSnapshotRequest{
Name: name,
SnapshotId: snapID,
})
return err
})
}
func (ms *MasterServer) defaultBlockVSExpand(ctx context.Context, server string, name string, newSize uint64) (uint64, error) {
var capacity uint64
err := operation.WithVolumeServerClient(false, pb.ServerAddress(server), ms.grpcDialOption, func(client volume_server_pb.VolumeServerClient) error {
@@ -0,0 +1,126 @@
package weed_server
import (
"context"
"path/filepath"
"strings"
"testing"
"time"
engine "github.com/seaweedfs/seaweedfs/sw-block/engine/replication"
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
"github.com/seaweedfs/seaweedfs/weed/storage"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol/v2bridge"
)
// ============================================================
// Phase 10 P4: Adversarial — stale epoch on PROMOTED volume
//
// Corrected: targets the promoted BS + promoted volume path,
// matching the real failover test shape.
// ============================================================
func TestAdversarial_P10P4_StaleEpochOnPromotedVolume(t *testing.T) {
s := newP4Setup(t)
// Step 1: create volume at epoch 1 (primary=vs1, replica=vs2).
ctx := context.Background()
createResp, err := s.ms.CreateBlockVolume(ctx, &master_pb.CreateBlockVolumeRequest{
Name: "pvc-stale-2",
SizeBytes: 1 << 20,
ReplicaFactor: 2,
DurabilityMode: "sync_all",
})
if err != nil {
t.Fatal(err)
}
primaryVS := createResp.VolumeServer
t.Logf("created: primary=%s", primaryVS)
// Deliver epoch 1 to primary BS.
s.deliverAssignments(primaryVS)
time.Sleep(200 * time.Millisecond)
// Step 2: failover → epoch 2, new primary is the old replica.
s.ms.blockRegistry.UpdateEntry("pvc-stale-2", func(e *BlockVolumeEntry) {
e.LastLeaseGrant = time.Now().Add(-1 * time.Minute)
})
s.ms.failoverBlockVolumes(primaryVS)
time.Sleep(100 * time.Millisecond)
entryAfter, ok := s.ms.blockRegistry.Lookup("pvc-stale-2")
if !ok {
t.Fatal("volume not found after failover")
}
if entryAfter.Epoch != 2 {
t.Fatalf("registry epoch: %d, want 2", entryAfter.Epoch)
}
newPrimary := entryAfter.VolumeServer
t.Logf("after failover: new primary=%s epoch=2", newPrimary)
// Step 3: create a SEPARATE promoted BlockService (same as P4 failover test).
// This is the promoted VS — it has its own store with the promoted volume.
sanitized := strings.ReplaceAll(newPrimary, ":", "_")
promotedDir := filepath.Join(s.dir, sanitized+"_promoted")
promotedStore := storage.NewBlockVolumeStore()
volPath := filepath.Join(s.dir, sanitized, "pvc-stale-2.blk")
if _, err := promotedStore.AddBlockVolume(volPath, ""); err != nil {
t.Fatalf("add promoted vol: %v", err)
}
promotedBS := &BlockService{
blockStore: promotedStore,
blockDir: promotedDir,
listenAddr: "127.0.0.1:3260",
localServerID: newPrimary,
v2Bridge: v2bridge.NewControlBridge(),
v2Orchestrator: engine.NewRecoveryOrchestrator(),
replStates: make(map[string]*volReplState),
}
promotedBS.v2Recovery = NewRecoveryManager(promotedBS)
t.Cleanup(func() {
promotedBS.v2Recovery.Shutdown()
promotedStore.Close()
})
// Deliver epoch 2 assignment to promoted BS (not s.bs).
pending := s.ms.blockAssignmentQueue.Peek(newPrimary)
if len(pending) > 0 {
protoAssignments := blockvol.AssignmentsToProto(pending)
goAssignments := blockvol.AssignmentsFromProto(protoAssignments)
promotedBS.ProcessAssignments(goAssignments)
}
time.Sleep(200 * time.Millisecond)
// Verify: promoted vol has epoch 2.
var promotedEpoch uint64
promotedStore.WithVolume(volPath, func(vol *blockvol.BlockVol) error {
promotedEpoch = vol.Epoch()
return nil
})
if promotedEpoch != 2 {
t.Fatalf("promoted vol epoch: %d, want 2", promotedEpoch)
}
// Step 4: deliver STALE epoch 1 to the PROMOTED BS.
staleAssignment := blockvol.BlockVolumeAssignment{
Path: volPath,
Epoch: 1, // STALE — lower than current epoch 2
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
}
promotedBS.ProcessAssignments([]blockvol.BlockVolumeAssignment{staleAssignment})
time.Sleep(100 * time.Millisecond)
// Step 5: verify NOT reverted on the PROMOTED volume.
var afterStale uint64
promotedStore.WithVolume(volPath, func(vol *blockvol.BlockVol) error {
afterStale = vol.Epoch()
return nil
})
if afterStale < 2 {
t.Fatalf("BUG: promoted vol epoch reverted from 2 to %d after stale delivery", afterStale)
}
t.Logf("adversarial: stale epoch 1 on promoted vol → epoch stays at %d (HandleAssignment rejects regression)", afterStale)
}
+383
View File
@@ -0,0 +1,383 @@
package weed_server
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
engine "github.com/seaweedfs/seaweedfs/sw-block/engine/replication"
"github.com/seaweedfs/seaweedfs/weed/storage"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol/v2bridge"
)
// ============================================================
// Phase 10 P4: Master-driven control-loop closure
//
// These tests use REAL master assignment production (CreateBlockVolume,
// failoverBlockVolumes) with the allocator wired to produce paths that
// match REAL registered block volumes in the test BlockService.
// ============================================================
// p4Setup creates a master + BlockService where the master's allocator
// produces paths matching real volumes registered in the BlockService's store.
type p4Setup struct {
ms *MasterServer
bs *BlockService
store *storage.BlockVolumeStore
dir string
volSeq int
}
func newP4Setup(t *testing.T) *p4Setup {
t.Helper()
dir := t.TempDir()
store := storage.NewBlockVolumeStore()
bs := &BlockService{
blockStore: store,
blockDir: dir,
listenAddr: "127.0.0.1:3260",
localServerID: "vs1:9333",
v2Bridge: v2bridge.NewControlBridge(),
v2Orchestrator: engine.NewRecoveryOrchestrator(),
replStates: make(map[string]*volReplState),
}
bs.v2Recovery = NewRecoveryManager(bs)
ms := &MasterServer{
blockRegistry: NewBlockVolumeRegistry(),
blockAssignmentQueue: NewBlockAssignmentQueue(),
blockFailover: newBlockFailoverState(),
}
ms.blockRegistry.MarkBlockCapable("vs1:9333")
ms.blockRegistry.MarkBlockCapable("vs2:9333")
setup := &p4Setup{ms: ms, bs: bs, store: store, dir: dir}
// Wire allocator to create REAL block volumes at per-server paths.
ms.blockVSAllocate = func(ctx context.Context, server string, name string, sizeBytes uint64, diskType string, durabilityMode string) (*blockAllocResult, error) {
// Per-server subdir (sanitize colons for Windows).
sanitized := strings.ReplaceAll(server, ":", "_")
serverDir := filepath.Join(dir, sanitized)
if err := os.MkdirAll(serverDir, 0755); err != nil {
return nil, err
}
volPath := filepath.Join(serverDir, fmt.Sprintf("%s.blk", name))
vol, err := blockvol.CreateBlockVol(volPath, blockvol.CreateOptions{
VolumeSize: 1 * 1024 * 1024,
BlockSize: 4096,
WALSize: 256 * 1024,
})
if err != nil {
return nil, err
}
vol.Close()
// Register ALL volumes in the shared store (single-process test
// simulates both primary and promoted-replica VS).
if _, err := store.AddBlockVolume(volPath, ""); err != nil {
return nil, err
}
return &blockAllocResult{
Path: volPath,
IQN: fmt.Sprintf("iqn.2024.test:%s", name),
ISCSIAddr: server + ":3260",
ReplicaDataAddr: server + ":14260",
ReplicaCtrlAddr: server + ":14261",
RebuildListenAddr: server + ":15000",
}, nil
}
ms.blockVSDelete = func(ctx context.Context, server string, name string) error {
return nil
}
t.Cleanup(func() {
bs.v2Recovery.Shutdown()
store.Close()
})
return setup
}
// deliverAssignments simulates heartbeat delivery: queue → proto → decode → ProcessAssignments.
func (s *p4Setup) deliverAssignments(server string) int {
pending := s.ms.blockAssignmentQueue.Peek(server)
if len(pending) == 0 {
return 0
}
protoAssignments := blockvol.AssignmentsToProto(pending)
goAssignments := blockvol.AssignmentsFromProto(protoAssignments)
s.bs.ProcessAssignments(goAssignments)
return len(goAssignments)
}
// --- 1. Real master create → full delivery with real volume ---
func TestP10P4_MasterCreate_FullDelivery(t *testing.T) {
s := newP4Setup(t)
ctx := context.Background()
createResp, err := s.ms.CreateBlockVolume(ctx, &master_pb.CreateBlockVolumeRequest{
Name: "pvc-data-1",
SizeBytes: 1 << 30,
})
if err != nil {
t.Fatalf("CreateBlockVolume: %v", err)
}
primaryVS := createResp.VolumeServer
t.Logf("master created: primary=%s replica=%s", primaryVS, createResp.ReplicaServer)
// Deliver master-produced assignments.
n := s.deliverAssignments(primaryVS)
if n == 0 {
t.Fatal("no assignments delivered")
}
time.Sleep(200 * time.Millisecond)
// Verify: engine has sender with stable ReplicaID.
entry, _ := s.ms.blockRegistry.Lookup("pvc-data-1")
expectedID := entry.Path + "/" + entry.Replicas[0].Server
sender := s.bs.v2Orchestrator.Registry.Sender(expectedID)
if sender == nil {
t.Fatalf("sender not found: %s", expectedID)
}
// Verify: V1 HandleAssignment succeeded (vol exists in store).
var volEpoch uint64
if err := s.store.WithVolume(entry.Path, func(vol *blockvol.BlockVol) error {
volEpoch = vol.Epoch()
return nil
}); err != nil {
t.Fatalf("volume not accessible in store: %v", err)
}
if volEpoch == 0 {
t.Fatal("vol epoch should be set after HandleAssignment")
}
// Verify: heartbeat reports the assigned volume.
msgs := s.bs.CollectBlockVolumeHeartbeat()
found := false
for _, m := range msgs {
if m.Path == entry.Path {
found = true
if m.Epoch != entry.Epoch {
t.Fatalf("heartbeat epoch=%d, want %d", m.Epoch, entry.Epoch)
}
}
}
if !found {
t.Fatalf("assigned volume %s not in heartbeat", entry.Path)
}
t.Logf("P10P4 create: master → queue → proto → VS → sender(%s) + vol(epoch=%d) + heartbeat", expectedID, volEpoch)
}
// --- 2. Real master failover → delivery → convergence ---
func TestP10P4_MasterFailover_Convergence(t *testing.T) {
s := newP4Setup(t)
ctx := context.Background()
createResp, err := s.ms.CreateBlockVolume(ctx, &master_pb.CreateBlockVolumeRequest{
Name: "pvc-data-2",
SizeBytes: 1 << 30,
})
if err != nil {
t.Fatalf("CreateBlockVolume: %v", err)
}
primaryVS := createResp.VolumeServer
replicaVS := createResp.ReplicaServer
t.Logf("created: primary=%s replica=%s", primaryVS, replicaVS)
// Deliver initial assignment to primary BS.
n1 := s.deliverAssignments(primaryVS)
if n1 == 0 {
t.Fatal("no initial assignments delivered")
}
time.Sleep(200 * time.Millisecond)
entry, _ := s.ms.blockRegistry.Lookup("pvc-data-2")
// Expire lease, then failover.
s.ms.blockRegistry.UpdateEntry("pvc-data-2", func(e *BlockVolumeEntry) {
e.LastLeaseGrant = time.Now().Add(-1 * time.Minute)
})
s.ms.failoverBlockVolumes(primaryVS)
entryAfter, _ := s.ms.blockRegistry.Lookup("pvc-data-2")
if entryAfter.Epoch != 2 {
t.Fatalf("epoch=%d, want 2", entryAfter.Epoch)
}
newPrimary := entryAfter.VolumeServer
t.Logf("after failover: new primary=%s epoch=%d", newPrimary, entryAfter.Epoch)
// Build a SEPARATE BlockService for the promoted replica VS.
// This simulates the promoted VS having its own store and heartbeat.
replicaStore := storage.NewBlockVolumeStore()
// Find and register the replica's vol path.
sanitized := strings.ReplaceAll(replicaVS, ":", "_")
replicaVolPath := filepath.Join(s.dir, sanitized, "pvc-data-2.blk")
if _, err := replicaStore.AddBlockVolume(replicaVolPath, ""); err != nil {
t.Fatalf("register replica vol: %v", err)
}
promotedBS := &BlockService{
blockStore: replicaStore,
blockDir: s.dir,
listenAddr: "127.0.0.1:3261",
localServerID: newPrimary,
v2Bridge: v2bridge.NewControlBridge(),
v2Orchestrator: engine.NewRecoveryOrchestrator(),
replStates: make(map[string]*volReplState),
}
promotedBS.v2Recovery = NewRecoveryManager(promotedBS)
t.Cleanup(func() {
promotedBS.v2Recovery.Shutdown()
replicaStore.Close()
})
// Deliver failover assignment to the promoted BS through proto path.
failoverPending := s.ms.blockAssignmentQueue.Peek(newPrimary)
if len(failoverPending) == 0 {
t.Fatal("no failover assignment in queue")
}
fa := failoverPending[0]
if fa.Epoch != 2 {
t.Fatalf("failover assignment epoch=%d, want 2", fa.Epoch)
}
protoAssignments := blockvol.AssignmentsToProto(failoverPending)
goAssignments := blockvol.AssignmentsFromProto(protoAssignments)
promotedBS.ProcessAssignments(goAssignments)
time.Sleep(200 * time.Millisecond)
// Verify: HandleAssignment succeeded on the promoted vol (epoch=2).
var promotedEpoch uint64
if err := replicaStore.WithVolume(fa.Path, func(vol *blockvol.BlockVol) error {
promotedEpoch = vol.Epoch()
return nil
}); err != nil {
t.Fatalf("promoted vol not accessible: %v", err)
}
if promotedEpoch != 2 {
t.Fatalf("promoted vol epoch=%d, want 2", promotedEpoch)
}
// Verify: promoted VS's OWN heartbeat reports the vol at epoch 2.
// This is the promoted VS's independent heartbeat, not the shared store.
promotedMsgs := promotedBS.CollectBlockVolumeHeartbeat()
foundPromoted := false
for _, m := range promotedMsgs {
if m.Path == fa.Path && m.Epoch == 2 {
foundPromoted = true
}
}
if !foundPromoted {
t.Fatal("promoted VS heartbeat should show vol at epoch 2")
}
// Verify: promoted VS heartbeat does NOT contain the old primary's vol.
_ = entry // reference to initial entry
for _, m := range promotedMsgs {
if m.Path == entry.Path && m.Path != fa.Path {
t.Fatalf("promoted VS heartbeat should NOT contain old primary vol %s", entry.Path)
}
}
t.Logf("P10P4 failover: create → failover(epoch=2) → separate promoted BS → vol(epoch=2) + own heartbeat")
}
// --- 3. Identity preservation through master-produced path ---
func TestP10P4_IdentityPreservation(t *testing.T) {
s := newP4Setup(t)
ctx := context.Background()
_, err := s.ms.CreateBlockVolume(ctx, &master_pb.CreateBlockVolumeRequest{
Name: "pvc-data-3",
SizeBytes: 1 << 30,
})
if err != nil {
t.Fatalf("CreateBlockVolume: %v", err)
}
entry, _ := s.ms.blockRegistry.Lookup("pvc-data-3")
// Verify master-produced assignment carries stable ServerID.
pending := s.ms.blockAssignmentQueue.Peek(entry.VolumeServer)
hasStableID := false
for _, a := range pending {
if a.ReplicaServerID != "" {
hasStableID = true
}
for _, ra := range a.ReplicaAddrs {
if ra.ServerID != "" {
hasStableID = true
}
}
}
if !hasStableID {
t.Fatal("master assignment missing stable ServerID")
}
// Deliver and verify engine uses stable ID, not address.
s.deliverAssignments(entry.VolumeServer)
time.Sleep(200 * time.Millisecond)
for _, ri := range entry.Replicas {
stableID := entry.Path + "/" + ri.Server
addressID := entry.Path + "/" + ri.DataAddr
if s.bs.v2Orchestrator.Registry.Sender(stableID) == nil {
t.Fatalf("stable sender %s not found", stableID)
}
if s.bs.v2Orchestrator.Registry.Sender(addressID) != nil {
t.Fatalf("address-derived sender %s should NOT exist", addressID)
}
}
t.Log("P10P4 identity: master-produced stable ID preserved through full delivery")
}
// --- 4. Repeated delivery idempotence ---
func TestP10P4_RepeatedDelivery_Idempotence(t *testing.T) {
s := newP4Setup(t)
ctx := context.Background()
_, err := s.ms.CreateBlockVolume(ctx, &master_pb.CreateBlockVolumeRequest{
Name: "pvc-data-4",
SizeBytes: 1 << 30,
})
if err != nil {
t.Fatalf("CreateBlockVolume: %v", err)
}
entry, _ := s.ms.blockRegistry.Lookup("pvc-data-4")
primaryVS := entry.VolumeServer
replicaID := entry.Path + "/" + entry.Replicas[0].Server
// First delivery.
s.deliverAssignments(primaryVS)
time.Sleep(200 * time.Millisecond)
eventsAfterFirst := len(s.bs.v2Orchestrator.Log.EventsFor(replicaID))
if eventsAfterFirst == 0 {
t.Fatal("first delivery must create engine events (guard against vacuous pass)")
}
t.Logf("first delivery: %d events", eventsAfterFirst)
// Second delivery (Peek returns same assignments).
s.deliverAssignments(primaryVS)
time.Sleep(100 * time.Millisecond)
eventsAfterSecond := len(s.bs.v2Orchestrator.Log.EventsFor(replicaID))
if eventsAfterSecond != eventsAfterFirst {
t.Fatalf("idempotence broken: events %d → %d", eventsAfterFirst, eventsAfterSecond)
}
t.Logf("P10P4 idempotent: repeated delivery → events stable at %d (guard: >0)", eventsAfterFirst)
}
@@ -0,0 +1,158 @@
package weed_server
import (
"testing"
"time"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol"
)
// ============================================================
// Phase 10 P2: Adversarial convergence tests
// ============================================================
// --- Adversarial 1: Rapid triple reassignment (vs2 → vs3 → vs4) ---
func TestAdversarial_P10P2_RapidTripleReassignment(t *testing.T) {
bs, volPath := createP2BlockService(t)
makeAssignment := func(epoch uint64, serverID, dataAddr string) []blockvol.BlockVolumeAssignment {
return []blockvol.BlockVolumeAssignment{
{
Path: volPath,
Epoch: epoch,
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
ReplicaServerID: serverID,
ReplicaDataAddr: dataAddr,
ReplicaCtrlAddr: dataAddr,
},
}
}
// Three rapid reassignments.
bs.ProcessAssignments(makeAssignment(1, "vs2-node:18080", "10.0.0.2:14260"))
time.Sleep(50 * time.Millisecond)
bs.ProcessAssignments(makeAssignment(2, "vs3-node:18080", "10.0.0.3:14260"))
bs.ProcessAssignments(makeAssignment(3, "vs4-node:18080", "10.0.0.4:14260"))
time.Sleep(300 * time.Millisecond)
// Only vs4 should exist in engine.
vs2ID := volPath + "/vs2-node:18080"
vs3ID := volPath + "/vs3-node:18080"
vs4ID := volPath + "/vs4-node:18080"
if bs.v2Orchestrator.Registry.Sender(vs2ID) != nil {
t.Fatal("vs2 sender should be removed")
}
if bs.v2Orchestrator.Registry.Sender(vs3ID) != nil {
t.Fatal("vs3 sender should be removed")
}
if bs.v2Orchestrator.Registry.Sender(vs4ID) == nil {
t.Fatal("vs4 sender should exist")
}
// Runtime: no stale tasks for vs2 or vs3.
bs.v2Recovery.mu.Lock()
_, hasVs2 := bs.v2Recovery.tasks[vs2ID]
_, hasVs3 := bs.v2Recovery.tasks[vs3ID]
bs.v2Recovery.mu.Unlock()
if hasVs2 || hasVs3 {
t.Fatalf("stale runtime tasks: vs2=%v vs3=%v", hasVs2, hasVs3)
}
// Heartbeat: must report vs4 address.
msgs := bs.CollectBlockVolumeHeartbeat()
hb := findHeartbeatMsg(msgs, volPath)
if hb == nil {
t.Fatal("volume not in heartbeat")
}
if hb.ReplicaDataAddr != "10.0.0.4:14260" {
t.Fatalf("heartbeat addr=%s, want 10.0.0.4:14260", hb.ReplicaDataAddr)
}
t.Log("adversarial 1: rapid triple reassignment → only vs4 in all truth surfaces")
}
// --- Adversarial 2: Same replica, address change (epoch bump) ---
func TestAdversarial_P10P2_SameReplica_AddressChange(t *testing.T) {
bs, volPath := createP2BlockService(t)
replicaID := volPath + "/vs2-node:18080"
// Epoch 1: vs2 at address 10.0.0.2.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{
Path: volPath,
Epoch: 1,
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
ReplicaServerID: "vs2-node:18080",
ReplicaDataAddr: "10.0.0.2:14260",
ReplicaCtrlAddr: "10.0.0.2:14261",
},
})
time.Sleep(100 * time.Millisecond)
senderBefore := bs.v2Orchestrator.Registry.Sender(replicaID)
if senderBefore == nil {
t.Fatal("sender should exist at epoch 1")
}
// Epoch 2: SAME ServerID, DIFFERENT address (replica restarted on new IP).
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{
Path: volPath,
Epoch: 2,
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
ReplicaServerID: "vs2-node:18080",
ReplicaDataAddr: "10.0.0.99:14260", // different IP
ReplicaCtrlAddr: "10.0.0.99:14261",
},
})
time.Sleep(200 * time.Millisecond)
// Same sender identity (pointer).
senderAfter := bs.v2Orchestrator.Registry.Sender(replicaID)
if senderAfter == nil {
t.Fatal("sender should still exist")
}
if senderAfter != senderBefore {
t.Fatal("sender identity must be preserved (same pointer) across address change")
}
// Endpoint updated.
if senderAfter.Endpoint().DataAddr != "10.0.0.99:14260" {
t.Fatalf("endpoint not updated: %s", senderAfter.Endpoint().DataAddr)
}
// Heartbeat: new address.
msgs := bs.CollectBlockVolumeHeartbeat()
hb := findHeartbeatMsg(msgs, volPath)
if hb == nil {
t.Fatal("volume not in heartbeat")
}
if hb.ReplicaDataAddr != "10.0.0.99:14260" {
t.Fatalf("heartbeat addr=%s, want 10.0.0.99:14260", hb.ReplicaDataAddr)
}
// No duplicate sender.
count := 0
for _, s := range bs.v2Orchestrator.Registry.All() {
if s.ReplicaID() == replicaID {
count++
}
}
if count != 1 {
t.Fatalf("duplicate senders: %d for %s", count, replicaID)
}
t.Log("adversarial 2: same replica, address change → identity preserved, endpoint updated, no duplicate")
}
+358
View File
@@ -0,0 +1,358 @@
package weed_server
import (
"path/filepath"
"testing"
"time"
engine "github.com/seaweedfs/seaweedfs/sw-block/engine/replication"
"github.com/seaweedfs/seaweedfs/weed/storage"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol/v2bridge"
)
// ============================================================
// Phase 10 P2: Reassignment / result convergence
//
// Proofs:
// 1. Reassignment convergence: epoch bump updates control truth
// 2. Stale owner removal: old sender/session gone after reassignment
// 3. Reported truth: heartbeat reflects new replica assignment
// 4. No split truth: ingress + runtime + reported all agree
// ============================================================
func createP2BlockService(t *testing.T) (*BlockService, string) {
t.Helper()
dir := t.TempDir()
volPath := filepath.Join(dir, "vol1.blk")
vol, err := blockvol.CreateBlockVol(volPath, blockvol.CreateOptions{
VolumeSize: 1 * 1024 * 1024,
BlockSize: 4096,
WALSize: 256 * 1024,
})
if err != nil {
t.Fatalf("CreateBlockVol: %v", err)
}
vol.Close()
store := storage.NewBlockVolumeStore()
if _, err := store.AddBlockVolume(volPath, ""); err != nil {
t.Fatalf("AddBlockVolume: %v", err)
}
bs := &BlockService{
blockStore: store,
blockDir: dir,
listenAddr: "127.0.0.1:3260",
localServerID: "vs1-node:18080",
v2Bridge: v2bridge.NewControlBridge(),
v2Orchestrator: engine.NewRecoveryOrchestrator(),
replStates: make(map[string]*volReplState),
}
bs.v2Recovery = NewRecoveryManager(bs)
t.Cleanup(func() {
bs.v2Recovery.Shutdown()
store.Close()
})
return bs, volPath
}
// --- 1. Reassignment convergence: epoch bump updates control truth ---
func TestP10P2_ReassignmentConvergence_EpochBump(t *testing.T) {
bs, volPath := createP2BlockService(t)
// Epoch 1: primary with replica vs2.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{
Path: volPath,
Epoch: 1,
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
ReplicaServerID: "vs2-node:18080",
ReplicaDataAddr: "10.0.0.2:14260",
ReplicaCtrlAddr: "10.0.0.2:14261",
},
})
time.Sleep(200 * time.Millisecond)
replicaID1 := volPath + "/vs2-node:18080"
s1 := bs.v2Orchestrator.Registry.Sender(replicaID1)
if s1 == nil {
t.Fatal("epoch 1: sender for vs2 not created")
}
epoch1Session := s1.SessionID()
t.Logf("epoch 1: sender=%s sessionID=%d", replicaID1, epoch1Session)
// Epoch 2: primary with DIFFERENT replica vs3.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{
Path: volPath,
Epoch: 2,
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
ReplicaServerID: "vs3-node:18080",
ReplicaDataAddr: "10.0.0.3:14260",
ReplicaCtrlAddr: "10.0.0.3:14261",
},
})
time.Sleep(200 * time.Millisecond)
// New replica sender should exist.
replicaID2 := volPath + "/vs3-node:18080"
s2 := bs.v2Orchestrator.Registry.Sender(replicaID2)
if s2 == nil {
t.Fatal("epoch 2: sender for vs3 not created")
}
t.Logf("epoch 2: sender=%s sessionID=%d", replicaID2, s2.SessionID())
// Old replica sender should be gone (removed by Reconcile).
s1After := bs.v2Orchestrator.Registry.Sender(replicaID1)
if s1After != nil {
t.Fatalf("epoch 2: old sender %s should be removed", replicaID1)
}
t.Log("P10P2 convergence: epoch bump → old replica removed, new replica active")
}
// --- 2. Stale owner removal: live old recovery cancelled during reassignment ---
func TestP10P2_StaleOwnerRemoval(t *testing.T) {
bs, volPath := createP2BlockService(t)
rm := bs.v2Recovery
// Write data so recovery has real work (not zero-gap auto-complete).
if err := bs.blockStore.WithVolume(volPath, func(vol *blockvol.BlockVol) error {
for i := 0; i < 5; i++ {
vol.WriteLBA(uint64(i), make([]byte, 4096))
}
return nil
}); err != nil {
t.Fatalf("write: %v", err)
}
// Hook: block the first recovery goroutine so it's alive during reassignment.
holdFirst := make(chan struct{})
firstReached := make(chan struct{}, 1)
callCount := 0
rm.OnBeforeExecute = func(replicaID string) {
callCount++
if callCount == 1 {
firstReached <- struct{}{}
<-holdFirst // block until released
}
}
// Epoch 1: start recovery for vs2 (will block at hook).
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{
Path: volPath,
Epoch: 1,
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
ReplicaServerID: "vs2-node:18080",
ReplicaDataAddr: "10.0.0.2:14260",
ReplicaCtrlAddr: "10.0.0.2:14261",
},
})
// Wait for old goroutine to be alive.
select {
case <-firstReached:
t.Log("old recovery goroutine for vs2 is alive and blocked")
case <-time.After(5 * time.Second):
t.Fatal("old goroutine did not reach hook")
}
// Capture old task's done channel.
oldReplicaID := volPath + "/vs2-node:18080"
rm.mu.Lock()
oldTask := rm.tasks[oldReplicaID]
rm.mu.Unlock()
if oldTask == nil {
t.Fatal("old task must exist while goroutine is blocked")
}
oldDone := oldTask.done
// Release old goroutine from another goroutine.
go func() {
time.Sleep(50 * time.Millisecond)
close(holdFirst)
}()
// Epoch 2: reassignment to vs3. cancelAndDrain waits for old to exit.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{
Path: volPath,
Epoch: 2,
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
ReplicaServerID: "vs3-node:18080",
ReplicaDataAddr: "10.0.0.3:14260",
ReplicaCtrlAddr: "10.0.0.3:14261",
},
})
// Old goroutine must be drained.
select {
case <-oldDone:
t.Log("confirmed: old recovery goroutine drained during reassignment")
default:
t.Fatal("old done channel still open after reassignment")
}
// Old task must be gone.
rm.mu.Lock()
_, hasOld := rm.tasks[oldReplicaID]
rm.mu.Unlock()
if hasOld {
t.Fatal("stale recovery task should be gone")
}
t.Log("P10P2 stale removal: live old owner cancelled + drained during reassignment")
}
// --- 3. Reported truth: actual heartbeat output reflects new replica ---
func TestP10P2_ReportedTruth_HeartbeatConverges(t *testing.T) {
bs, volPath := createP2BlockService(t)
// Epoch 1: primary with vs2.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{
Path: volPath,
Epoch: 1,
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
ReplicaServerID: "vs2-node:18080",
ReplicaDataAddr: "10.0.0.2:14260",
ReplicaCtrlAddr: "10.0.0.2:14261",
},
})
time.Sleep(100 * time.Millisecond)
// Verify actual heartbeat output reports vs2.
msgs1 := bs.CollectBlockVolumeHeartbeat()
found1 := findHeartbeatMsg(msgs1, volPath)
if found1 == nil {
t.Fatal("epoch 1: volume not in heartbeat output")
}
if found1.ReplicaDataAddr != "10.0.0.2:14260" {
t.Fatalf("epoch 1 heartbeat: ReplicaDataAddr=%q, want 10.0.0.2:14260", found1.ReplicaDataAddr)
}
t.Logf("epoch 1 heartbeat: ReplicaDataAddr=%s ✓", found1.ReplicaDataAddr)
// Epoch 2: primary with vs3.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{
Path: volPath,
Epoch: 2,
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
ReplicaServerID: "vs3-node:18080",
ReplicaDataAddr: "10.0.0.3:14260",
ReplicaCtrlAddr: "10.0.0.3:14261",
},
})
time.Sleep(100 * time.Millisecond)
// Actual heartbeat output should report vs3, NOT stale vs2.
msgs2 := bs.CollectBlockVolumeHeartbeat()
found2 := findHeartbeatMsg(msgs2, volPath)
if found2 == nil {
t.Fatal("epoch 2: volume not in heartbeat output")
}
if found2.ReplicaDataAddr != "10.0.0.3:14260" {
t.Fatalf("epoch 2 heartbeat: ReplicaDataAddr=%q, want 10.0.0.3:14260 (stale vs2)", found2.ReplicaDataAddr)
}
if found2.ReplicaCtrlAddr != "10.0.0.3:14261" {
t.Fatalf("epoch 2 heartbeat: ReplicaCtrlAddr=%q, want 10.0.0.3:14261", found2.ReplicaCtrlAddr)
}
t.Logf("P10P2 reported truth: CollectBlockVolumeHeartbeat converged from vs2 → vs3")
}
func findHeartbeatMsg(msgs []blockvol.BlockVolumeInfoMessage, path string) *blockvol.BlockVolumeInfoMessage {
for i := range msgs {
if msgs[i].Path == path {
return &msgs[i]
}
}
return nil
}
// --- 4. No split truth: ingress + runtime + reported all agree ---
func TestP10P2_NoSplitTruth(t *testing.T) {
bs, volPath := createP2BlockService(t)
// Epoch 1: primary with vs2.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{
Path: volPath,
Epoch: 1,
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
ReplicaServerID: "vs2-node:18080",
ReplicaDataAddr: "10.0.0.2:14260",
ReplicaCtrlAddr: "10.0.0.2:14261",
},
})
time.Sleep(200 * time.Millisecond)
// Epoch 2: primary with vs3.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{
Path: volPath,
Epoch: 2,
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
ReplicaServerID: "vs3-node:18080",
ReplicaDataAddr: "10.0.0.3:14260",
ReplicaCtrlAddr: "10.0.0.3:14261",
},
})
time.Sleep(200 * time.Millisecond)
// Check all three truth surfaces agree on vs3:
// 1. Engine (assignment ingress): sender for vs3 exists, vs2 gone.
vs3ID := volPath + "/vs3-node:18080"
vs2ID := volPath + "/vs2-node:18080"
if bs.v2Orchestrator.Registry.Sender(vs3ID) == nil {
t.Fatal("engine: vs3 sender missing")
}
if bs.v2Orchestrator.Registry.Sender(vs2ID) != nil {
t.Fatal("engine: vs2 sender should be removed")
}
// 2. Runtime (recovery manager): no task for vs2.
bs.v2Recovery.mu.Lock()
_, hasVs2Task := bs.v2Recovery.tasks[vs2ID]
bs.v2Recovery.mu.Unlock()
if hasVs2Task {
t.Fatal("runtime: stale vs2 recovery task exists")
}
// 3. Reported (heartbeat output): addresses match vs3.
msgs := bs.CollectBlockVolumeHeartbeat()
hb := findHeartbeatMsg(msgs, volPath)
if hb == nil {
t.Fatal("reported: volume not in heartbeat")
}
if hb.ReplicaDataAddr != "10.0.0.3:14260" {
t.Fatalf("reported: ReplicaDataAddr=%q, want 10.0.0.3:14260", hb.ReplicaDataAddr)
}
t.Log("P10P2 no-split-truth: engine(vs3) + runtime(no vs2 task) + heartbeat(vs3 addr) — all converged")
}
+214
View File
@@ -0,0 +1,214 @@
package weed_server
import (
"path/filepath"
"testing"
"time"
engine "github.com/seaweedfs/seaweedfs/sw-block/engine/replication"
"github.com/seaweedfs/seaweedfs/weed/storage"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol/v2bridge"
)
// ============================================================
// Phase 10 P3: Bounded repeated-assignment idempotence
//
// Proofs:
// 1. Unchanged repeated assignment is idempotent (no rebuild-server relisten)
// 2. Changed assignment still triggers replacement (P2 guard)
// 3. No duplicate runtime side effects after repeated assignment
// ============================================================
func createP3BlockService(t *testing.T) (*BlockService, string) {
t.Helper()
dir := t.TempDir()
volPath := filepath.Join(dir, "vol1.blk")
vol, err := blockvol.CreateBlockVol(volPath, blockvol.CreateOptions{
VolumeSize: 1 * 1024 * 1024,
BlockSize: 4096,
WALSize: 256 * 1024,
})
if err != nil {
t.Fatalf("CreateBlockVol: %v", err)
}
vol.Close()
store := storage.NewBlockVolumeStore()
if _, err := store.AddBlockVolume(volPath, ""); err != nil {
t.Fatalf("AddBlockVolume: %v", err)
}
bs := &BlockService{
blockStore: store,
blockDir: dir,
listenAddr: "127.0.0.1:3260",
localServerID: "vs1-node:18080",
v2Bridge: v2bridge.NewControlBridge(),
v2Orchestrator: engine.NewRecoveryOrchestrator(),
replStates: make(map[string]*volReplState),
}
bs.v2Recovery = NewRecoveryManager(bs)
t.Cleanup(func() {
bs.v2Recovery.Shutdown()
store.Close()
})
return bs, volPath
}
// --- 1. Unchanged repeated assignment is idempotent ---
func TestP10P3_RepeatedAssignment_Idempotent(t *testing.T) {
bs, volPath := createP3BlockService(t)
assignment := blockvol.BlockVolumeAssignment{
Path: volPath,
Epoch: 1,
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
ReplicaServerID: "vs2-node:18080",
ReplicaDataAddr: "10.0.0.2:14260",
ReplicaCtrlAddr: "10.0.0.2:14261",
}
// First assignment: sets up replication + engine session + recovery.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{assignment})
time.Sleep(200 * time.Millisecond)
// Capture V2 engine event count after first assignment.
replicaID := volPath + "/vs2-node:18080"
eventsAfterFirst := len(bs.v2Orchestrator.Log.EventsFor(replicaID))
t.Logf("first assignment: %d engine events", eventsAfterFirst)
// Second assignment: identical. Must be fully idempotent — no V2 engine
// processing, no new sessions, no recovery starts.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{assignment})
time.Sleep(100 * time.Millisecond)
eventsAfterSecond := len(bs.v2Orchestrator.Log.EventsFor(replicaID))
if eventsAfterSecond != eventsAfterFirst {
t.Fatalf("V2 engine events grew: %d → %d (repeated assignment not idempotent)",
eventsAfterFirst, eventsAfterSecond)
}
// Third assignment: same again. Still no new events.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{assignment})
time.Sleep(100 * time.Millisecond)
eventsAfterThird := len(bs.v2Orchestrator.Log.EventsFor(replicaID))
if eventsAfterThird != eventsAfterFirst {
t.Fatalf("V2 engine events grew on third: %d → %d", eventsAfterFirst, eventsAfterThird)
}
// V1 state also unchanged.
dataAddr, _ := bs.GetReplState(volPath)
if dataAddr != "10.0.0.2:14260" {
t.Fatalf("replState: %q", dataAddr)
}
t.Logf("P10P3 idempotent: 3x same → V2 events stable at %d, V1 state unchanged", eventsAfterFirst)
}
// --- 2. Changed assignment triggers replacement (P2 guard) ---
func TestP10P3_ChangedAssignment_StillReplaces(t *testing.T) {
bs, volPath := createP3BlockService(t)
// First assignment: vs2.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{
Path: volPath,
Epoch: 1,
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
ReplicaServerID: "vs2-node:18080",
ReplicaDataAddr: "10.0.0.2:14260",
ReplicaCtrlAddr: "10.0.0.2:14261",
},
})
time.Sleep(100 * time.Millisecond)
dataAddr1, _ := bs.GetReplState(volPath)
if dataAddr1 != "10.0.0.2:14260" {
t.Fatalf("epoch 1: dataAddr=%q", dataAddr1)
}
// Second assignment: CHANGED replica (vs3). Must NOT be idempotent.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{
Path: volPath,
Epoch: 2,
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
ReplicaServerID: "vs3-node:18080",
ReplicaDataAddr: "10.0.0.3:14260",
ReplicaCtrlAddr: "10.0.0.3:14261",
},
})
time.Sleep(100 * time.Millisecond)
// Verify: state updated to vs3.
dataAddr2, _ := bs.GetReplState(volPath)
if dataAddr2 != "10.0.0.3:14260" {
t.Fatalf("epoch 2: dataAddr=%q, want 10.0.0.3:14260 (should have replaced)", dataAddr2)
}
// Engine should have vs3, not vs2.
vs3ID := volPath + "/vs3-node:18080"
vs2ID := volPath + "/vs2-node:18080"
if bs.v2Orchestrator.Registry.Sender(vs3ID) == nil {
t.Fatal("vs3 sender should exist")
}
if bs.v2Orchestrator.Registry.Sender(vs2ID) != nil {
t.Fatal("vs2 sender should be removed")
}
t.Log("P10P3 guard: changed assignment still triggers full replacement")
}
// --- 3. Heartbeat coherent after repeated assignment ---
func TestP10P3_HeartbeatCoherent_AfterRepeated(t *testing.T) {
bs, volPath := createP3BlockService(t)
assignment := blockvol.BlockVolumeAssignment{
Path: volPath,
Epoch: 1,
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
ReplicaServerID: "vs2-node:18080",
ReplicaDataAddr: "10.0.0.2:14260",
ReplicaCtrlAddr: "10.0.0.2:14261",
}
// Apply 3 times.
for i := 0; i < 3; i++ {
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{assignment})
time.Sleep(50 * time.Millisecond)
}
// Heartbeat should report vs2 exactly once (no duplicates in output).
msgs := bs.CollectBlockVolumeHeartbeat()
count := 0
for _, m := range msgs {
if m.Path == volPath {
count++
if m.ReplicaDataAddr != "10.0.0.2:14260" {
t.Fatalf("heartbeat: ReplicaDataAddr=%q", m.ReplicaDataAddr)
}
}
}
if count != 1 {
t.Fatalf("heartbeat: volume appeared %d times, want 1", count)
}
t.Log("P10P3 heartbeat: coherent after 3x repeated assignment")
}
+173
View File
@@ -0,0 +1,173 @@
package weed_server
import (
"testing"
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol/v2bridge"
engine "github.com/seaweedfs/seaweedfs/sw-block/engine/replication"
)
// ============================================================
// Phase 10 P1: Stable identity on the real control wire
//
// Proofs:
// 1. Real ingress: proto wire → decode → ProcessAssignments → ControlBridge → ReplicaID
// 2. Canonical local identity: non-default serverID flows through block/control path
// 3. Fail-closed: missing ServerID on proto wire → replica skipped
// ============================================================
// --- Real ingress proof: proto wire → engine ReplicaID ---
func TestP10P1_RealIngress_ProtoToReplicaID(t *testing.T) {
// Simulate the real control ingress chain:
// 1. Master builds proto assignment with stable ServerID
// 2. Proto is encoded (as it would be over gRPC wire)
// 3. Volume server decodes proto → Go assignment
// 4. ProcessAssignments → ControlBridge → engine
// 5. Engine has sender with correct ReplicaID = <path>/<ServerID>
// Step 1: Master builds proto (simulates master_grpc_server_block.go).
protoAssignment := &master_pb.BlockVolumeAssignment{
Path: "pvc-vol-1",
Epoch: 5,
Role: uint32(blockvol.RolePrimary),
LeaseTtlMs: 30000,
ReplicaServerId: "vs2-node.cluster:18080", // stable ID from registry
ReplicaDataAddr: "10.0.0.2:14260",
ReplicaCtrlAddr: "10.0.0.2:14261",
ReplicaAddrs: []*master_pb.ReplicaAddrMessage{
{
DataAddr: "10.0.0.2:14260",
CtrlAddr: "10.0.0.2:14261",
ServerId: "vs2-node.cluster:18080",
},
},
}
// Step 2: Proto round-trip (simulates gRPC wire encode/decode).
// In real gRPC, this is automatic. Here we verify the Go struct
// carries the fields after proto-to-Go conversion.
goAssignment := blockvol.AssignmentFromProto(protoAssignment)
// Verify: Go wire type preserves the stable ID.
if goAssignment.ReplicaServerID != "vs2-node.cluster:18080" {
t.Fatalf("scalar ReplicaServerID=%q, want vs2-node.cluster:18080", goAssignment.ReplicaServerID)
}
if len(goAssignment.ReplicaAddrs) != 1 || goAssignment.ReplicaAddrs[0].ServerID != "vs2-node.cluster:18080" {
t.Fatalf("multi-replica ServerID not preserved")
}
// Step 3-4: ProcessAssignments → ControlBridge → engine.
bs := &BlockService{
v2Bridge: v2bridge.NewControlBridge(),
v2Orchestrator: engine.NewRecoveryOrchestrator(),
localServerID: "vs1-node.cluster:18080",
}
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{goAssignment})
// Step 5: Verify engine sender has the correct stable ReplicaID.
expectedReplicaID := "pvc-vol-1/vs2-node.cluster:18080"
sender := bs.v2Orchestrator.Registry.Sender(expectedReplicaID)
if sender == nil {
t.Fatalf("engine sender not found for %s", expectedReplicaID)
}
if !sender.HasActiveSession() {
t.Fatal("sender should have active session")
}
t.Logf("P10P1 real ingress: proto(ServerId=%s) → decode → ProcessAssignments → sender(%s) with session",
"vs2-node.cluster:18080", expectedReplicaID)
}
// --- Canonical local identity: non-default serverID ---
func TestP10P1_CanonicalLocalIdentity(t *testing.T) {
// Prove that when the volume server has a canonical ID different from
// plain ip:port, the block/control path uses that canonical ID.
canonicalID := "my-custom-volume-server-id"
bs := &BlockService{
v2Bridge: v2bridge.NewControlBridge(),
v2Orchestrator: engine.NewRecoveryOrchestrator(),
localServerID: canonicalID, // explicitly NOT ip:port shaped
}
// Process a replica assignment that targets THIS volume server.
// The ControlBridge uses localServerID for replica/rebuild assignments.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{
Path: "pvc-vol-2",
Epoch: 3,
Role: uint32(blockvol.RolePrimary),
ReplicaServerID: "other-server-id",
ReplicaDataAddr: "10.0.0.2:9333",
ReplicaCtrlAddr: "10.0.0.2:9334",
},
})
// The sender should use the ServerID from the assignment, not localServerID.
sender := bs.v2Orchestrator.Registry.Sender("pvc-vol-2/other-server-id")
if sender == nil {
t.Fatal("sender should exist with ServerID from assignment, not from localServerID")
}
// Now process a replica assignment where THIS server is the replica.
// The ControlBridge uses localServerID for the local ReplicaID.
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{
Path: "pvc-vol-3",
Epoch: 1,
Role: uint32(blockvol.RoleReplica),
ReplicaDataAddr: "10.0.0.1:14260",
ReplicaCtrlAddr: "10.0.0.1:14261",
},
})
// For a replica assignment, ControlBridge builds ReplicaID as <path>/<localServerID>.
expectedLocalReplicaID := "pvc-vol-3/" + canonicalID
localSender := bs.v2Orchestrator.Registry.Sender(expectedLocalReplicaID)
if localSender == nil {
t.Fatalf("local replica sender not found for %s — canonical ID not used", expectedLocalReplicaID)
}
t.Logf("P10P1 canonical identity: localServerID=%q → local ReplicaID=%s", canonicalID, expectedLocalReplicaID)
}
// --- Fail-closed: missing ServerID on proto wire ---
func TestP10P1_FailClosed_MissingServerID(t *testing.T) {
// When the proto assignment has addresses but no ServerID,
// the ControlBridge must skip that replica (fail closed).
bs := &BlockService{
v2Bridge: v2bridge.NewControlBridge(),
v2Orchestrator: engine.NewRecoveryOrchestrator(),
localServerID: "vs1:18080",
}
bs.ProcessAssignments([]blockvol.BlockVolumeAssignment{
{
Path: "pvc-vol-4",
Epoch: 1,
Role: uint32(blockvol.RolePrimary),
ReplicaDataAddr: "10.0.0.2:9333",
ReplicaCtrlAddr: "10.0.0.2:9334",
// ReplicaServerID intentionally empty — should be skipped.
},
})
// No sender should exist — the replica was skipped due to missing ServerID.
// The ControlBridge logs "scalar replica assignment without ServerID" and skips.
// Check that no sender exists with an address-derived ID.
addressDerivedID := "pvc-vol-4/10.0.0.2:9333"
if bs.v2Orchestrator.Registry.Sender(addressDerivedID) != nil {
t.Fatal("address-derived sender should NOT exist — missing ServerID must fail closed")
}
t.Log("P10P1 fail-closed: missing ServerID → replica skipped, no address-derived fallback")
}
+162 -1
View File
@@ -22,6 +22,8 @@ import (
type volReplState struct {
replicaDataAddr string
replicaCtrlAddr string
// allReplicas stores the full replica set for multi-replica idempotence.
allReplicas []blockvol.ReplicaAddr
}
// NVMeConfig holds NVMe/TCP target configuration passed from CLI flags.
@@ -51,6 +53,11 @@ type BlockService struct {
// V2 engine bridge (Phase 08 P1).
v2Bridge *v2bridge.ControlBridge
v2Orchestrator *engine.RecoveryOrchestrator
v2Recovery *RecoveryManager
// P3: last-applied assignment per volume path for idempotence.
lastAssignMu sync.RWMutex
lastAssign map[string]lastAppliedAssignment
// localServerID: stable identity for this volume server.
// INTERIM: uses listenAddr (transport-shaped). Should be replaced
// with a registry-assigned stable server ID in a later hardening pass.
@@ -62,6 +69,13 @@ func (bs *BlockService) V2Orchestrator() *engine.RecoveryOrchestrator {
return bs.v2Orchestrator
}
// SetServerID sets the stable server identity for V2 control semantics.
// Should be called with the gRPC address that the master knows this VS by,
// replacing the interim listenAddr-based identity.
func (bs *BlockService) SetServerID(id string) {
bs.localServerID = id
}
// WireStateChangeNotify sets up shipper state change callbacks on all
// registered volumes so that degradation/recovery triggers an immediate
// heartbeat via the provided channel. Non-blocking send (buffered chan 1).
@@ -103,6 +117,7 @@ func StartBlockService(listenAddr, blockDir, iqnPrefix, portalAddr string, nvmeC
v2Orchestrator: engine.NewRecoveryOrchestrator(),
localServerID: listenAddr, // INTERIM: transport-shaped, see field doc
}
bs.v2Recovery = NewRecoveryManager(bs)
// iSCSI target setup.
logger := log.New(os.Stderr, "iscsi: ", log.LstdFlags)
@@ -347,13 +362,27 @@ func (bs *BlockService) DeleteBlockVol(name string) error {
// V2 bridge: also delivers each assignment to the V2 engine for recovery ownership.
func (bs *BlockService) ProcessAssignments(assignments []blockvol.BlockVolumeAssignment) {
// V2 bridge: convert and deliver to engine orchestrator (Phase 08 P1).
// P3: skip V2 processing for repeated unchanged assignments.
// P4: RecoveryManager starts/cancels recovery goroutines based on results.
if bs.v2Bridge != nil && bs.v2Orchestrator != nil {
for _, a := range assignments {
// P3 idempotence: skip V2 processing if this assignment is
// materially unchanged from the last one applied for this path.
if bs.isAssignmentUnchanged(a) {
continue
}
bs.recordAppliedAssignment(a)
intent := bs.v2Bridge.ConvertAssignment(a, bs.localServerID)
result := bs.v2Orchestrator.ProcessAssignment(intent)
glog.V(1).Infof("v2bridge: assignment %s epoch=%d → added=%d removed=%d sessions=%d",
a.Path, a.Epoch, len(result.Added), len(result.Removed),
len(result.SessionsCreated)+len(result.SessionsSuperseded))
// P4: drive live recovery execution based on engine result.
if bs.v2Recovery != nil && (len(result.SessionsCreated) > 0 || len(result.SessionsSuperseded) > 0 || len(result.Removed) > 0) {
bs.v2Recovery.HandleAssignmentResult(result, assignments)
}
}
}
@@ -397,6 +426,15 @@ func (bs *BlockService) ProcessAssignments(assignments []blockvol.BlockVolumeAss
// setupPrimaryReplication configures WAL shipping from primary to replica
// and starts the rebuild server (R1-2).
func (bs *BlockService) setupPrimaryReplication(path, replicaDataAddr, replicaCtrlAddr string) {
// P3 idempotence: skip if replica state is unchanged.
bs.replMu.RLock()
existing := bs.replStates[path]
bs.replMu.RUnlock()
if existing != nil && existing.replicaDataAddr == replicaDataAddr && existing.replicaCtrlAddr == replicaCtrlAddr {
// Unchanged repeated assignment — idempotent, no side effects.
return
}
// Compute deterministic rebuild listen address.
_, _, rebuildPort := bs.ReplicationPorts(path)
host := bs.listenAddr
@@ -436,6 +474,17 @@ func (bs *BlockService) setupPrimaryReplication(path, replicaDataAddr, replicaCt
// setupPrimaryReplicationMulti configures WAL shipping from primary to N replicas
// using SetReplicaAddrs (CP8-2: multi-replica support).
func (bs *BlockService) setupPrimaryReplicationMulti(path string, addrs []blockvol.ReplicaAddr) {
// P3 idempotence: skip if ALL replica addresses unchanged.
// Compare full replica set, not just the first entry.
if len(addrs) > 0 {
bs.replMu.RLock()
existing := bs.replStates[path]
bs.replMu.RUnlock()
if existing != nil && bs.multiReplicaUnchanged(path, addrs) {
return
}
}
// Compute deterministic rebuild listen address.
_, _, rebuildPort := bs.ReplicationPorts(path)
host := bs.listenAddr
@@ -459,11 +508,15 @@ func (bs *BlockService) setupPrimaryReplicationMulti(path string, addrs []blockv
if bs.replStates == nil {
bs.replStates = make(map[string]*volReplState)
}
// Store first replica in replState for backward compat heartbeat reporting.
// Store full replica set + first replica for backward compat heartbeat.
if len(addrs) > 0 {
// Copy the addrs slice to avoid aliasing.
copied := make([]blockvol.ReplicaAddr, len(addrs))
copy(copied, addrs)
bs.replStates[path] = &volReplState{
replicaDataAddr: addrs[0].DataAddr,
replicaCtrlAddr: addrs[0].CtrlAddr,
allReplicas: copied,
}
}
bs.replMu.Unlock()
@@ -560,6 +613,15 @@ func (bs *BlockService) DeleteBlockSnapshot(name string, snapID uint32) error {
})
}
// RestoreBlockSnapshot restores the named volume to the specified snapshot.
// This is a destructive operation: all writes after the snapshot are lost.
func (bs *BlockService) RestoreBlockSnapshot(name string, snapID uint32) error {
path := bs.volumePath(name)
return bs.blockStore.WithVolume(path, func(vol *blockvol.BlockVol) error {
return vol.RestoreSnapshot(snapID)
})
}
// ListBlockSnapshots lists all snapshots on the named volume.
func (bs *BlockService) ListBlockSnapshots(name string) ([]blockvol.SnapshotInfo, uint64, error) {
path := bs.volumePath(name)
@@ -656,6 +718,101 @@ func (bs *BlockService) CollectBlockVolumeHeartbeat() []blockvol.BlockVolumeInfo
return msgs
}
// multiReplicaUnchanged checks if the full replica set is unchanged.
func (bs *BlockService) multiReplicaUnchanged(path string, addrs []blockvol.ReplicaAddr) bool {
bs.replMu.RLock()
defer bs.replMu.RUnlock()
existing, ok := bs.replStates[path]
if !ok || existing == nil {
return false
}
if len(existing.allReplicas) != len(addrs) {
return false
}
for i := range addrs {
if existing.allReplicas[i].DataAddr != addrs[i].DataAddr ||
existing.allReplicas[i].CtrlAddr != addrs[i].CtrlAddr ||
existing.allReplicas[i].ServerID != addrs[i].ServerID {
return false
}
}
return true
}
// --- P3: Assignment idempotence ---
// lastAppliedAssignment stores the full assignment for idempotence comparison.
// Keyed by volume path.
type lastAppliedAssignment struct {
Path string
Epoch uint64
Role uint32
ReplicaServerID string
ReplicaDataAddr string
ReplicaCtrlAddr string
ReplicaAddrs []blockvol.ReplicaAddr
}
func lastAppliedFrom(a blockvol.BlockVolumeAssignment) lastAppliedAssignment {
// Copy the slice to avoid aliasing.
var addrs []blockvol.ReplicaAddr
if len(a.ReplicaAddrs) > 0 {
addrs = make([]blockvol.ReplicaAddr, len(a.ReplicaAddrs))
copy(addrs, a.ReplicaAddrs)
}
return lastAppliedAssignment{
Path: a.Path,
Epoch: a.Epoch,
Role: a.Role,
ReplicaServerID: a.ReplicaServerID,
ReplicaDataAddr: a.ReplicaDataAddr,
ReplicaCtrlAddr: a.ReplicaCtrlAddr,
ReplicaAddrs: addrs,
}
}
func (la lastAppliedAssignment) equals(a blockvol.BlockVolumeAssignment) bool {
if la.Path != a.Path || la.Epoch != a.Epoch || la.Role != a.Role {
return false
}
if la.ReplicaServerID != a.ReplicaServerID || la.ReplicaDataAddr != a.ReplicaDataAddr || la.ReplicaCtrlAddr != a.ReplicaCtrlAddr {
return false
}
if len(la.ReplicaAddrs) != len(a.ReplicaAddrs) {
return false
}
for i := range la.ReplicaAddrs {
if la.ReplicaAddrs[i].DataAddr != a.ReplicaAddrs[i].DataAddr ||
la.ReplicaAddrs[i].CtrlAddr != a.ReplicaAddrs[i].CtrlAddr ||
la.ReplicaAddrs[i].ServerID != a.ReplicaAddrs[i].ServerID {
return false
}
}
return true
}
func (bs *BlockService) isAssignmentUnchanged(a blockvol.BlockVolumeAssignment) bool {
bs.lastAssignMu.RLock()
defer bs.lastAssignMu.RUnlock()
if bs.lastAssign == nil {
return false
}
last, ok := bs.lastAssign[a.Path]
if !ok {
return false
}
return last.equals(a)
}
func (bs *BlockService) recordAppliedAssignment(a blockvol.BlockVolumeAssignment) {
bs.lastAssignMu.Lock()
defer bs.lastAssignMu.Unlock()
if bs.lastAssign == nil {
bs.lastAssign = make(map[string]lastAppliedAssignment)
}
bs.lastAssign[a.Path] = lastAppliedFrom(a)
}
// ReplicationPorts computes deterministic replication ports for a volume.
// Ports are derived from a hash of the volume path offset from the iSCSI base port.
func (bs *BlockService) ReplicationPorts(volPath string) (dataPort, ctrlPort, rebuildPort int) {
@@ -681,6 +838,10 @@ func (bs *BlockService) Shutdown() {
return
}
glog.V(0).Infof("block service: shutting down...")
// P4: drain active recovery goroutines before closing volumes.
if bs.v2Recovery != nil {
bs.v2Recovery.Shutdown()
}
if bs.nvmeServer != nil {
bs.nvmeServer.Close()
}
@@ -106,11 +106,13 @@ func AssignmentToProto(a BlockVolumeAssignment) *master_pb.BlockVolumeAssignment
ReplicaDataAddr: a.ReplicaDataAddr,
ReplicaCtrlAddr: a.ReplicaCtrlAddr,
RebuildAddr: a.RebuildAddr,
ReplicaServerId: a.ReplicaServerID, // V2: stable identity
}
for _, ra := range a.ReplicaAddrs {
pb.ReplicaAddrs = append(pb.ReplicaAddrs, &master_pb.ReplicaAddrMessage{
DataAddr: ra.DataAddr,
CtrlAddr: ra.CtrlAddr,
ServerId: ra.ServerID, // V2: stable identity
})
}
return pb
@@ -123,11 +125,12 @@ func AssignmentFromProto(p *master_pb.BlockVolumeAssignment) BlockVolumeAssignme
return BlockVolumeAssignment{}
}
a := BlockVolumeAssignment{
Path: p.Path,
Epoch: p.Epoch,
Role: p.Role,
LeaseTtlMs: p.LeaseTtlMs,
RebuildAddr: p.RebuildAddr,
Path: p.Path,
Epoch: p.Epoch,
Role: p.Role,
LeaseTtlMs: p.LeaseTtlMs,
RebuildAddr: p.RebuildAddr,
ReplicaServerID: p.ReplicaServerId, // V2: stable identity
}
if len(p.ReplicaAddrs) > 0 {
// Multi-replica: populate ReplicaAddrs, leave scalar fields empty.
@@ -135,6 +138,7 @@ func AssignmentFromProto(p *master_pb.BlockVolumeAssignment) BlockVolumeAssignme
a.ReplicaAddrs = append(a.ReplicaAddrs, ReplicaAddr{
DataAddr: ra.DataAddr,
CtrlAddr: ra.CtrlAddr,
ServerID: ra.ServerId, // V2: stable identity
})
}
} else {
@@ -464,3 +464,76 @@ func TestInfoMessage_HealthFieldsZeroDefault(t *testing.T) {
t.Fatal("ReplicaDegraded zero default should be false")
}
}
// --- Phase 10 P1: Stable server identity on the proto wire ---
func TestP10P1_ProtoRoundTrip_ScalarServerID(t *testing.T) {
a := BlockVolumeAssignment{
Path: "/data/vol1.blk",
Epoch: 5,
Role: RoleToWire(RolePrimary),
ReplicaDataAddr: "10.0.0.2:9333",
ReplicaCtrlAddr: "10.0.0.2:9334",
ReplicaServerID: "vs2-grpc:18080",
RebuildAddr: "10.0.0.1:5000",
}
pb := AssignmentToProto(a)
if pb.ReplicaServerId != "vs2-grpc:18080" {
t.Fatalf("proto encode: ReplicaServerId=%q, want vs2-grpc:18080", pb.ReplicaServerId)
}
decoded := AssignmentFromProto(pb)
if decoded.ReplicaServerID != "vs2-grpc:18080" {
t.Fatalf("proto decode: ReplicaServerID=%q, want vs2-grpc:18080", decoded.ReplicaServerID)
}
}
func TestP10P1_ProtoRoundTrip_MultiReplicaServerID(t *testing.T) {
a := BlockVolumeAssignment{
Path: "/data/vol1.blk",
Epoch: 5,
Role: RoleToWire(RolePrimary),
ReplicaAddrs: []ReplicaAddr{
{DataAddr: "10.0.0.2:9333", CtrlAddr: "10.0.0.2:9334", ServerID: "vs2-grpc:18080"},
{DataAddr: "10.0.0.3:9333", CtrlAddr: "10.0.0.3:9334", ServerID: "vs3-grpc:18080"},
},
}
pb := AssignmentToProto(a)
if pb.ReplicaAddrs[0].ServerId != "vs2-grpc:18080" {
t.Fatalf("encode[0]: ServerId=%q", pb.ReplicaAddrs[0].ServerId)
}
if pb.ReplicaAddrs[1].ServerId != "vs3-grpc:18080" {
t.Fatalf("encode[1]: ServerId=%q", pb.ReplicaAddrs[1].ServerId)
}
decoded := AssignmentFromProto(pb)
if decoded.ReplicaAddrs[0].ServerID != "vs2-grpc:18080" {
t.Fatalf("decode[0]: ServerID=%q", decoded.ReplicaAddrs[0].ServerID)
}
if decoded.ReplicaAddrs[1].ServerID != "vs3-grpc:18080" {
t.Fatalf("decode[1]: ServerID=%q", decoded.ReplicaAddrs[1].ServerID)
}
}
func TestP10P1_ProtoRoundTrip_MissingServerID_NotSynthesized(t *testing.T) {
a := BlockVolumeAssignment{
Path: "/data/vol1.blk",
Epoch: 5,
Role: RoleToWire(RolePrimary),
ReplicaDataAddr: "10.0.0.2:9333",
ReplicaCtrlAddr: "10.0.0.2:9334",
// ReplicaServerID intentionally empty.
}
pb := AssignmentToProto(a)
if pb.ReplicaServerId != "" {
t.Fatalf("encode: ReplicaServerId=%q, want empty", pb.ReplicaServerId)
}
decoded := AssignmentFromProto(pb)
if decoded.ReplicaServerID != "" {
t.Fatalf("decode: ReplicaServerID=%q, want empty (not synthesized)", decoded.ReplicaServerID)
}
}