330 lines
8.4 KiB
Go
330 lines
8.4 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/csv"
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type BoxZfs struct {
|
|
filesystems map[string]*ZfsFs
|
|
box *Box
|
|
online bool
|
|
mx sync.Mutex
|
|
}
|
|
|
|
type ZfsFs struct {
|
|
path string
|
|
managed bool
|
|
backedUp bool
|
|
zfs *BoxZfs
|
|
snapshots map[string]*ZfsSnapshot
|
|
srcApps []*App
|
|
destApps []*App
|
|
mx sync.Mutex
|
|
}
|
|
|
|
type ZfsSnapshot struct {
|
|
name string
|
|
fs *ZfsFs
|
|
}
|
|
|
|
func (z *BoxZfs) Open() error {
|
|
log.WithFields(log.Fields{"name": z.box.name}).Debugf("starting")
|
|
defer log.WithFields(log.Fields{"name": z.box.name}).Debugf("done")
|
|
|
|
z.mx.Lock()
|
|
defer z.mx.Unlock()
|
|
|
|
if z.online {
|
|
return nil
|
|
}
|
|
|
|
z.filesystems = make(map[string]*ZfsFs)
|
|
|
|
zfsList, err := z.box.Exec("zfs list -H -t filesystem -o name,mountpoint")
|
|
if err != nil {
|
|
log.WithFields(log.Fields{"name": z.box.name, "call": "Exec", "attr": "zfs list -H -t filesystem -o name,mountpoint", "error": err}).Errorf("")
|
|
return err
|
|
}
|
|
|
|
csvReader := csv.NewReader(bytes.NewBufferString(zfsList))
|
|
csvReader.Comma = '\t'
|
|
csvReader.FieldsPerRecord = 2
|
|
|
|
csvData, err := csvReader.ReadAll()
|
|
if err != nil {
|
|
log.WithFields(log.Fields{"name": z.box.name, "call": "csvReader.ReadAll", "error": err}).Errorf("")
|
|
return err
|
|
}
|
|
|
|
for _, rec := range csvData {
|
|
log.WithFields(log.Fields{"name": z.box.name, "zfs-name": rec[0], "zfs-mount": rec[1]}).Debugf("zfs list -t filesystem")
|
|
if rec[1] != "legacy" {
|
|
fs := &ZfsFs{
|
|
path: rec[0],
|
|
zfs: z,
|
|
snapshots: make(map[string]*ZfsSnapshot),
|
|
}
|
|
z.filesystems[rec[0]] = fs
|
|
log.WithFields(log.Fields{"name": z.box.name, "fs": rec[0]}).Infof("new filesystem")
|
|
}
|
|
}
|
|
|
|
log.WithFields(log.Fields{"name": z.box.name, "call": "csvReader.ReadAll"}).Infof("")
|
|
|
|
zfsList, err = z.box.Exec("zfs list -H -t snapshot -o name")
|
|
if err != nil {
|
|
log.WithFields(log.Fields{"name": z.box.name, "call": "Exec", "attr": "zfs list -H -t snapshot -o name", "error": err}).Errorf("")
|
|
return err
|
|
}
|
|
|
|
csvReader = csv.NewReader(bytes.NewBufferString(zfsList))
|
|
csvReader.Comma = '\t'
|
|
csvReader.FieldsPerRecord = 1
|
|
|
|
csvData, err = csvReader.ReadAll()
|
|
if err != nil {
|
|
log.WithFields(log.Fields{"name": z.box.name, "call": "csvReader.ReadAll", "error": err}).Errorf("")
|
|
return err
|
|
}
|
|
|
|
for _, rec := range csvData {
|
|
log.WithFields(log.Fields{"name": z.box.name, "zfs-snapshot": rec[0]}).Debugf("zfs list -t snapshot")
|
|
|
|
s := strings.Split(rec[0], `@`)
|
|
if fs, ok := z.filesystems[s[0]]; ok {
|
|
snap := &ZfsSnapshot{
|
|
name: s[1],
|
|
fs: fs,
|
|
}
|
|
fs.snapshots[s[1]] = snap
|
|
log.WithFields(log.Fields{"name": z.box.name, "fs": s[0], "snapshot": s[1]}).Infof("new snapshot")
|
|
}
|
|
}
|
|
|
|
zfsList, err = z.box.Exec("zfs get -H -o name,value " + zfsManagedPropertyName)
|
|
if err != nil {
|
|
log.WithFields(log.Fields{"name": z.box.name, "call": "Exec", "attr": "zfs get -H -o name,value,source " + zfsManagedPropertyName, "error": err}).Errorf("")
|
|
return err
|
|
}
|
|
|
|
csvReader = csv.NewReader(bytes.NewBufferString(zfsList))
|
|
csvReader.Comma = '\t'
|
|
csvReader.FieldsPerRecord = 2
|
|
|
|
csvData, err = csvReader.ReadAll()
|
|
if err != nil {
|
|
log.WithFields(log.Fields{"name": z.box.name, "call": "csvReader.ReadAll", "error": err}).Errorf("")
|
|
return err
|
|
}
|
|
|
|
for _, rec := range csvData {
|
|
log.WithFields(log.Fields{"name": z.box.name, "zfs-fs": rec[0], "zfs-value": rec[1]}).Debugf("zfs get " + zfsManagedPropertyName)
|
|
|
|
if fs, ok := z.filesystems[rec[0]]; ok {
|
|
if rec[1] == "true" {
|
|
fs.managed = true
|
|
log.WithFields(log.Fields{"name": z.box.name, "zfs-fs": rec[0], "zfs-value": rec[1]}).Infof("managed fs")
|
|
}
|
|
}
|
|
}
|
|
|
|
z.online = true
|
|
|
|
return nil
|
|
}
|
|
|
|
func (z *BoxZfs) Close() error {
|
|
log.WithFields(log.Fields{"name": z.box.name}).Debugf("starting")
|
|
defer log.WithFields(log.Fields{"name": z.box.name}).Debugf("done")
|
|
|
|
z.mx.Lock()
|
|
defer z.mx.Unlock()
|
|
|
|
for _, fs := range z.filesystems {
|
|
fs.mx.Lock()
|
|
defer fs.mx.Unlock()
|
|
}
|
|
|
|
z.online = false
|
|
|
|
return nil
|
|
}
|
|
|
|
func (z *BoxZfs) Mkdir(path string) error {
|
|
log.WithFields(log.Fields{"name": z.box.name}).Debugf("starting")
|
|
defer log.WithFields(log.Fields{"name": z.box.name}).Debugf("done")
|
|
|
|
if !z.online {
|
|
err := errors.New("zfs offline")
|
|
log.WithFields(log.Fields{"name": z.box.name, "error": err}).Errorf("")
|
|
return err
|
|
}
|
|
|
|
z.mx.Lock()
|
|
defer z.mx.Unlock()
|
|
|
|
b := z.box
|
|
if !b.online {
|
|
err := errors.New("box offline")
|
|
log.WithFields(log.Fields{"name": z.box.name, "error": err}).Errorf("")
|
|
return err
|
|
}
|
|
|
|
if _, ok := z.filesystems[path]; ok {
|
|
return nil
|
|
}
|
|
|
|
if _, err := b.Exec(fmt.Sprintf("zfs create -p %s", path)); err != nil {
|
|
log.WithFields(log.Fields{"name": z.box.name, "call": "Exec", "error": err}).Errorf("")
|
|
return err
|
|
}
|
|
|
|
newPath := ""
|
|
for _, p := range strings.Split(path, "/") {
|
|
if newPath == "" {
|
|
newPath = p
|
|
} else {
|
|
newPath = newPath + "/" + p
|
|
}
|
|
|
|
if _, ok := z.filesystems[newPath]; !ok {
|
|
fs := &ZfsFs{
|
|
path: newPath,
|
|
managed: false,
|
|
zfs: z,
|
|
snapshots: make(map[string]*ZfsSnapshot),
|
|
srcApps: make([]*App, 0),
|
|
destApps: make([]*App, 0),
|
|
}
|
|
z.filesystems[newPath] = fs
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (fs *ZfsFs) TakeSnapshot(name string) (*ZfsSnapshot, error) {
|
|
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path, "name": name}).Debugf("starting")
|
|
defer log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path, "name": name}).Debugf("done")
|
|
|
|
if !fs.zfs.online {
|
|
err := errors.New("zfs offline")
|
|
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path, "name": name, "error": err}).Errorf("")
|
|
return nil, err
|
|
}
|
|
|
|
re := regexp.MustCompile(`^[a-zA-Z0-9\-\._]{1,255}$`)
|
|
if !re.MatchString(name) {
|
|
err := errors.New("unsupported name")
|
|
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path, "name": name, "error": err}).Errorf("")
|
|
return nil, err
|
|
}
|
|
|
|
fs.mx.Lock()
|
|
defer fs.mx.Unlock()
|
|
|
|
if _, ok := fs.snapshots[name]; ok {
|
|
err := errors.New("already exists")
|
|
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path, "name": name, "error": err}).Errorf("")
|
|
return nil, err
|
|
}
|
|
|
|
if _, err := fs.zfs.box.Exec("zfs snapshot " + fs.path + "@" + name); err != nil {
|
|
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path, "name": name, "error": err}).Errorf("")
|
|
return nil, err
|
|
}
|
|
|
|
s := &ZfsSnapshot{
|
|
name: name,
|
|
fs: fs,
|
|
}
|
|
|
|
fs.snapshots[name] = s
|
|
|
|
return s, nil
|
|
}
|
|
|
|
func (fs *ZfsFs) DelSnapshot(name string) error {
|
|
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path, "name": name}).Debugf("starting")
|
|
defer log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path, "name": name}).Debugf("done")
|
|
|
|
if !fs.zfs.online {
|
|
err := errors.New("zfs offline")
|
|
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path, "name": name, "error": err}).Errorf("")
|
|
return err
|
|
}
|
|
|
|
fs.mx.Lock()
|
|
defer fs.mx.Unlock()
|
|
|
|
if _, ok := fs.snapshots[name]; !ok {
|
|
err := errors.New("doesn't exist")
|
|
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path, "name": name, "error": err}).Errorf("")
|
|
return err
|
|
}
|
|
|
|
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path, "name": name}).Debugf("zfs destroy " + fs.path + "@" + name)
|
|
|
|
if _, err := fs.zfs.box.Exec("zfs destroy " + fs.path + "@" + name); err != nil {
|
|
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path, "name": name, "error": err}).Errorf("")
|
|
return err
|
|
}
|
|
|
|
delete(fs.snapshots, name)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func (fs *ZfsFs) AddSnapshot(s *ZfsSnapshot) error {
|
|
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path, "name": s.name}).Debugf("starting")
|
|
defer log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path, "name": s.name}).Debugf("done")
|
|
|
|
if !fs.zfs.online {
|
|
err := errors.New("zfs offline")
|
|
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path, "name": s.name, "error": err}).Errorf("")
|
|
return err
|
|
}
|
|
|
|
fs.mx.Lock()
|
|
defer fs.mx.Unlock()
|
|
|
|
if _, ok := fs.snapshots[s.name]; ok {
|
|
err := errors.New("already exist")
|
|
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path, "name": s.name, "error": err}).Errorf("")
|
|
return err
|
|
}
|
|
|
|
fs.snapshots[s.name] = s
|
|
|
|
return nil
|
|
}
|
|
|
|
func (fs *ZfsFs) ValidSnapshots() []*ZfsSnapshot {
|
|
tab := make([]*ZfsSnapshot, 0)
|
|
|
|
for _, s := range fs.snapshots {
|
|
if s.Valid() {
|
|
tab = append(tab, s)
|
|
}
|
|
}
|
|
|
|
sort.Slice(tab, func(i, j int) bool {
|
|
ti, _ := tab[i].Timestamp()
|
|
tj, _ := tab[j].Timestamp()
|
|
return ti.Before(tj)
|
|
})
|
|
|
|
return tab
|
|
}
|