package cmd import ( "os" "fmt" "bufio" "errors" "strings" ) const ( // 1MB BUFFER_SIZE = 512 ) /******************************************************************************** * Migrate jail to another zfs pool *******************************************************************************/ func MigrateJail(args []string) { var jailNames []string if len(args) > 0 { for _, a := range args { jailNames = append(jailNames, a) } } for _, jn := range jailNames { cj, err := getJailFromArray(jn, []string{""}, 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") fmt.Printf("Continue? (y/n) ") scanr := bufio.NewScanner(os.Stdin) scanr.Scan() if false == strings.EqualFold(scanr.Text(), "y") { fmt.Printf("Migration aborted\n") return } } /* TODO : Check dest pool (gMigrateDestDatastore) existence zfs snapshot /iocage/jails/$jail@gocage_mig_first_snap zfs snapshot /iocage/jails/$jail/root@gocage_mig_first_snap zfs send jail@gocage_mig_first_snap | zfs receive destpool/jails/jail_name zfs send jail/root@gocage_mig_first_snap | zfs receive destpool/jails/jail_name/root shutdown jail if needed if jail was shutdown zfs snapshot /iocage/jails/$jail@gocage_mig_last_snap to get last data zfs send -i jail@gocage_mig_first_snap jail/root@gocage_mig_last_snap | zfs receive -F destpool/jails/jail_name start jail on new dest zfs destroy destpool/jails/jail_name@gocage_mig_first_snap zfs destroy destpool/jails/jail_name/root@gocage_mig_first_snap */ // Snapshot config 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) return } fmt.Printf("Done\n") // Snapshot filesystem dsdata := strings.Join([]string{curDS.ZFSDataset, "jails", jn, "root"}, "/") fmt.Printf("Snapshot %s: ", dsdata) if err := zfsSnapshot(dsdata, "gocage_mig_init"); err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Done\n") // TODO: Browse jailed ZFS dataset to migrate them (see poudriere for example) // zfs send -R src/poudriere@init | zfs receive -Fu dest/poudriere dsconfdest := strings.Join([]string{destDS.ZFSDataset, "jails", jn}, "/") fmt.Printf("Migrate jail config dataset to %s: ", dsconfdest) if err := zfsCopy(fmt.Sprintf("%s@gocage_mig_init", dsconf), dsconfdest); err != nil { fmt.Printf("Error: %v\n", err) } fmt.Printf("Done\n") dsdatadest := strings.Join([]string{destDS.ZFSDataset, "jails", jn, "root"}, "/") fmt.Printf("Migrate jail filesystem dataset to %s: ", dsdatadest) if err := zfsCopy(fmt.Sprintf("%s@gocage_mig_init", dsdata), dsdatadest); err != nil { fmt.Printf("Error: %v\n", err) } fmt.Printf("Done\n") // Running jail needs a last snapshot for an incremental send/recv after shutting down. if cj.Running == true { fmt.Printf("Shutdown jail %s for last data sync, this could take some time.\n", cj.Name) if gYesToAll == false { fmt.Printf("Continue? (y/n) ") scanr := bufio.NewScanner(os.Stdin) scanr.Scan() if false == strings.EqualFold(scanr.Text(), "y") { fmt.Printf("Migration aborted. Now cleaning destination pool.\n") if err := CleanMigrateMess([]string{cj.Name}); err != nil { fmt.Printf("Error: %v\n", err) } // TODO : Remove destination datasets, or handle the cas at the beginning of current function // (when snapshot already exist on source and dest) return } } StopJail([]string{cj.Name}) fmt.Printf("Snapshot %s: ", dsconf) if err = zfsSnapshot(dsconf, "gocage_mig_last_sync"); err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Done\n") fmt.Printf("Snapshot %s: ", dsdata) if err := zfsSnapshot(dsdata, "gocage_mig_last_sync"); err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Done\n") fmt.Printf("Synchronize jail config to %s: ", dsconfdest) if err := zfsCopyIncremental(fmt.Sprintf("%s@gocage_mig_init", dsconf), fmt.Sprintf("%s@gocage_mig_last_sync", dsconf), dsconfdest); err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Printf("Done\n") fmt.Printf("Synchronize jail filesystem dataset to %s: ", dsdatadest) if err := zfsCopyIncremental(fmt.Sprintf("%s@gocage_mig_init", dsdata), fmt.Sprintf("%s@gocage_mig_last_sync", dsdata), dsdatadest); err != nil { return fmt.Printf("Error: %v\n", err) } fmt.Printf("Done\n") // TODO : Start jail on new datastore (! Currently ListJails won't support 2 jails with same name !) // TODO : zfs destroy destpool/jails/jail_name@gocage_mig_first_snap // TODO : zfs destroy destpool/jails/jail_name/root@gocage_mig_first_snap // TODO : zfs destroy -r srcpool/iocage/jails/$jail } } } // Clean snapshots from an aborted migration func CleanMigrateMess(args []string) error { var jailNames []string if len(args) > 0 { for _, a := range args { jailNames = append(jailNames, a) } } for _, jn := range jailNames { cj, err := getJailFromArray(jn, []string{""}, gJails) if cj == nil { 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{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") { fmt.Printf("Error executing command %s: %v; command returned: %s\n", cmd, err, out) 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{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") { fmt.Printf("Error executing command %s: %v; command returned: %s\n", cmd, err, out) 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{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") { fmt.Printf("Error executing command %s: %v; command returned: %s\n", cmd, err, out) 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{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") { fmt.Printf("Error executing command %s: %v; command returned: %s\n", cmd, err, out) return errors.New(fmt.Sprintf("Error executing command %s: %v; command returned: %s\n", cmd, err, out)) } } } return nil }