package main import ( "bytes" "encoding/csv" "fmt" "log" "strings" ) type Box struct { Addr string `json:"addr"` User string `json:"user"` Key string `json:"key"` Name string `json:"-"` ssh *SSHConfig zfs *ZFSConfig } func (b *Box) ZFSTakeSnapshot(schedule, path string) (err error) { if *debugFlag { log.Printf("Box.ZFSTakeSnapshot : %s : Taking snapshot on %s for %s", b.Name, path, schedule) } err = b.SnapshotInitialize() if err != nil { return } b.zfs.M.Lock() defer b.zfs.M.Unlock() timestamp := cfg.Now.Format("2006-01-02_15.04.05") name := fmt.Sprintf("%s-%s--%s", schedule, timestamp, cfg.Zfsnap[schedule]) _, err = b.ssh.exec("zfs snapshot " + path + "@" + name) if err != nil { return } b.zfs.SnapshotAdded = true b.zfs.SnapshotList = append(b.zfs.SnapshotList, Snapshot(path+"@"+name)) return } func (b *Box) ZFSGetLastSnapshot(path string) (last Snapshot, err error) { if *debugFlag { log.Printf("Box.ZFSGetLastSnapshot : %s : Start %s (%d snapshots)", b.Name, path, len(b.zfs.SnapshotList)) } err = b.SnapshotInitialize() if err != nil { return } b.zfs.M.Lock() defer b.zfs.M.Unlock() for _, v := range b.zfs.SnapshotList { if v.Path() == path { last = v } } if len(string(last)) == 0 { err = fmt.Errorf("no snapshot") } return } func (b *Box) ZFSIsLastSnapshot(src Snapshot) (is bool, err error) { if *debugFlag { log.Printf("Box.ZFSIsLastSnapshot : %s : Start %s", b.Name, string(src)) } err = b.SnapshotInitialize() if err != nil { return } b.zfs.M.Lock() defer b.zfs.M.Unlock() if *debugFlag { log.Printf("SSHConfig.isLastSnapshot : %s : Start %s", b.Name, string(src)) } _, err = b.ZFSGetNextSnapshot(src) if err != nil { if err.Error() == "no snapshot" { is = true err = nil } } else { is = false } return } func (b *Box) ZFSGetFirstSnapshot(path string) (first Snapshot, err error) { if *debugFlag { log.Printf("SSHConfig.getFirstSnapshot : %s : Start %s", b.Name, path) } err = b.SnapshotInitialize() if err != nil { return } b.zfs.M.Lock() defer b.zfs.M.Unlock() for _, v := range b.zfs.SnapshotList { if v.Path() == path { first = v return } } err = fmt.Errorf("no snapshot") return } func (b *Box) ZFSGetNextSnapshot(src Snapshot) (next Snapshot, err error) { if *debugFlag { log.Printf("Box.ZFSGetNextSnapshot : %s : Start %s", b.Name, string(src)) } err = b.SnapshotInitialize() if err != nil { return } b.zfs.M.Lock() defer b.zfs.M.Unlock() for id, v := range b.zfs.SnapshotList { if v == src { if len(b.zfs.SnapshotList) > id+1 { next = b.zfs.SnapshotList[id+1] if next.Path() == src.Path() { return } else { err = fmt.Errorf("no snapshot") return } } else { err = fmt.Errorf("no snapshot") return } } } err = fmt.Errorf("no snapshot") return } func (b *Box) ZFSUpdateSnapshotList() (err error) { if *debugFlag { log.Printf("Box.ZFSUpdateSnapshotList : %s : Start", b.Name) } b.zfs.M.Lock() if b.zfs.SnapshotDeleted || b.zfs.SnapshotAdded { b.zfs.SnapshotInitialized = false } b.zfs.M.Unlock() err = b.SnapshotInitialize() return } func (b *Box) ZFSGetSnapshotList() (snaps []Snapshot, err error) { if *debugFlag { log.Printf("Box.ZFSGetSnapshotList : %s : Start", b.Name) } err = b.SnapshotInitialize() if err != nil { return } b.zfs.M.Lock() defer b.zfs.M.Unlock() snaps = b.zfs.SnapshotList return } func (b *Box) SnapshotInitialize() (err error) { b.zfs.M.Lock() defer b.zfs.M.Unlock() if b.zfs.SnapshotInitialized { return nil } if *debugFlag { log.Printf("Box.SnapshotInitialize : %s : Start", b.Name) } b.zfs.SnapshotList = make([]Snapshot, 0) var buf *bytes.Buffer buf, err = b.SSHExec("zfs list -H -t snapshot -o name") csvReader := csv.NewReader(buf) csvReader.Comma = '\t' csvReader.FieldsPerRecord = 1 csvData, err := csvReader.ReadAll() if err != nil { if *debugFlag { log.Printf("Box.SnapshotInitialize : %s : csvReader.ReadAll() : %s", b.Name, err) } return err } for _, rec := range csvData { b.zfs.SnapshotList = append(b.zfs.SnapshotList, Snapshot(rec[0])) } if *debugFlag { log.Printf("Box.SnapshotInitialize : %s : read %d zfs snapshots", b.Name, len(b.zfs.SnapshotList)) } b.zfs.SnapshotInitialized = true b.zfs.SnapshotAdded = false b.zfs.SnapshotDeleted = false return nil } func (b *Box) ZFSUpdateList() (err error) { if *debugFlag { log.Printf("Box.ZFSUpdateList : %s : Start", b.Name) } b.zfs.M.Lock() if b.zfs.ZFSDeleted || b.zfs.ZFSAdded { b.zfs.ZFSInitialized = false } b.zfs.M.Unlock() err = b.ZFSInitialize() return } func (b *Box) ZFSIsZFS(path string) bool { if *debugFlag { log.Printf("Box.ZFSIsZFS : %s : Start %s", b.Name, path) } err := b.ZFSInitialize() if err != nil { return false } b.zfs.M.Lock() defer b.zfs.M.Unlock() if _, ok := b.zfs.ZFSMap[path]; ok { return true } return false } func (b *Box) ZFSCreateZFS(path string) (err error) { if *debugFlag { log.Printf("Box.ZFSCreateZFS : %s : Start %s", b.Name, path) } err = b.ZFSInitialize() if err != nil { return } b.zfs.M.Lock() defer b.zfs.M.Unlock() p := strings.Split(path, `/`) var base string for _, d := range p { if base == "" { base = d } else { base = base + `/` + d } if _, ok := b.zfs.ZFSMap[base]; !ok { if *debugFlag { log.Printf("Box.ZFSCreateZFS : Creating %s:%s", b.Name, base) } _, err = b.SSHExec("zfs create -o mountpoint=none " + base) if err != nil { if *debugFlag { log.Printf("Box.ZFSCreateZFS : %s : SSHExec : %s", b.Name, err) } return } b.zfs.ZFSMap[base] = "none" b.zfs.ZFSAdded = true } } return } func (b *Box) ZFSInitialize() (err error) { b.zfs.M.Lock() defer b.zfs.M.Unlock() if b.zfs.ZFSInitialized { return nil } if *debugFlag { log.Printf("Box.ZFSInitialize : %s : Start", b.Name) } b.zfs.ZFSMap = make(map[string]string) var buf *bytes.Buffer buf, err = b.SSHExec("zfs list -H -o name,mountpoint") csvReader := csv.NewReader(buf) csvReader.Comma = '\t' csvReader.FieldsPerRecord = 2 csvData, err := csvReader.ReadAll() if err != nil { if *debugFlag { log.Printf("Box.ZFSInitialize : %s : csvReader.ReadAll() : %s", b.Name, err) } return err } for _, rec := range csvData { b.zfs.ZFSMap[rec[0]] = rec[1] } b.zfs.ZFSInitialized = true b.zfs.ZFSAdded = false b.zfs.ZFSDeleted = false return nil } func (b *Box) SSHExec(cmd string) (buf *bytes.Buffer, err error) { buf, err = b.ssh.exec(cmd) return }