mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-20 13:30:46 +02:00
Pinner wired to real retention: - NewPinner calls vol.SetV2RetentionFloor(p.MinWALRetentionFloor) - Flusher.RetentionFloorFn() / SetRetentionFloorFn() exposed - SetV2RetentionFloor chains with existing shipper retention floor - Holds actually prevent WAL reclaim (not just tracked state) Executor uses real WAL scan: - BlockVol.ScanWALEntries(fromLSN, callback) wraps wal.ScanFrom with real fd, walOffset, checkpointLSN - Executor.StreamWALEntries uses ScanWALEntries (not stub) - Reads real WAL entries, tracks highest LSN scanned CommittedLSN mapping: - Explicitly documented as interim V1 model (committed = checkpointed) - Will diverge when V2 distributed commit separates from local flush Carry-forward: - TransferSnapshot/TransferFullBase/TruncateWAL: stubs (need extent I/O) - Control intent from confirmed failover: deferred Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
163 lines
5.7 KiB
Go
163 lines
5.7 KiB
Go
package actions
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
tr "github.com/seaweedfs/seaweedfs/weed/storage/blockvol/testrunner"
|
|
)
|
|
|
|
// RegisterCleanupActions registers environment cleanup and device discovery actions.
|
|
func RegisterCleanupActions(r *tr.Registry) {
|
|
r.RegisterFunc("pre_run_cleanup", tr.TierCore, preRunCleanup)
|
|
r.RegisterFunc("nvme_connect_direct", tr.TierBlock, nvmeConnectDirect)
|
|
r.RegisterFunc("nvme_disconnect_all", tr.TierBlock, nvmeDisconnectAll)
|
|
}
|
|
|
|
// preRunCleanup kills stale processes, unmounts filesystems, disconnects
|
|
// NVMe/iSCSI sessions, and verifies ports are free. Runs on a specified node.
|
|
//
|
|
// Params:
|
|
// - kill_patterns: comma-separated process names to kill (default: "weed,iscsi-target,postgres")
|
|
// - unmount: comma-separated mount points to unmount
|
|
// - nvme_disconnect: "true" to disconnect all NVMe sessions
|
|
// - iscsi_logout_prefix: IQN prefix to logout (e.g., "iqn.2024-01.com.seaweedfs")
|
|
// - check_ports: comma-separated ports that must be free after cleanup
|
|
//
|
|
// Always succeeds (ignore_error semantics built in) — logs warnings but doesn't fail the scenario.
|
|
func preRunCleanup(ctx context.Context, actx *tr.ActionContext, act tr.Action) (map[string]string, error) {
|
|
node, err := GetNode(actx, act.Node)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("pre_run_cleanup: %w", err)
|
|
}
|
|
|
|
var cleaned []string
|
|
|
|
// Kill stale processes.
|
|
patterns := act.Params["kill_patterns"]
|
|
if patterns == "" {
|
|
patterns = "weed,iscsi-target,postgres"
|
|
}
|
|
for _, p := range strings.Split(patterns, ",") {
|
|
p = strings.TrimSpace(p)
|
|
if p == "" {
|
|
continue
|
|
}
|
|
node.RunRoot(ctx, fmt.Sprintf("pkill -9 %s 2>/dev/null || true", p))
|
|
cleaned = append(cleaned, "kill:"+p)
|
|
}
|
|
|
|
// Unmount filesystems.
|
|
if mounts := act.Params["unmount"]; mounts != "" {
|
|
for _, m := range strings.Split(mounts, ",") {
|
|
m = strings.TrimSpace(m)
|
|
if m == "" {
|
|
continue
|
|
}
|
|
node.RunRoot(ctx, fmt.Sprintf("umount -l %s 2>/dev/null || true", m))
|
|
cleaned = append(cleaned, "umount:"+m)
|
|
}
|
|
}
|
|
|
|
// Disconnect NVMe.
|
|
if act.Params["nvme_disconnect"] == "true" {
|
|
node.RunRoot(ctx, "nvme disconnect-all 2>/dev/null || true")
|
|
cleaned = append(cleaned, "nvme:disconnect-all")
|
|
}
|
|
|
|
// Logout iSCSI sessions.
|
|
if prefix := act.Params["iscsi_logout_prefix"]; prefix != "" {
|
|
node.RunRoot(ctx, fmt.Sprintf(
|
|
"iscsiadm -m session 2>/dev/null | grep '%s' | awk '{print $4}' | while read iqn; do "+
|
|
"iscsiadm -m node -T $iqn --logout 2>/dev/null; "+
|
|
"iscsiadm -m node -T $iqn -o delete 2>/dev/null; done || true", prefix))
|
|
cleaned = append(cleaned, "iscsi:"+prefix)
|
|
}
|
|
|
|
// Check ports are free.
|
|
if ports := act.Params["check_ports"]; ports != "" {
|
|
for _, p := range strings.Split(ports, ",") {
|
|
p = strings.TrimSpace(p)
|
|
stdout, _, _, _ := node.RunRoot(ctx, fmt.Sprintf("ss -tlnp | grep ':%s ' | head -1", p))
|
|
if strings.TrimSpace(stdout) != "" {
|
|
actx.Log(" WARNING: port %s still in use after cleanup: %s", p, strings.TrimSpace(stdout))
|
|
}
|
|
}
|
|
}
|
|
|
|
actx.Log(" cleanup: %s", strings.Join(cleaned, ", "))
|
|
return map[string]string{"value": strings.Join(cleaned, ",")}, nil
|
|
}
|
|
|
|
// nvmeConnect connects to an NVMe-oF target and returns the discovered device path.
|
|
// Handles modprobe, disconnect stale sessions, connect, and device discovery.
|
|
//
|
|
// Params:
|
|
// - target_addr: NVMe target IP (required)
|
|
// - target_port: NVMe target port (default: "4420")
|
|
// - nqn: NVMe subsystem NQN (required)
|
|
// - transport: "tcp" or "rdma" (default: "tcp")
|
|
// - expected_size: expected device size for discovery (e.g., "2G") (optional)
|
|
//
|
|
// Returns: value = device path (e.g., "/dev/nvme1n1")
|
|
func nvmeConnectDirect(ctx context.Context, actx *tr.ActionContext, act tr.Action) (map[string]string, error) {
|
|
node, err := GetNode(actx, act.Node)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("nvme_connect: %w", err)
|
|
}
|
|
|
|
addr := act.Params["target_addr"]
|
|
if addr == "" {
|
|
return nil, fmt.Errorf("nvme_connect: target_addr required")
|
|
}
|
|
port := paramDefault(act.Params, "target_port", "4420")
|
|
nqn := act.Params["nqn"]
|
|
if nqn == "" {
|
|
return nil, fmt.Errorf("nvme_connect: nqn required")
|
|
}
|
|
transport := paramDefault(act.Params, "transport", "tcp")
|
|
|
|
// Ensure NVMe-TCP kernel module is loaded.
|
|
node.RunRoot(ctx, fmt.Sprintf("modprobe nvme_%s 2>/dev/null || true", transport))
|
|
|
|
// Connect.
|
|
cmd := fmt.Sprintf("nvme connect -t %s -a %s -s %s -n %s 2>&1", transport, addr, port, nqn)
|
|
stdout, stderr, code, err := node.RunRoot(ctx, cmd)
|
|
if err != nil || code != 0 {
|
|
return nil, fmt.Errorf("nvme_connect: code=%d stdout=%s stderr=%s err=%v", code, stdout, stderr, err)
|
|
}
|
|
|
|
// Wait for device to appear.
|
|
node.Run(ctx, "sleep 2")
|
|
|
|
// Discover the device. Strategy: find NVMe namespace matching expected size.
|
|
expectedSize := act.Params["expected_size"]
|
|
var devCmd string
|
|
if expectedSize != "" {
|
|
devCmd = fmt.Sprintf("lsblk -dpno NAME,SIZE | grep '%s' | head -1 | awk '{print $1}'", expectedSize)
|
|
} else {
|
|
// Fall back to newest NVMe device (not nvme0 which is the boot disk).
|
|
devCmd = "lsblk -dpno NAME | grep nvme | grep -v nvme0 | tail -1"
|
|
}
|
|
|
|
devOut, _, _, _ := node.RunRoot(ctx, devCmd)
|
|
device := strings.TrimSpace(devOut)
|
|
if device == "" {
|
|
return nil, fmt.Errorf("nvme_connect: connected but no device found (expected_size=%s)", expectedSize)
|
|
}
|
|
|
|
actx.Log(" nvme connected: %s → %s", nqn, device)
|
|
return map[string]string{"value": device}, nil
|
|
}
|
|
|
|
// nvmeDisconnectAll disconnects all NVMe-oF sessions on the node.
|
|
func nvmeDisconnectAll(ctx context.Context, actx *tr.ActionContext, act tr.Action) (map[string]string, error) {
|
|
node, err := GetNode(actx, act.Node)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("nvme_disconnect_all: %w", err)
|
|
}
|
|
node.RunRoot(ctx, "nvme disconnect-all 2>/dev/null || true")
|
|
return nil, nil
|
|
}
|