Add datastore to snapshots, force Datastore display when jail exist on multi datastores

This commit is contained in:
yo 2022-06-18 18:24:09 +02:00
parent 1c04f62ed8
commit 9218ffafe1
6 changed files with 145 additions and 92 deletions

View File

@ -3,7 +3,6 @@ package cmd
import (
"encoding/json"
"fmt"
"github.com/spf13/viper"
"gocage/jail"
"io/ioutil"
"log"
@ -40,12 +39,12 @@ func ListJailsProps(args []string) {
* into gJails global var
*******************************************************************************/
func ListJails(args []string, display bool) {
fields := strings.Split(gDisplayJColumns, ",")
for _, d := range viper.GetStringSlice("datastore") {
listJailsFromDatastore(d)
for _, ds := range gDatastores {
listJailsFromDatastore(ds)
}
fields := strings.Split(gDisplayJColumns, ",")
// This is the structure we will filter, then display
var jails []Jail
@ -91,7 +90,6 @@ func ListJails(args []string, display bool) {
for _, j := range jails {
if j.Name == a {
js = append(js, j)
break
}
}
}
@ -150,17 +148,17 @@ func ListJails(args []string, display bool) {
}
}
func listJailsFromDatastore(datastore string) {
fileInfo, err := os.Stat(datastore)
func listJailsFromDatastore(ds Datastore) {
fileInfo, err := os.Stat(ds.Mountpoint)
if err != nil {
log.Fatalln(fmt.Sprintf("Unable to access %s, check path and/or rights", datastore))
log.Fatalln(fmt.Sprintf("Unable to access %s, check path and/or rights", ds.Mountpoint))
}
if fileInfo.IsDir() == false {
log.Fatalln(fmt.Sprintf("%s is not a directory", datastore))
log.Fatalln(fmt.Sprintf("%s is not a directory", ds.Mountpoint))
}
// A datastore have to contain a "jails" directory
jailsDir := fmt.Sprintf("%s/jails", datastore)
jailsDir := fmt.Sprintf("%s/jails", ds.Mountpoint)
fileInfo, err = os.Stat(jailsDir)
if err != nil {
log.Fatalln(fmt.Sprintf("Unable to access %s, check path and/or rights", jailsDir))
@ -169,10 +167,10 @@ func listJailsFromDatastore(datastore string) {
log.Fatalln(fmt.Sprintf("%s is not a directory", jailsDir))
}
listJailsFromDirectory(jailsDir)
listJailsFromDirectory(jailsDir, ds.Name)
}
func listJailsFromDirectory(dir string) []Jail {
func listJailsFromDirectory(dir string, dsname string) []Jail {
files, err := ioutil.ReadDir(dir)
if err != nil {
log.Fatalln(fmt.Sprintf("Unable to browse %s, check path and/or rights", dir))
@ -194,6 +192,7 @@ func listJailsFromDirectory(dir string) []Jail {
Name: jailConf.Host_hostname,
Config: jailConf,
ConfigPath: jailConfPath,
Datastore: dsname,
RootPath: jailRootPath,
Running: false,
}
@ -223,9 +222,12 @@ func listJailsFromDirectory(dir string) []Jail {
// Check if jail with the same name already exist on another DS
for _, jj := range gJails {
if strings.EqualFold(jj.Name, j.Name) {
fmt.Printf("ERROR: A jail with name %s already exist on datastore %s!\n", j.Name, jj.Zpool)
fmt.Printf("Jail %s on datastore %s wont be handled\n", j.Name, j.Zpool)
return gJails
fmt.Printf(" ---------------------------------------------- \n")
fmt.Printf("Warning: A jail with name %s already exist on datastore %s!\n", j.Name, jj.Datastore)
fmt.Printf(" ---------------------------------------------- \n")
// Add Datastore to avoid confusion
gDisplayJColumns += ",Datastore"
gDisplaySColumns += ",Datastore"
}
}

View File

@ -18,7 +18,6 @@ const (
*******************************************************************************/
func MigrateJail(args []string) {
var jailNames []string
var destDS Datastore
if len(args) > 0 {
for _, a := range args {
@ -27,26 +26,24 @@ func MigrateJail(args []string) {
}
for _, jn := range jailNames {
// Check if destination datastore exist
found := false
for _, ds := range gDatastores {
if strings.EqualFold(gMigrateDestDatastore, ds.Name) {
found = true
destDS = ds
break
}
}
if false == found {
fmt.Printf("Unkown datastore: %s\n", gMigrateDestDatastore)
return
}
cj, err := getJailFromArray(jn, gJails)
if cj == nil {
fmt.Printf("Error getting jail %s: Not found\n", jn)
return
}
// Check if destination datastore exist & get current DS
destDS, err := getDatastoreFromArray(gMigrateDestDatastore, gDatastores)
if err != nil {
fmt.Printf("Error getting datastore \"%s\": %v\n", gMigrateDestDatastore, err)
return
}
curDS, err := getDatastoreFromArray(cj.Datastore, gDatastores)
if err != nil {
fmt.Printf("Error getting datastore \"%s\": %v\n", gMigrateDestDatastore, err)
return
}
if cj.Running == true && gYesToAll == false {
fmt.Printf("WARNING: Jail %s is running\n", cj.Name)
fmt.Printf("Migration will stop it for data sync before starting on the new pool. You will be prompted for shutdown.\n")
@ -74,7 +71,7 @@ func MigrateJail(args []string) {
*/
// Snapshot config
dsconf := strings.Join([]string{cj.Zpool, "iocage", "jails", jn}, "/")
dsconf := strings.Join([]string{curDS.ZFSDataset, "jails", jn}, "/")
fmt.Printf("Snapshot %s: ", dsconf)
if err = zfsSnapshot(dsconf, "gocage_mig_init"); err != nil {
fmt.Printf("Error: %v\n", err)
@ -83,7 +80,7 @@ func MigrateJail(args []string) {
fmt.Printf("Done\n")
// Snapshot filesystem
dsdata := strings.Join([]string{cj.Zpool, "iocage", "jails", jn, "root"}, "/")
dsdata := strings.Join([]string{curDS.ZFSDataset, "iocage", "jails", jn, "root"}, "/")
fmt.Printf("Snapshot %s: ", dsdata)
if err := zfsSnapshot(dsdata, "gocage_mig_init"); err != nil {
fmt.Printf("Error: %v\n", err)
@ -178,11 +175,14 @@ func CleanMigrateMess(args []string) error {
for _, jn := range jailNames {
cj, err := getJailFromArray(jn, gJails)
if cj == nil {
fmt.Printf("Error getting jail %s: Not found\n", jn)
return errors.New(fmt.Sprintf("Error getting jail %s: Not found\n", jn))
}
curDS, err := getDatastoreFromArray(cj.Datastore, gDatastores)
if err != nil {
return errors.New(fmt.Sprintf("Error getting datastore \"%s\": %v\n", cj.Datastore, err))
}
cmd := fmt.Sprintf("zfs destroy %s@gocage_mig_init", strings.Join([]string{cj.Zpool, "iocage", "jails", jn}, "/"))
cmd := fmt.Sprintf("zfs destroy %s@gocage_mig_init", strings.Join([]string{curDS.ZFSDataset, "jails", jn}, "/"))
out, err := executeCommand(cmd)
if err != nil {
if false == strings.HasSuffix(out, "could not find any snapshots to destroy; check snapshot names.\n") {
@ -190,7 +190,7 @@ func CleanMigrateMess(args []string) error {
return errors.New(fmt.Sprintf("Error executing command %s: %v; command returned: %s\n", cmd, err, out))
}
}
cmd = fmt.Sprintf("zfs destroy %s@gocage_mig_init", strings.Join([]string{cj.Zpool, "iocage", "jails", jn, "root"}, "/"))
cmd = fmt.Sprintf("zfs destroy %s@gocage_mig_init", strings.Join([]string{curDS.ZFSDataset, "jails", jn, "root"}, "/"))
out, err = executeCommand(cmd)
if err != nil {
if false == strings.HasSuffix(out, "could not find any snapshots to destroy; check snapshot names.\n") {
@ -198,7 +198,7 @@ func CleanMigrateMess(args []string) error {
return errors.New(fmt.Sprintf("Error executing command %s: %v; command returned: %s\n", cmd, err, out))
}
}
cmd = fmt.Sprintf("zfs destroy %s@gocage_mig_last_sync", strings.Join([]string{cj.Zpool, "iocage", "jails", jn}, "/"))
cmd = fmt.Sprintf("zfs destroy %s@gocage_mig_last_sync", strings.Join([]string{curDS.ZFSDataset, "jails", jn}, "/"))
out, err = executeCommand(cmd)
if err != nil {
if false == strings.HasSuffix(out, "could not find any snapshots to destroy; check snapshot names.\n") {
@ -206,7 +206,7 @@ func CleanMigrateMess(args []string) error {
return errors.New(fmt.Sprintf("Error executing command %s: %v; command returned: %s\n", cmd, err, out))
}
}
cmd = fmt.Sprintf("zfs destroy %s@gocage_mig_last_sync", strings.Join([]string{cj.Zpool, "iocage", "jails", jn, "root"}, "/"))
cmd = fmt.Sprintf("zfs destroy %s@gocage_mig_last_sync", strings.Join([]string{curDS.ZFSDataset, "jails", jn, "root"}, "/"))
out, err = executeCommand(cmd)
if err != nil {
if false == strings.HasSuffix(out, "could not find any snapshots to destroy; check snapshot names.\n") {

View File

@ -225,7 +225,10 @@ You can specify multiple jails.`,
Run: func(cmd *cobra.Command, args []string) {
// Load inventory
ListJails(args, false)
CleanMigrateMess(args)
err := CleanMigrateMess(args)
if err != nil {
fmt.Printf("%v", err)
}
},
}
@ -262,8 +265,8 @@ func init() {
// Command dependant switches
// We reuse these in "gocage snapshot list myjail" and 'gocage datastore list" commands (TODO)
listCmd.Flags().StringVarP(&gDisplayJColumns, "outcol", "o", "JID,Name,Config.Release,Config.Ip4_addr,Running", "Show these columns in output")
// We reuse these flags in "gocage snapshot list myjail" and 'gocage datastore list" commands
listCmd.Flags().StringVarP(&gDisplayJColumns, "outcol", "o", "JID,Name,Datastore,Config.Release,Config.Ip4_addr,Running", "Show these columns in output")
listCmd.Flags().BoolVarP(&gNoJailLineSep, "nolinesep", "l", false, "Do not display line separator between jails")
listCmd.Flags().StringVarP(&gFilterJails, "filter", "f", "none", "Only display jails with these values. Ex: \"gocage list -f Config.Boot=1\" will only list started on boot jails")
listCmd.Flags().StringVarP(&gSortJailFields, "sort", "s", "none", "Display jails sorted by field values. Ex: \"gocage list -s +Name,-Config.Priority\" will sort jails by their decreasing name, then increasing start priority. 3 critera max supported.")

View File

@ -6,6 +6,7 @@ import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"reflect"
"time"
@ -23,6 +24,10 @@ func ListJailsSnapshots(args []string) {
/**************************************************************/
if len(args) > 0 {
for _, a := range args {
/*if countOfJailsWithThisName(a) > 1 {
fmt.Printf("Nope")
return
}*/
jailNames = append(jailNames, a)
}
}
@ -99,23 +104,27 @@ func listJailSnapshots(jail Jail) []Snapshot {
// 1. List all datasets
// TODO : Include mounted filesystems?
rs := strings.Split(jail.RootPath, "/")
rootDataset := fmt.Sprintf("%s%s", jail.Zpool, strings.Join(rs[:len(rs)-1], "/"))
cmd := fmt.Sprintf("zfs list -r -H -o name,mountpoint,used,referenced,creation -t snapshot %s", rootDataset)
curDS, err := getDatastoreFromArray(jail.Datastore, gDatastores)
if err != nil {
fmt.Printf("Error getting datastore \"%s\": %v\n", jail.Datastore, err)
return snapshots
}
rootDataset := fmt.Sprintf("%s/%s/%s", curDS.ZFSDataset, "jails", jail.Name)
cmd := fmt.Sprintf("zfs list -p -r -H -o name,mountpoint,used,referenced,creation -t snapshot %s", rootDataset)
out, err := executeCommand(cmd)
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
return snapshots
}
dateLayout := "Mon Jan _2 15:04 2006"
loc, _ := time.LoadLocation(gTimeZone)
for _, line := range strings.Split(out, "\n") {
if len(line) > 0 {
ls := strings.Split(line, "\t")
// Parse creation date so we can use it to sort snapshots
creationts, err := time.ParseInLocation(dateLayout, ls[4], loc)
//creationts, err := time.ParseInLocation(dateLayout, ls[4], loc)
creationts, err := strconv.ParseInt(ls[4], 10, 64)
if err != nil {
fmt.Println("Error while parsing date %s:", ls[4], err)
return snapshots
@ -123,13 +132,15 @@ func listJailSnapshots(jail Jail) []Snapshot {
// Get subdir to append to snapshot name
subdir := strings.Replace(strings.Split(ls[0], "@")[0], rootDataset, "", 1)
snapshots = append(snapshots, Snapshot{Dsname: ls[0],
u, _ := strconv.ParseUint(ls[2], 10, 64)
r, _ := strconv.ParseUint(ls[3], 10, 64)
snapshots = append(snapshots, Snapshot{Datastore: curDS.Name,
Name: fmt.Sprintf("%s%s", strings.Split(ls[0], "@")[1], subdir),
Jailname: jail.Name,
Mountpoint: ls[1],
Used: ls[2],
Referenced: ls[3],
Creation: creationts})
Used: u,
Referenced: r,
Creation: time.Unix(creationts, 0)})
}
}
@ -165,8 +176,8 @@ func CreateJailSnapshot(args []string) {
* Create snapshot for a jail
*******************************************************************************/
func createJailSnapshot(jail Jail) error {
rs := strings.Split(jail.RootPath, "/")
rootDataset := fmt.Sprintf("%s%s", jail.Zpool, strings.Join(rs[:len(rs)-1], "/"))
curDS, _ := getDatastoreFromArray(jail.Datastore, gDatastores)
rootDataset := fmt.Sprintf("%s/%s/%s", curDS.ZFSDataset, "jails", jail.Name)
cmd := fmt.Sprintf("zfs snapshot -r %s@%s", rootDataset, gSnapshotName)
_, err := executeCommand(cmd)
@ -207,8 +218,8 @@ func deleteJailSnapshot(jail Jail) error {
var snaptodel []string
// Get all recursive snapshots
rs := strings.Split(jail.RootPath, "/")
rootDataset := fmt.Sprintf("%s%s", jail.Zpool, strings.Join(rs[:len(rs)-1], "/"))
curDS, _ := getDatastoreFromArray(jail.Datastore, gDatastores)
rootDataset := fmt.Sprintf("%s/%s/%s", curDS.ZFSDataset, "jails", jail.Name)
cmd := fmt.Sprintf("zfs list -r -H -o name -t snapshot %s", rootDataset)
out, err := executeCommand(cmd)
if err != nil {
@ -284,8 +295,8 @@ func rollbackJailSnapshot(jail Jail) error {
// We need to rollback parent and childs
// Get all recursive snapshots
rs := strings.Split(jail.RootPath, "/")
rootDataset := fmt.Sprintf("%s%s", jail.Zpool, strings.Join(rs[:len(rs)-1], "/"))
curDS, _ := getDatastoreFromArray(jail.Datastore, gDatastores)
rootDataset := fmt.Sprintf("%s/%s/%s", curDS.ZFSDataset, "jails", jail.Name)
cmd := fmt.Sprintf("zfs list -r -H -o name -t snapshot %s", rootDataset)
out, err := executeCommand(cmd)
if err != nil {

View File

@ -27,7 +27,8 @@ type Jail struct {
Running bool
// No need, Config.Release always represent what is running (plus it know release for non-running jails)
//Release string
Zpool string
Zpool string
Datastore string
}
// iocage struct as stored in config.json
@ -191,11 +192,11 @@ type Mount struct {
type Snapshot struct {
// snapshot name is stored after '@' in dataset name
Name string
Dsname string
Datastore string
Jailname string
Mountpoint string
Used string
Referenced string
Used uint64
Referenced uint64
Creation time.Time
}
@ -215,6 +216,8 @@ type JailSort struct {
ConfigPathDec jailLessFunc
RunningInc jailLessFunc
RunningDec jailLessFunc
DatastoreInc jailLessFunc
DatastoreDec jailLessFunc
ZpoolInc jailLessFunc
ZpoolDec jailLessFunc
Config JailConfigSort
@ -498,8 +501,8 @@ type JailConfigSort struct {
type SnapshotSort struct {
NameInc snapshotLessFunc
NameDec snapshotLessFunc
DsnameInc snapshotLessFunc
DsnameDec snapshotLessFunc
DatastoreInc snapshotLessFunc
DatastoreDec snapshotLessFunc
JailnameInc snapshotLessFunc
JailnameDec snapshotLessFunc
MountpointInc snapshotLessFunc

View File

@ -391,7 +391,7 @@ func getJailFromArray(name string, jarray []Jail) (*Jail, error) {
return &Jail{}, errors.New("Jail not found")
}
/*****************************************************************************
/******************************************************************************
*
* Generic utilities
*
@ -405,6 +405,28 @@ func isStringInArray(strarr []string, searched string) bool {
return false
}
func getDatastoreFromArray(name string, dsa []Datastore) (*Datastore, error) {
for _, d := range dsa {
if name == d.Name {
return &d, nil
}
}
return &Datastore{}, errors.New("Datastore not found")
}
/******************************************************************************
* Return the quantity of jails with the name passed as parameter
*****************************************************************************/
func countOfJailsWithThisName(name string) int {
count := 0
for _, j := range gJails {
if strings.EqualFold(j.Name, name) {
count++
}
}
return count
}
/********************************************************************************
* Recurse into structure, returning reflect.Kind of named field.
* Nested fields are named with a dot (ex "MyStruct.MyField")
@ -836,7 +858,7 @@ func displayJailsFields(jails []Jail, valsToDisplay []string) {
/********************************************************************************
* Pretty display of snapshots field
* Fields to show are given in a string array parameter
* Ex. : displaySnapshotsFields(snapshots, ["Name", "Dsname", "Used"])
* Ex. : displaySnapshotsFields(snapshots, ["Name", "Datastore", "Used"])
*******************************************************************************/
func displaySnapshotsFields(snaps []Snapshot, valsToDisplay []string) {
/* A line is defined by :
@ -878,7 +900,14 @@ func displaySnapshotsFields(snaps []Snapshot, valsToDisplay []string) {
itnr := len(strings.Split(string(a.FieldByName(f).Interface().(string)), ","))
field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface())) / itnr
} else {
field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface()))
// Special case of disk size : We will print human readable values
if field.Name == "Used" || field.Name == "Referenced" || field.Name == "Available" {
var v datasize.ByteSize
v.UnmarshalText([]byte(fmt.Sprintf("%v", a.FieldByName(f).Interface())))
field.MaxLen = len(fmt.Sprintf("%v", v.HumanReadable()))
} else {
field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface()))
}
}
field.Value = fmt.Sprintf("%v", a.FieldByName(f).Interface())
} else {
@ -983,25 +1012,24 @@ func displaySnapshotsFields(snaps []Snapshot, valsToDisplay []string) {
fmt.Printf("|")
}
// Special cases of value displaying
/* if f.Name == "JID" && f.Value == "0" {
fmt.Printf(" ")
} else if f.Name == "Ip4_addr" {
ia := strings.Split(f.Value, ",")
// If we have more than 1 value we need to finish this line, and store value for writing at the end of line loop
for i, inter := range ia {
if i > 0 {
supplines[f.Name] = inter
} else {
fmt.Printf(" %s", inter)
}
}
//fmt.Printf(" %s", strings.Split(strings.Split(f.Value, "|")[1], "/")[0])
} else {*/
fmt.Printf(" %s", f.Value)
/*}*/
// Complete with spaces to the max length
for i := len(f.Value) + 1; i < f.MaxLen+1; i++ {
fmt.Printf(" ")
// Pretty print of sizes
if f.Name == "Used" || f.Name == "Referenced" || f.Name == "Available" {
var v datasize.ByteSize
err := v.UnmarshalText([]byte(f.Value))
if err != nil {
return
}
fmt.Printf(" %s", v.HumanReadable())
// Complete with spaces to the max length
for i := len(v.HumanReadable()) + 1; i < f.MaxLen+1; i++ {
fmt.Printf(" ")
}
} else {
fmt.Printf(" %s", f.Value)
// Complete with spaces to the max length
for i := len(f.Value) + 1; i < f.MaxLen+1; i++ {
fmt.Printf(" ")
}
}
fmt.Printf(" |")
}
@ -2125,6 +2153,12 @@ func initJailSortStruct() JailSort {
ConfigPathDec: func(j1, j2 *Jail) bool {
return j1.ConfigPath > j2.ConfigPath
},
DatastoreInc: func(j1, j2 *Jail) bool {
return j1.Datastore < j2.Datastore
},
DatastoreDec: func(j1, j2 *Jail) bool {
return j1.Datastore > j2.Datastore
},
InternalNameInc: func(j1, j2 *Jail) bool {
return j1.InternalName < j2.InternalName
},
@ -2241,11 +2275,11 @@ func initSnapshotSortStruct() SnapshotSort {
NameDec: func(s1, s2 *Snapshot) bool {
return s1.Name > s2.Name
},
DsnameInc: func(s1, s2 *Snapshot) bool {
return s1.Dsname < s2.Dsname
DatastoreInc: func(s1, s2 *Snapshot) bool {
return s1.Datastore < s2.Datastore
},
DsnameDec: func(s1, s2 *Snapshot) bool {
return s1.Dsname > s2.Dsname
DatastoreDec: func(s1, s2 *Snapshot) bool {
return s1.Datastore > s2.Datastore
},
JailnameInc: func(s1, s2 *Snapshot) bool {
return s1.Jailname < s2.Jailname