139 lines
3.8 KiB
Go
139 lines
3.8 KiB
Go
package zfs
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/example/storage-appliance/internal/domain"
|
|
"github.com/example/storage-appliance/internal/infra/osexec"
|
|
)
|
|
|
|
type Adapter struct {
|
|
Runner osexec.Runner
|
|
}
|
|
|
|
func NewAdapter(runner osexec.Runner) *Adapter { return &Adapter{Runner: runner} }
|
|
|
|
func (a *Adapter) ListPools(ctx context.Context) ([]domain.Pool, error) {
|
|
out, errOut, code, err := osexec.ExecWithRunner(a.Runner, ctx, "zpool", "list", "-H", "-o", "name,health,size")
|
|
_ = errOut
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if code != 0 {
|
|
return nil, fmt.Errorf("zpool list returned %d: %s", code, out)
|
|
}
|
|
var pools []domain.Pool
|
|
lines := strings.Split(strings.TrimSpace(out), "\n")
|
|
for _, l := range lines {
|
|
parts := strings.Split(l, "\t")
|
|
if len(parts) >= 3 {
|
|
pools = append(pools, domain.Pool{Name: parts[0], Health: parts[1], Capacity: parts[2]})
|
|
}
|
|
}
|
|
return pools, nil
|
|
}
|
|
|
|
func (a *Adapter) GetPoolStatus(ctx context.Context, pool string) (domain.PoolHealth, error) {
|
|
out, _, _, err := osexec.ExecWithRunner(a.Runner, ctx, "zpool", "status", pool)
|
|
if err != nil {
|
|
return domain.PoolHealth{}, err
|
|
}
|
|
// heuristic: find HEALTH: lines or scan lines
|
|
status := "UNKNOWN"
|
|
detail := ""
|
|
for _, line := range strings.Split(out, "\n") {
|
|
if strings.Contains(line, "state:") || strings.Contains(line, "health:") {
|
|
detail = detail + line + "\n"
|
|
if strings.Contains(line, "ONLINE") || strings.Contains(line, "ONLINE") {
|
|
status = "ONLINE"
|
|
}
|
|
if strings.Contains(line, "DEGRADED") {
|
|
status = "DEGRADED"
|
|
}
|
|
}
|
|
}
|
|
return domain.PoolHealth{Pool: pool, Status: status, Detail: detail}, nil
|
|
}
|
|
|
|
func (a *Adapter) CreatePoolSync(ctx context.Context, name string, vdevs []string) error {
|
|
args := append([]string{"create", name}, vdevs...)
|
|
_, stderr, code, err := osexec.ExecWithRunner(a.Runner, ctx, "zpool", args...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if code != 0 {
|
|
return fmt.Errorf("zpool create failed: %s", stderr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *Adapter) ListDatasets(ctx context.Context, pool string) ([]domain.Dataset, error) {
|
|
out, _, _, err := osexec.ExecWithRunner(a.Runner, ctx, "zfs", "list", "-H", "-o", "name,type", "-r", pool)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var res []domain.Dataset
|
|
for _, l := range strings.Split(strings.TrimSpace(out), "\n") {
|
|
parts := strings.Split(l, "\t")
|
|
if len(parts) >= 2 {
|
|
res = append(res, domain.Dataset{Name: parts[0], Pool: pool, Type: parts[1]})
|
|
}
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (a *Adapter) CreateDataset(ctx context.Context, name string, props map[string]string) error {
|
|
args := []string{"create", name}
|
|
for k, v := range props {
|
|
args = append(args, "-o", fmt.Sprintf("%s=%s", k, v))
|
|
}
|
|
_, stderr, code, err := osexec.ExecWithRunner(a.Runner, ctx, "zfs", args...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if code != 0 {
|
|
return fmt.Errorf("zfs create failed: %s", stderr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *Adapter) Snapshot(ctx context.Context, dataset, snapName string) error {
|
|
name := fmt.Sprintf("%s@%s", dataset, snapName)
|
|
_, stderr, code, err := osexec.ExecWithRunner(a.Runner, ctx, "zfs", "snapshot", name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if code != 0 {
|
|
return fmt.Errorf("zfs snapshot failed: %s", stderr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *Adapter) ScrubStart(ctx context.Context, pool string) error {
|
|
_, stderr, code, err := osexec.ExecWithRunner(a.Runner, ctx, "zpool", "scrub", pool)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if code != 0 {
|
|
return fmt.Errorf("zpool scrub failed: %s", stderr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *Adapter) ScrubStatus(ctx context.Context, pool string) (string, error) {
|
|
out, _, _, err := osexec.ExecWithRunner(a.Runner, ctx, "zpool", "status", pool)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// Find scan line
|
|
for _, line := range strings.Split(out, "\n") {
|
|
if strings.Contains(line, "scan: ") {
|
|
return strings.TrimSpace(line), nil
|
|
}
|
|
}
|
|
return "no-scan-status", nil
|
|
}
|