Files
ssongliu c06c4e338b feat(vm): enable community virtual machines and migrate xpack data (#14279)
* feat(vm): enable community virtual machines and migrate xpack data

* fix(vm): stop ISO download polling on failure or cancellation

* fix(vm): respect deleteFile when deleting failed ISO imports
2026-11-02 18:22:58 +08:00

108 lines
3.3 KiB
Go

package migrations
import (
"fmt"
"net/url"
"os"
"path/filepath"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/glebarez/sqlite"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
var MigrateVMDatabase = &gormigrate.Migration{
ID: "20261102-migrate-vm-to-community",
Migrate: func(db *gorm.DB) error {
return migrateVMDatabase(db, global.Dir.DbDir)
},
}
func migrateVMDatabase(db *gorm.DB, dbDir string) error {
var legacy *gorm.DB
legacyPath := filepath.Join(dbDir, "xpack.db")
if _, err := os.Stat(legacyPath); err == nil {
uri := (&url.URL{Scheme: "file", Path: legacyPath, RawQuery: "mode=ro"}).String()
legacy, err = gorm.Open(sqlite.Open(uri), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true})
if err != nil {
return fmt.Errorf("open legacy VM database: %w", err)
}
sqlDB, err := legacy.DB()
if err != nil {
return err
}
defer sqlDB.Close()
} else if !os.IsNotExist(err) {
return fmt.Errorf("inspect legacy VM database: %w", err)
}
return db.Transaction(func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.VirtualMachine{}, &model.VMTemplate{}, &model.VMIso{}, &model.VMNetwork{}, &model.VMStorage{}); err != nil {
return err
}
if legacy != nil {
tables := []struct {
name string
records interface{}
}{
{name: "virtual_machines", records: &[]model.VirtualMachine{}},
{name: "vm_templates", records: &[]model.VMTemplate{}},
{name: "vm_isos", records: &[]model.VMIso{}},
{name: "vm_networks", records: &[]model.VMNetwork{}},
{name: "vm_storages", records: &[]model.VMStorage{}},
}
for _, table := range tables {
if err := migrateVMTable(legacy, tx, table.name, table.records); err != nil {
return fmt.Errorf("migrate %s: %w", table.name, err)
}
}
}
return nil
})
}
func migrateVMTable(source, target *gorm.DB, table string, records interface{}) error {
if !source.Migrator().HasTable(table) {
return nil
}
if err := target.Table(table).Where("1 = 1").Delete(records).Error; err != nil {
return err
}
query := source.Table(table).Find(records)
if query.Error != nil {
return query.Error
}
if query.RowsAffected != 0 {
if err := target.Table(table).CreateInBatches(records, 50).Error; err != nil {
return err
}
var migrated int64
if err := target.Table(table).Count(&migrated).Error; err != nil {
return err
}
if migrated != query.RowsAffected {
return fmt.Errorf("expected %d records, migrated %d", query.RowsAffected, migrated)
}
}
return migrateVMSequence(source, target, table)
}
func migrateVMSequence(source, target *gorm.DB, table string) error {
var sequenceTable int64
if err := source.Raw("SELECT COUNT(*) FROM sqlite_master WHERE name = 'sqlite_sequence'").Scan(&sequenceTable).Error; err != nil {
return err
}
if sequenceTable == 0 {
return nil
}
var sequence int64
if err := source.Raw("SELECT COALESCE(MAX(seq), 0) FROM sqlite_sequence WHERE name = ?", table).Scan(&sequence).Error; err != nil {
return err
}
if err := target.Exec("INSERT INTO sqlite_sequence (name, seq) SELECT ?, ? WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = ?)", table, sequence, table).Error; err != nil {
return err
}
return target.Exec("UPDATE sqlite_sequence SET seq = MAX(seq, ?) WHERE name = ?", sequence, table).Error
}