353 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			353 lines
		
	
	
		
			9.0 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) Lock() {
 | 
						|
	log.WithFields(log.Fields{"name": z.box.name}).Debugf("starting")
 | 
						|
	z.mx.Lock()
 | 
						|
}
 | 
						|
 | 
						|
func (z *BoxZfs) Unlock() {
 | 
						|
	log.WithFields(log.Fields{"name": z.box.name}).Debugf("starting")
 | 
						|
	z.mx.Unlock()
 | 
						|
}
 | 
						|
 | 
						|
func (fs *ZfsFs) Lock() {
 | 
						|
	log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path}).Debugf("starting")
 | 
						|
	fs.mx.Lock()
 | 
						|
}
 | 
						|
 | 
						|
func (fs *ZfsFs) Unlock() {
 | 
						|
	log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path}).Debugf("starting")
 | 
						|
	fs.mx.Unlock()
 | 
						|
}
 | 
						|
 | 
						|
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.Lock()
 | 
						|
	defer z.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] == "+" {
 | 
						|
				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.Lock()
 | 
						|
	defer z.Unlock()
 | 
						|
 | 
						|
	for _, fs := range z.filesystems {
 | 
						|
		fs.Lock()
 | 
						|
		defer fs.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.Lock()
 | 
						|
	defer z.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.Lock()
 | 
						|
	defer fs.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.Lock()
 | 
						|
	defer fs.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.Lock()
 | 
						|
	defer fs.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 {
 | 
						|
	log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path}).Debugf("starting")
 | 
						|
	defer log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path}).Debugf("done")
 | 
						|
 | 
						|
	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
 | 
						|
}
 |