diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3dc966bd..00000000 --- a/.travis.yml +++ /dev/null @@ -1,29 +0,0 @@ -language: go - -go: - - 1.16.x - -dist: focal - -cache: - directories: - - /home/travis/.vagrant.d/boxes - -env: - global: - - GO111MODULE=on - -install: - # install build dependencies for vagrant-libvirt - - sudo apt-get update && sudo apt-get build-dep vagrant ruby-libvirt && sudo apt-get install -y qemu libvirt-daemon-system libvirt-clients ebtables dnsmasq-base libxslt-dev libxml2-dev libvirt-dev zlib1g-dev ruby-dev qemu-utils qemu-kvm - - # install vagrant - - sudo wget -nv https://releases.hashicorp.com/vagrant/2.2.14/vagrant_2.2.14_x86_64.deb - - sudo dpkg -i vagrant_2.2.14_x86_64.deb - - # install vagrant-libvirt plugin - - sudo vagrant plugin install vagrant-libvirt - -script: - - sudo vagrant up --provider=libvirt - - make test diff --git a/README.md b/README.md index 002708fd..d8b200d3 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,8 @@ export PM_HTTP_HEADERS=Key,Value,Key1,Value1 (only if required) ./proxmox-api-go node shutdown proxmox-node-name +./proxmox-api-go unlink 123 proxmox-node-name "virtio1,virtio2,virtioN" [false|true] + ``` ## Proxy server support @@ -157,6 +159,11 @@ createQemu JSON Sample: "tag": -1 } }, + "rng0": { + "source": "/dev/urandom", + "max_bytes": "1024", + "period": "1000" + }, "usb": { "0": { "host": "0658:0200", diff --git a/Vagrantfile b/Vagrantfile index 65e8f64f..b27db063 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,5 +1,5 @@ Vagrant.configure("2") do |config| - config.vm.box = "debian/buster64" + config.vm.box = "generic/debian11" config.vm.boot_timeout = 1800 config.vm.synced_folder ".", "/vagrant", disabled: true @@ -11,13 +11,39 @@ Vagrant.configure("2") do |config| host: 8006 # install and configure proxmox - config.vm.provision "shell", + config.vm.provision "Bootstrap System", + type: "shell", privileged: true, path: './scripts/vagrant-bootstrap.sh' + config.vm.provision "Import LXC Template", + type: "shell", + privileged: true, + path: './scripts/vagrant-get-container-template.sh', + run: "always" + + config.vm.provision "Download Cloud-Init Template", + type: "shell", + privileged: true, + path: './scripts/vagrant-get-cloudinit-template.sh', + run: "always" + config.vm.provider :virtualbox do |vb| vb.memory = 2048 vb.cpus = 2 + vb.customize ["modifyvm", :id, "--nested-hw-virt", "on"] + end + + config.vm.provider :hyperv do |hv| + hv.memory = 2048 + hv.cpus = 2 + hv.enable_virtualization_extensions = true + end + + config.vm.provider :vmware do |vm| + vm.vmx["memsize"] = "2048" + vm.vmx["numvcpus"] = "2" + vm.vmx["vhv.enable"] = "TRUE" end config.vm.provider :libvirt do |v, override| diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index f8798517..2b04aa1d 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -9,6 +9,7 @@ import ( _ "github.com/Telmate/proxmox-api-go/cli/command/delete" _ "github.com/Telmate/proxmox-api-go/cli/command/example" _ "github.com/Telmate/proxmox-api-go/cli/command/get" + _ "github.com/Telmate/proxmox-api-go/cli/command/get/guest" _ "github.com/Telmate/proxmox-api-go/cli/command/get/id" _ "github.com/Telmate/proxmox-api-go/cli/command/guest" _ "github.com/Telmate/proxmox-api-go/cli/command/guest/qemu" diff --git a/cli/command/create/create-snapshot.go b/cli/command/create/create-snapshot.go index 74b80eec..e187fa68 100644 --- a/cli/command/create/create-snapshot.go +++ b/cli/command/create/create-snapshot.go @@ -18,12 +18,18 @@ var ( id := cli.ValidateIntIDset(args, "GuestID") snapName := cli.RequiredIDset(args, 1, "SnapshotName") config := proxmox.ConfigSnapshot{ - Name: snapName, + Name: proxmox.SnapshotName(snapName), Description: cli.OptionalIDset(args, 2), VmState: memory, } memory = false - err = config.CreateSnapshot(cli.NewClient(), uint(id)) + client := cli.NewClient() + vmr := proxmox.NewVmRef(id) + _, err = client.GetVmInfo(vmr) + if err != nil { + return + } + err = config.CreateSnapshot(client, vmr) if err != nil { return } diff --git a/cli/command/delete/delete-snapshot.go b/cli/command/delete/delete-snapshot.go index 06538fab..7fd7e00b 100644 --- a/cli/command/delete/delete-snapshot.go +++ b/cli/command/delete/delete-snapshot.go @@ -14,7 +14,7 @@ var ( RunE: func(cmd *cobra.Command, args []string) (err error) { id := cli.ValidateIntIDset(args, "GuestID") snapName := cli.RequiredIDset(args, 1, "SnapshotName") - _, err = proxmox.DeleteSnapshot(cli.NewClient(), proxmox.NewVmRef(id), snapName) + _, err = proxmox.DeleteSnapshot(cli.NewClient(), proxmox.NewVmRef(id), proxmox.SnapshotName(snapName)) if err != nil { return } diff --git a/cli/command/get/get-guest.go b/cli/command/get/guest/get-guest-config.go similarity index 81% rename from cli/command/get/get-guest.go rename to cli/command/get/guest/get-guest-config.go index ffce1db7..0941785a 100644 --- a/cli/command/get/get-guest.go +++ b/cli/command/get/guest/get-guest-config.go @@ -1,4 +1,4 @@ -package get +package guest import ( "github.com/Telmate/proxmox-api-go/cli" @@ -6,8 +6,8 @@ import ( "github.com/spf13/cobra" ) -var get_guestCmd = &cobra.Command{ - Use: "guest GUESTID", +var configCmd = &cobra.Command{ + Use: "config GUESTID", Short: "Gets the configuration of the specified guest", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) (err error) { @@ -29,11 +29,11 @@ var get_guestCmd = &cobra.Command{ if err != nil { return } - cli.PrintFormattedJson(GetCmd.OutOrStdout(), config) + cli.PrintFormattedJson(guestCmd.OutOrStdout(), config) return }, } func init() { - GetCmd.AddCommand(get_guestCmd) + guestCmd.AddCommand(configCmd) } diff --git a/cli/command/get/guest/get-guest-feature.go b/cli/command/get/guest/get-guest-feature.go new file mode 100644 index 00000000..d19b87e0 --- /dev/null +++ b/cli/command/get/guest/get-guest-feature.go @@ -0,0 +1,32 @@ +package guest + +import ( + "github.com/Telmate/proxmox-api-go/cli" + "github.com/Telmate/proxmox-api-go/proxmox" + "github.com/spf13/cobra" +) + +var featureCmd = &cobra.Command{ + Use: "feature GUESTID", + Short: "Gets the available features of the specified guest", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + id := cli.ValidateIntIDset(args, "GuestID") + vmr := proxmox.NewVmRef(id) + c := cli.NewClient() + err = c.CheckVmRef(vmr) + if err != nil { + return + } + features, err := proxmox.ListGuestFeatures(vmr, c) + if err != nil { + return + } + cli.PrintFormattedJson(guestCmd.OutOrStdout(), features) + return + }, +} + +func init() { + guestCmd.AddCommand(featureCmd) +} diff --git a/cli/command/get/guest/get-guest.go b/cli/command/get/guest/get-guest.go new file mode 100644 index 00000000..81672e75 --- /dev/null +++ b/cli/command/get/guest/get-guest.go @@ -0,0 +1,15 @@ +package guest + +import ( + "github.com/Telmate/proxmox-api-go/cli/command/get" + "github.com/spf13/cobra" +) + +var guestCmd = &cobra.Command{ + Use: "guest", + Short: "Commands to get information of guests on Proxmox", +} + +func init() { + get.GetCmd.AddCommand(guestCmd) +} diff --git a/cli/command/list/list-guests.go b/cli/command/list/list-guests.go index 35ebf702..7fe8364e 100644 --- a/cli/command/list/list-guests.go +++ b/cli/command/list/list-guests.go @@ -1,6 +1,8 @@ package list import ( + "github.com/Telmate/proxmox-api-go/cli" + "github.com/Telmate/proxmox-api-go/proxmox" "github.com/spf13/cobra" ) @@ -9,7 +11,10 @@ var list_qemuguestsCmd = &cobra.Command{ Short: "Prints a list of Qemu/Lxc Guests in raw json format", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - listRaw("Guests") + c := cli.NewClient() + guests, err := proxmox.ListGuests(c) + cli.LogFatalListing("Guests", err) + cli.PrintRawJson(listCmd.OutOrStdout(), guests) }, } diff --git a/cli/command/list/list-snapshots.go b/cli/command/list/list-snapshots.go index ea35b896..b7058105 100644 --- a/cli/command/list/list-snapshots.go +++ b/cli/command/list/list-snapshots.go @@ -16,7 +16,7 @@ var ( Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) (err error) { id := cli.ValidateExistingGuestID(args, 0) - jBody, err := proxmox.ListSnapshots(cli.NewClient(), proxmox.NewVmRef(id)) + rawSnapshots, err := proxmox.ListSnapshots(cli.NewClient(), proxmox.NewVmRef(id)) if err != nil { noTree = false return @@ -24,9 +24,9 @@ var ( var list []*proxmox.Snapshot if noTree { noTree = false - list = proxmox.FormatSnapshotsList(jBody) + list = rawSnapshots.FormatSnapshotsList() } else { - list = proxmox.FormatSnapshotsTree(jBody) + list = rawSnapshots.FormatSnapshotsTree() } if len(list) == 0 { listCmd.Printf("Guest with ID (%d) has no snapshots", id) diff --git a/cli/command/list/list.go b/cli/command/list/list.go index 51a872ee..c57ae9f7 100644 --- a/cli/command/list/list.go +++ b/cli/command/list/list.go @@ -23,8 +23,6 @@ func listRaw(IDtype string) { list, err = c.GetAcmeAccountList() case "AcmePlugins": list, err = c.GetAcmePluginList() - case "Guests": - list, err = c.GetVmList() case "MetricServers": list, err = c.GetMetricsServerList() case "Nodes": diff --git a/cli/command/update/update-snapshotdescription.go b/cli/command/update/update-snapshotdescription.go index 93775f51..adb400d7 100644 --- a/cli/command/update/update-snapshotdescription.go +++ b/cli/command/update/update-snapshotdescription.go @@ -14,7 +14,7 @@ var update_snapshotCmd = &cobra.Command{ id := cli.ValidateIntIDset(args, "GuestID") snapName := cli.RequiredIDset(args, 1, "SnapshotName") des := cli.OptionalIDset(args, 2) - err = proxmox.UpdateSnapshotDescription(cli.NewClient(), proxmox.NewVmRef(id), snapName, des) + err = proxmox.UpdateSnapshotDescription(cli.NewClient(), proxmox.NewVmRef(id), proxmox.SnapshotName(snapName), des) if err != nil { return } diff --git a/go.mod b/go.mod index 8de2c3bd..ff678567 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,15 @@ module github.com/Telmate/proxmox-api-go -go 1.18 +go 1.19 require ( - github.com/spf13/cobra v1.5.0 - github.com/stretchr/testify v1.8.0 + github.com/spf13/cobra v1.7.0 + github.com/stretchr/testify v1.8.2 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 00ef3216..0df3271b 100644 --- a/go.sum +++ b/go.sum @@ -2,24 +2,24 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index b790e855..d6fafc25 100644 --- a/main.go +++ b/main.go @@ -785,6 +785,170 @@ func main() { } log.Printf("Network configuration on node %s has been reverted\n", node) + //SDN + case "applySDN": + exitStatus, err := c.ApplySDN() + if err != nil { + failError(fmt.Errorf("error: %+v\n api error: %s", err, exitStatus)) + } + log.Printf("SDN configuration has been applied\n") + + case "getZonesList": + zones, err := c.GetSDNZones(true, "") + if err != nil { + log.Printf("Error listing SDN zones %+v\n", err) + os.Exit(1) + } + zonesList, err := json.Marshal(zones) + failError(err) + fmt.Println(string(zonesList)) + + case "getZone": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: Zone name is needed")) + } + zoneName := flag.Args()[1] + zone, err := c.GetSDNZone(zoneName) + if err != nil { + log.Printf("Error listing SDN zones %+v\n", err) + os.Exit(1) + } + zoneList, err := json.Marshal(zone) + failError(err) + fmt.Println(string(zoneList)) + + case "createZone": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: Zone name is needed")) + } + zoneName := flag.Args()[1] + config, err := proxmox.NewConfigSDNZoneFromJson(GetConfig(*fConfigFile)) + failError(err) + failError(config.CreateWithValidate(zoneName, c)) + log.Printf("Zone %s has been created\n", zoneName) + + case "deleteZone": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: zone name required")) + } + zoneName := flag.Args()[1] + err := c.DeleteSDNZone(zoneName) + failError(err) + + case "updateZone": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: zone name required")) + } + zoneName := flag.Args()[1] + config, err := proxmox.NewConfigSDNZoneFromJson(GetConfig(*fConfigFile)) + failError(err) + failError(config.UpdateWithValidate(zoneName, c)) + log.Printf("Zone %s has been updated\n", zoneName) + + case "getVNetsList": + zones, err := c.GetSDNVNets(true) + if err != nil { + log.Printf("Error listing SDN zones %+v\n", err) + os.Exit(1) + } + vnetsList, err := json.Marshal(zones) + failError(err) + fmt.Println(string(vnetsList)) + + case "getVNet": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: VNet name is needed")) + } + vnetName := flag.Args()[1] + vnet, err := c.GetSDNVNet(vnetName) + if err != nil { + log.Printf("Error listing SDN VNets %+v\n", err) + os.Exit(1) + } + vnetsList, err := json.Marshal(vnet) + failError(err) + fmt.Println(string(vnetsList)) + + case "createVNet": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: VNet name is needed")) + } + vnetName := flag.Args()[1] + config, err := proxmox.NewConfigSDNVNetFromJson(GetConfig(*fConfigFile)) + failError(err) + failError(config.CreateWithValidate(vnetName, c)) + log.Printf("VNet %s has been created\n", vnetName) + + case "deleteVNet": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: VNet name required")) + } + vnetName := flag.Args()[1] + err := c.DeleteSDNVNet(vnetName) + failError(err) + + case "updateVNet": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: zone name required")) + } + vnetName := flag.Args()[1] + config, err := proxmox.NewConfigSDNVNetFromJson(GetConfig(*fConfigFile)) + failError(err) + failError(config.UpdateWithValidate(vnetName, c)) + log.Printf("VNet %s has been updated\n", vnetName) + + case "getDNSList": + dns, err := c.GetSDNDNSs("") + if err != nil { + log.Printf("Error listing SDN DNS entries %+v\n", err) + os.Exit(1) + } + dnsList, err := json.Marshal(dns) + failError(err) + fmt.Println(string(dnsList)) + + case "getDNS": + if len(flag.Args()) < 2 { + failError(fmt.Errorf("error: DNS name is needed")) + } + name := flag.Args()[1] + dns, err := c.GetSDNDNS(name) + if err != nil { + log.Printf("Error listing SDN DNS %+v\n", err) + os.Exit(1) + } + dnsList, err := json.Marshal(dns) + failError(err) + fmt.Println(string(dnsList)) + + case "unlink": + if len(flag.Args()) < 4 { + failError(fmt.Errorf("error: invoke with ]")) + } + + vmIdUnparsed := flag.Args()[1] + node := flag.Args()[2] + vmId, err := strconv.Atoi(vmIdUnparsed) + if err != nil { + failError(fmt.Errorf("Failed to convert vmId: %s to a string, error: %+v", vmIdUnparsed, err)) + } + + disks := flag.Args()[3] + forceRemoval := false + if len(flag.Args()) > 4 { + forceRemovalUnparsed := flag.Args()[4] + forceRemoval, err = strconv.ParseBool(forceRemovalUnparsed) + if err != nil { + failError(fmt.Errorf("Failed to convert : %s to a bool, error: %+v", forceRemovalUnparsed, err)) + } + } + + exitStatus, err := c.Unlink(node, vmId, disks, forceRemoval) + if err != nil { + failError(fmt.Errorf("error: %+v\n api error: %s", err, exitStatus)) + } + log.Printf("Unlinked disks: %s from vmId: %d. Disks removed: %t", disks, vmId, forceRemoval) + default: fmt.Printf("unknown action, try start|stop vmid\n") } diff --git a/proxmox/client.go b/proxmox/client.go index 5c80279b..593955d6 100644 --- a/proxmox/client.go +++ b/proxmox/client.go @@ -34,6 +34,10 @@ type Client struct { TaskTimeout int } +const ( + VmRef_Error_Nil string = "vm reference may not be nil" +) + // VmRef - virtual machine ref parts // map[type:qemu node:proxmox1-xx id:qemu/132 diskread:5.57424738e+08 disk:0 netin:5.9297450593e+10 mem:3.3235968e+09 uptime:1.4567097e+07 vmid:132 template:0 maxcpu:2 netout:6.053310416e+09 maxdisk:3.4359738368e+10 maxmem:8.592031744e+09 diskwrite:1.49663619584e+12 status:running cpu:0.00386980694947209 name:appt-app1-dev.xxx.xx] type VmRef struct { @@ -81,6 +85,13 @@ func (vmr *VmRef) HaGroup() string { return vmr.haGroup } +func (vmr *VmRef) nilCheck() error { + if vmr == nil { + return errors.New("vm reference may not be nil") + } + return nil +} + func NewVmRef(vmId int) (vmr *VmRef) { vmr = &VmRef{vmId: vmId, node: "", vmType: ""} return @@ -138,7 +149,7 @@ func (c *Client) GetJsonRetryable(url string, data *map[string]interface{}, trie if strings.Contains(statErr.Error(), "500 no such resource") { return statErr } - //fmt.Printf("[DEBUG][GetJsonRetryable] Sleeping for %d seconds before asking url %s", ii+1, url) + // fmt.Printf("[DEBUG][GetJsonRetryable] Sleeping for %d seconds before asking url %s", ii+1, url) time.Sleep(time.Duration(ii+1) * time.Second) } return statErr @@ -149,24 +160,30 @@ func (c *Client) GetNodeList() (list map[string]interface{}, err error) { return } +const resourceListGuest string = "vm" + // GetResourceList returns a list of all enabled proxmox resources. // For resource types that can be in a disabled state, disabled resources // will not be returned -func (c *Client) GetResourceList(resourceType string) (list map[string]interface{}, err error) { - var endpoint = "/cluster/resources" +// TODO this func should not be exported +func (c *Client) GetResourceList(resourceType string) (list []interface{}, err error) { + url := "/cluster/resources" if resourceType != "" { - endpoint = fmt.Sprintf("%s?type=%s", endpoint, resourceType) + url = url + "?type=" + resourceType } - err = c.GetJsonRetryable(endpoint, &list, 3) - return + return c.GetItemListInterfaceArray(url) } -func (c *Client) GetVmList() (list map[string]interface{}, err error) { - list, err = c.GetResourceList("vm") - return +// TODO deprecate once nothing uses this anymore, use ListGuests() instead +func (c *Client) GetVmList() (map[string]interface{}, error) { + list, err := c.GetResourceList(resourceListGuest) + return map[string]interface{}{"data": list}, err } func (c *Client) CheckVmRef(vmr *VmRef) (err error) { + if vmr == nil { + return errors.New(VmRef_Error_Nil) + } if vmr.node == "" || vmr.vmType == "" { _, err = c.GetVmInfo(vmr) } @@ -174,13 +191,8 @@ func (c *Client) CheckVmRef(vmr *VmRef) (err error) { } func (c *Client) GetVmInfo(vmr *VmRef) (vmInfo map[string]interface{}, err error) { - var resp map[string]interface{} - if resp, err = c.GetVmList(); err != nil { - return - } - vms, ok := resp["data"].([]interface{}) - if !ok { - err = fmt.Errorf("failed to cast response to list, resp: %v", resp) + vms, err := c.GetResourceList(resourceListGuest) + if err != nil { return } for vmii := range vms { @@ -212,11 +224,10 @@ func (c *Client) GetVmRefByName(vmName string) (vmr *VmRef, err error) { } func (c *Client) GetVmRefsByName(vmName string) (vmrs []*VmRef, err error) { - resp, err := c.GetVmList() + vms, err := c.GetResourceList(resourceListGuest) if err != nil { return } - vms := resp["data"].([]interface{}) for vmii := range vms { vm := vms[vmii].(map[string]interface{}) if vm["name"] != nil && vm["name"].(string) == vmName { @@ -240,6 +251,35 @@ func (c *Client) GetVmRefsByName(vmName string) (vmrs []*VmRef, err error) { } } +func (c *Client) GetVmRefById(vmId int) (vmr *VmRef, err error) { + var exist bool = false + vms, err := c.GetResourceList(resourceListGuest) + if err != nil { + return + } + for vmii := range vms { + vm := vms[vmii].(map[string]interface{}) + if int(vm["vmid"].(float64)) != 0 && int(vm["vmid"].(float64)) == vmId { + vmr = NewVmRef(int(vm["vmid"].(float64))) + vmr.node = vm["node"].(string) + vmr.vmType = vm["type"].(string) + vmr.pool = "" + if vm["pool"] != nil { + vmr.pool = vm["pool"].(string) + } + if vm["hastate"] != nil { + vmr.haState = vm["hastate"].(string) + } + return + } + } + if !exist { + return nil, fmt.Errorf("vm 'id-%d' not found", vmId) + } else { + return + } +} + func (c *Client) GetVmState(vmr *VmRef) (vmState map[string]interface{}, err error) { err = c.CheckVmRef(vmr) if err != nil { @@ -380,7 +420,9 @@ func (c *Client) CreateTemplate(vmr *VmRef) error { return err } - if exitStatus != "OK" { + // Specifically ignore empty exit status for LXCs, since they don't return a task ID + // when creating templates in the first place (but still successfully create them). + if exitStatus != "OK" && vmr.vmType != "lxc" { return errors.New("Can't convert Vm to template:" + exitStatus) } @@ -443,7 +485,10 @@ func (c *Client) WaitForCompletion(taskResponse map[string]interface{}) (waitExi return "", fmt.Errorf("Wait timeout for:" + taskUpid) } -var rxTaskNode = regexp.MustCompile("UPID:(.*?):") +var ( + rxTaskNode = regexp.MustCompile("UPID:(.*?):") + rxExitStatusSuccess = regexp.MustCompile(`^(OK|WARNINGS)`) +) func (c *Client) GetTaskExitstatus(taskUpid string) (exitStatus interface{}, err error) { node := rxTaskNode.FindStringSubmatch(taskUpid)[1] @@ -453,7 +498,7 @@ func (c *Client) GetTaskExitstatus(taskUpid string) (exitStatus interface{}, err if err == nil { exitStatus = data["data"].(map[string]interface{})["exitstatus"] } - if exitStatus != nil && exitStatus != exitStatusSuccess { + if exitStatus != nil && rxExitStatusSuccess.FindString(exitStatus.(string)) == "" { err = fmt.Errorf(exitStatus.(string)) } return @@ -492,6 +537,10 @@ func (c *Client) ResetVm(vmr *VmRef) (exitStatus string, err error) { return c.StatusChangeVm(vmr, nil, "reset") } +func (c *Client) RebootVm(vmr *VmRef) (exitStatus string, err error) { + return c.StatusChangeVm(vmr, nil, "reboot") +} + func (c *Client) PauseVm(vmr *VmRef) (exitStatus string, err error) { return c.StatusChangeVm(vmr, nil, "suspend") } @@ -517,7 +566,7 @@ func (c *Client) DeleteVmParams(vmr *VmRef, params map[string]interface{}) (exit return "", err } - //Remove HA if required + // Remove HA if required if vmr.haState != "" { url := fmt.Sprintf("/cluster/ha/resources/%d", vmr.vmId) resp, err := c.session.Delete(url, nil, nil) @@ -615,7 +664,6 @@ func (c *Client) CreateLxcContainer(node string, vmParams map[string]interface{} } func (c *Client) CloneLxcContainer(vmr *VmRef, vmParams map[string]interface{}) (exitStatus string, err error) { - reqbody := ParamsToBody(vmParams) url := fmt.Sprintf("/nodes/%s/lxc/%s/clone", vmr.node, vmParams["vmid"]) resp, err := c.session.Post(url, nil, nil, &reqbody) @@ -676,7 +724,7 @@ func (c *Client) CreateQemuSnapshot(vmr *VmRef, snapshotName string) (exitStatus // DEPRECATED superseded by DeleteSnapshot() func (c *Client) DeleteQemuSnapshot(vmr *VmRef, snapshotName string) (exitStatus string, err error) { - return DeleteSnapshot(c, vmr, snapshotName) + return DeleteSnapshot(c, vmr, SnapshotName(snapshotName)) } // DEPRECATED superseded by ListSnapshots() @@ -702,7 +750,7 @@ func (c *Client) RollbackQemuVm(vmr *VmRef, snapshot string) (exitStatus string, return RollbackSnapshot(c, vmr, snapshot) } -// SetVmConfig - send config options +// DEPRECATED SetVmConfig - send config options func (c *Client) SetVmConfig(vmr *VmRef, params map[string]interface{}) (exitStatus interface{}, err error) { return c.PostWithTask(params, "/nodes/"+vmr.node+"/"+vmr.vmType+"/"+strconv.Itoa(vmr.vmId)+"/config") } @@ -742,6 +790,7 @@ func (c *Client) MigrateNode(vmr *VmRef, newTargetNode string, online bool) (exi } // ResizeQemuDisk allows the caller to increase the size of a disk by the indicated number of gigabytes +// TODO Deprecate once LXC is able to resize disk by itself (qemu can already do this) func (c *Client) ResizeQemuDisk(vmr *VmRef, disk string, moreSizeGB int) (exitStatus interface{}, err error) { size := fmt.Sprintf("+%dG", moreSizeGB) return c.ResizeQemuDiskRaw(vmr, disk, size) @@ -752,10 +801,11 @@ func (c *Client) ResizeQemuDisk(vmr *VmRef, disk string, moreSizeGB int) (exitSt // your desired size with a '+' character it will ADD size to the disk. If you just specify the size by // itself it will do an absolute resizing to the specified size. Permitted suffixes are K, M, G, T // to indicate order of magnitude (kilobyte, megabyte, etc). Decrease of disk size is not permitted. +// TODO Deprecate once LXC is able to resize disk by itself (qemu can already do this) func (c *Client) ResizeQemuDiskRaw(vmr *VmRef, disk string, size string) (exitStatus interface{}, err error) { // PUT //disk:virtio0 - //size:+2G + // size:+2G if disk == "" { disk = "virtio0" } @@ -792,6 +842,7 @@ func (c *Client) MoveLxcDisk(vmr *VmRef, disk string, storage string) (exitStatu return } +// DEPRECATED use MoveQemuDisk() instead. // MoveQemuDisk - Move a disk from one storage to another func (c *Client) MoveQemuDisk(vmr *VmRef, disk string, storage string) (exitStatus interface{}, err error) { if disk == "" { @@ -831,6 +882,25 @@ func (c *Client) MoveQemuDiskToVM(vmrSource *VmRef, disk string, vmrTarget *VmRe return } +// Unlink - Unlink (detach) a set of disks from a VM. +// Reference: https://pve.proxmox.com/pve-docs/api-viewer/index.html#/nodes/{node}/qemu/{vmid}/unlink +func (c *Client) Unlink(node string, vmId int, diskIds string, forceRemoval bool) (exitStatus string, err error) { + url := fmt.Sprintf("/nodes/%s/qemu/%d/unlink", node, vmId) + data := ParamsToBody(map[string]interface{}{ + "idlist": diskIds, + "force": forceRemoval, + }) + resp, err := c.session.Put(url, nil, nil, &data) + if err != nil { + return c.HandleTaskError(resp), err + } + json, err := ResponseJSON(resp) + if err != nil { + return "", err + } + return c.WaitForCompletion(json) +} + // GetNextID - Get next free VMID func (c *Client) GetNextID(currentID int) (nextID int, err error) { var data map[string]interface{} @@ -858,11 +928,10 @@ func (c *Client) GetNextID(currentID int) (nextID int, err error) { // VMIdExists - If you pass an VMID that exists it will return true, otherwise it wil return false func (c *Client) VMIdExists(vmID int) (exists bool, err error) { - resp, err := c.GetVmList() + vms, err := c.GetResourceList(resourceListGuest) if err != nil { return } - vms := resp["data"].([]interface{}) for vmii := range vms { vm := vms[vmii].(map[string]interface{}) if vmID == int(vm["vmid"].(float64)) { @@ -879,7 +948,6 @@ func (c *Client) CreateVMDisk( fullDiskName string, diskParams map[string]interface{}, ) error { - reqbody := ParamsToBody(diskParams) url := fmt.Sprintf("/nodes/%s/storage/%s/content", nodeName, storageName) resp, err := c.session.Post(url, nil, nil, &reqbody) @@ -935,7 +1003,6 @@ func (c *Client) createVMDisks( // CreateNewDisk - This method allows simpler disk creation for direct client users // It should work for any existing container and virtual machine func (c *Client) CreateNewDisk(vmr *VmRef, disk string, volume string) (exitStatus interface{}, err error) { - reqbody := ParamsToBody(map[string]interface{}{disk: volume}) url := fmt.Sprintf("/nodes/%s/%s/%d/config", vmr.node, vmr.vmType, vmr.vmId) resp, err := c.session.Put(url, nil, nil, &reqbody) @@ -1331,6 +1398,47 @@ func (c *Client) Upload(node string, storage string, contentType string, filenam return nil } +func (c *Client) UploadLargeFile(node string, storage string, contentType string, filename string, filesize int64, file io.Reader) error { + var contentLength int64 + + var body io.Reader + var mimetype string + var err error + body, mimetype, contentLength, err = createStreamedUploadBody(contentType, filename, filesize, file) + if err != nil { + return err + } + + url := fmt.Sprintf("%s/nodes/%s/storage/%s/upload", c.session.ApiUrl, node, storage) + headers := c.session.Headers.Clone() + headers.Add("Content-Type", mimetype) + headers.Add("Accept", "application/json") + req, err := c.session.NewRequest(http.MethodPost, url, &headers, body) + if err != nil { + return err + } + + req.ContentLength = contentLength + + resp, err := c.session.Do(req) + if err != nil { + return err + } + + taskResponse, err := ResponseJSON(resp) + if err != nil { + return err + } + exitStatus, err := c.WaitForCompletion(taskResponse) + if err != nil { + return err + } + if exitStatus != exitStatusSuccess { + return fmt.Errorf("moving file to destination failed: %v", exitStatus) + } + return nil +} + func createUploadBody(contentType string, filename string, r io.Reader) (io.Reader, string, error) { var buf bytes.Buffer w := multipart.NewWriter(&buf) @@ -1475,7 +1583,6 @@ func (c *Client) ReadVMHA(vmr *VmRef) (err error) { } } return - } func (c *Client) UpdateVMHA(vmr *VmRef, haState string, haGroup string) (exitStatus interface{}, err error) { @@ -1484,7 +1591,7 @@ func (c *Client) UpdateVMHA(vmr *VmRef, haState string, haGroup string) (exitSta return } - //Remove HA + // Remove HA if haState == "" { url := fmt.Sprintf("/cluster/ha/resources/%d", vmr.vmId) resp, err := c.session.Delete(url, nil, nil) @@ -1501,7 +1608,7 @@ func (c *Client) UpdateVMHA(vmr *VmRef, haState string, haGroup string) (exitSta return nil, err } - //Activate HA + // Activate HA if vmr.haState == "" { paramMap := map[string]interface{}{ "sid": vmr.vmId, @@ -1524,7 +1631,7 @@ func (c *Client) UpdateVMHA(vmr *VmRef, haState string, haGroup string) (exitSta } } - //Set wanted state + // Set wanted state paramMap := map[string]interface{}{ "state": haState, "group": haGroup, @@ -1572,8 +1679,7 @@ func (c *Client) DeletePool(poolid string) error { return c.Delete("/pools/" + poolid) } -//permissions check - +// permissions check func (c *Client) GetUserPermissions(id UserID, path string) (permissions []string, err error) { existence, err := CheckUserExistence(id, c) if err != nil { @@ -1786,6 +1892,173 @@ func (c *Client) RevertNetwork(node string) (exitStatus string, err error) { return c.DeleteWithTask(url) } +// SDN + +func (c *Client) ApplySDN() (string, error) { + return c.PutWithTask(nil, "/cluster/sdn") +} + +// GetSDNVNets returns a list of all VNet definitions in the "data" element of the returned +// map. +func (c *Client) GetSDNVNets(pending bool) (list map[string]interface{}, err error) { + url := fmt.Sprintf("/cluster/sdn/vnets?pending=%d", Btoi(pending)) + err = c.GetJsonRetryable(url, &list, 3) + return +} + +// CheckSDNVNetExistance returns true if a DNS entry with the provided ID exists, false otherwise. +func (c *Client) CheckSDNVNetExistance(id string) (existance bool, err error) { + list, err := c.GetSDNVNets(true) + existance = ItemInKeyOfArray(list["data"].([]interface{}), "vnet", id) + return +} + +// GetSDNVNet returns details about the DNS entry whose name was provided. +// An error is returned if the zone doesn't exist. +// The returned zone can be unmarshalled into a ConfigSDNVNet struct. +func (c *Client) GetSDNVNet(name string) (dns map[string]interface{}, err error) { + url := fmt.Sprintf("/cluster/sdn/vnets/%s", name) + err = c.GetJsonRetryable(url, &dns, 3) + return +} + +// CreateSDNVNet creates a new SDN DNS in the cluster +func (c *Client) CreateSDNVNet(params map[string]interface{}) error { + return c.Post(params, "/cluster/sdn/vnets") +} + +// DeleteSDNVNet deletes an existing SDN DNS in the cluster +func (c *Client) DeleteSDNVNet(name string) error { + return c.Delete(fmt.Sprintf("/cluster/sdn/vnets/%s", name)) +} + +// UpdateSDNVNet updates the given DNS with the provided parameters +func (c *Client) UpdateSDNVNet(id string, params map[string]interface{}) error { + return c.Put(params, "/cluster/sdn/vnets/"+id) +} + +// GetSDNSubnets returns a list of all Subnet definitions in the "data" element of the returned +// map. +func (c *Client) GetSDNSubnets(vnet string) (list map[string]interface{}, err error) { + url := fmt.Sprintf("/cluster/sdn/vnets/%s/subnets", vnet) + err = c.GetJsonRetryable(url, &list, 3) + return +} + +// CheckSDNSubnetExistance returns true if a DNS entry with the provided ID exists, false otherwise. +func (c *Client) CheckSDNSubnetExistance(vnet, id string) (existance bool, err error) { + list, err := c.GetSDNSubnets(vnet) + existance = ItemInKeyOfArray(list["data"].([]interface{}), "subnet", id) + return +} + +// GetSDNSubnet returns details about the Subnet entry whose name was provided. +// An error is returned if the zone doesn't exist. +// The returned map["data"] section can be unmarshalled into a ConfigSDNSubnet struct. +func (c *Client) GetSDNSubnet(vnet, name string) (subnet map[string]interface{}, err error) { + url := fmt.Sprintf("/cluster/sdn/vnets/%s/subnets/%s", vnet, name) + err = c.GetJsonRetryable(url, &subnet, 3) + return +} + +// CreateSDNSubnet creates a new SDN DNS in the cluster +func (c *Client) CreateSDNSubnet(vnet string, params map[string]interface{}) error { + return c.Post(params, fmt.Sprintf("/cluster/sdn/vnets/%s/subnets", vnet)) +} + +// DeleteSDNSubnet deletes an existing SDN DNS in the cluster +func (c *Client) DeleteSDNSubnet(vnet, name string) error { + return c.Delete(fmt.Sprintf("/cluster/sdn/vnets/%s/subnets/%s", vnet, name)) +} + +// UpdateSDNSubnet updates the given DNS with the provided parameters +func (c *Client) UpdateSDNSubnet(vnet, id string, params map[string]interface{}) error { + return c.Put(params, fmt.Sprintf("/cluster/sdn/vnets/%s/subnets/%s", vnet, id)) +} + +// GetSDNDNSs returns a list of all DNS definitions in the "data" element of the returned +// map. +func (c *Client) GetSDNDNSs(typeFilter string) (list map[string]interface{}, err error) { + url := "/cluster/sdn/dns" + if typeFilter != "" { + url += fmt.Sprintf("&type=%s", typeFilter) + } + err = c.GetJsonRetryable(url, &list, 3) + return +} + +// CheckSDNDNSExistance returns true if a DNS entry with the provided ID exists, false otherwise. +func (c *Client) CheckSDNDNSExistance(id string) (existance bool, err error) { + list, err := c.GetSDNDNSs("") + existance = ItemInKeyOfArray(list["data"].([]interface{}), "dns", id) + return +} + +// GetSDNDNS returns details about the DNS entry whose name was provided. +// An error is returned if the zone doesn't exist. +// The returned zone can be unmarshalled into a ConfigSDNDNS struct. +func (c *Client) GetSDNDNS(name string) (dns map[string]interface{}, err error) { + url := fmt.Sprintf("/cluster/sdn/dns/%s", name) + err = c.GetJsonRetryable(url, &dns, 3) + return +} + +// CreateSDNDNS creates a new SDN DNS in the cluster +func (c *Client) CreateSDNDNS(params map[string]interface{}) error { + return c.Post(params, "/cluster/sdn/dns") +} + +// DeleteSDNDNS deletes an existing SDN DNS in the cluster +func (c *Client) DeleteSDNDNS(name string) error { + return c.Delete(fmt.Sprintf("/cluster/sdn/dns/%s", name)) +} + +// UpdateSDNDNS updates the given DNS with the provided parameters +func (c *Client) UpdateSDNDNS(id string, params map[string]interface{}) error { + return c.Put(params, "/cluster/sdn/dns/"+id) +} + +// GetSDNZones returns a list of all the SDN zones defined in the cluster. +func (c *Client) GetSDNZones(pending bool, typeFilter string) (list map[string]interface{}, err error) { + url := fmt.Sprintf("/cluster/sdn/zones?pending=%d", Btoi(pending)) + if typeFilter != "" { + url += fmt.Sprintf("&type=%s", typeFilter) + } + err = c.GetJsonRetryable(url, &list, 3) + return +} + +// CheckSDNZoneExistance returns true if a zone with the provided ID exists, false otherwise. +func (c *Client) CheckSDNZoneExistance(id string) (existance bool, err error) { + list, err := c.GetSDNZones(true, "") + existance = ItemInKeyOfArray(list["data"].([]interface{}), "zone", id) + return +} + +// GetSDNZone returns details about the zone whose name was provided. +// An error is returned if the zone doesn't exist. +// The returned zone can be unmarshalled into a ConfigSDNZone struct. +func (c *Client) GetSDNZone(zoneName string) (zone map[string]interface{}, err error) { + url := fmt.Sprintf("/cluster/sdn/zones/%s", zoneName) + err = c.GetJsonRetryable(url, &zone, 3) + return +} + +// CreateSDNZone creates a new SDN zone in the cluster +func (c *Client) CreateSDNZone(params map[string]interface{}) error { + return c.Post(params, "/cluster/sdn/zones") +} + +// DeleteSDNZone deletes an existing SDN zone in the cluster +func (c *Client) DeleteSDNZone(zoneName string) error { + return c.Delete(fmt.Sprintf("/cluster/sdn/zones/%s", zoneName)) +} + +// UpdateSDNZone updates the given zone with the provided parameters +func (c *Client) UpdateSDNZone(id string, params map[string]interface{}) error { + return c.Put(params, "/cluster/sdn/zones/"+id) +} + // Shared func (c *Client) GetItemConfigMapStringInterface(url, text, message string) (map[string]interface{}, error) { data, err := c.GetItemConfig(url, text, message) @@ -1903,7 +2176,11 @@ func (c *Client) GetItemListInterfaceArray(url string) ([]interface{}, error) { if err != nil { return nil, err } - return list["data"].([]interface{}), nil + data, ok := list["data"].([]interface{}) + if !ok { + return nil, fmt.Errorf("failed to cast response to list, resp: %v", list) + } + return data, nil } func (c *Client) GetItemList(url string) (list map[string]interface{}, err error) { diff --git a/proxmox/config_guest.go b/proxmox/config_guest.go new file mode 100644 index 00000000..db25050c --- /dev/null +++ b/proxmox/config_guest.go @@ -0,0 +1,220 @@ +package proxmox + +import ( + "errors" + "strconv" + "strings" +) + +// All code LXC and Qemu have in common should be placed here. + +type GuestResource struct { + CpuCores uint `json:"cpu_cores"` + CpuUsage float64 `json:"cpu_usage"` + DiskReadTotal uint `json:"disk_read"` + DiskSizeInBytes uint `json:"disk_size"` + DiskUsedInBytes uint `json:"disk_used"` + DiskWriteTotal uint `json:"disk_write"` + HaState string `json:"hastate"` // TODO custom type? + Id uint `json:"id"` + MemoryTotalInBytes uint `json:"memory_total"` + MemoryUsedInBytes uint `json:"memory_used"` + Name string `json:"name"` // TODO custom type + NetworkIn uint `json:"network_in"` + NetworkOut uint `json:"network_out"` + Node string `json:"node"` // TODO custom type + Pool string `json:"pool"` // TODO custom type + Status string `json:"status"` // TODO custom type? + Tags []string `json:"tags"` // TODO custom type + Template bool `json:"template"` + Type GuestType `json:"type"` + UptimeInSeconds uint `json:"uptime"` +} + +// https://pve.proxmox.com/pve-docs/api-viewer/#/cluster/resources +func (GuestResource) mapToStruct(params []interface{}) []GuestResource { + if len(params) == 0 { + return nil + } + resources := make([]GuestResource, len(params)) + for i := range params { + tmpParams := params[i].(map[string]interface{}) + if _, isSet := tmpParams["maxcpu"]; isSet { + resources[i].CpuCores = uint(tmpParams["maxcpu"].(float64)) + } + if _, isSet := tmpParams["cpu"]; isSet { + resources[i].CpuUsage = tmpParams["cpu"].(float64) + } + if _, isSet := tmpParams["diskread"]; isSet { + resources[i].DiskReadTotal = uint(tmpParams["diskread"].(float64)) + } + if _, isSet := tmpParams["maxdisk"]; isSet { + resources[i].DiskSizeInBytes = uint(tmpParams["maxdisk"].(float64)) + } + if _, isSet := tmpParams["disk"]; isSet { + resources[i].DiskUsedInBytes = uint(tmpParams["disk"].(float64)) + } + if _, isSet := tmpParams["diskwrite"]; isSet { + resources[i].DiskWriteTotal = uint(tmpParams["diskwrite"].(float64)) + } + if _, isSet := tmpParams["hastate"]; isSet { + resources[i].HaState = tmpParams["hastate"].(string) + } + if _, isSet := tmpParams["vmid"]; isSet { + resources[i].Id = uint(tmpParams["vmid"].(float64)) + } + if _, isSet := tmpParams["maxmem"]; isSet { + resources[i].MemoryTotalInBytes = uint(tmpParams["maxmem"].(float64)) + } + if _, isSet := tmpParams["mem"]; isSet { + resources[i].MemoryUsedInBytes = uint(tmpParams["mem"].(float64)) + } + if _, isSet := tmpParams["name"]; isSet { + resources[i].Name = tmpParams["name"].(string) + } + if _, isSet := tmpParams["netin"]; isSet { + resources[i].NetworkIn = uint(tmpParams["netin"].(float64)) + } + if _, isSet := tmpParams["netout"]; isSet { + resources[i].NetworkOut = uint(tmpParams["netout"].(float64)) + } + if _, isSet := tmpParams["node"]; isSet { + resources[i].Node = tmpParams["node"].(string) + } + if _, isSet := tmpParams["pool"]; isSet { + resources[i].Pool = tmpParams["pool"].(string) + } + if _, isSet := tmpParams["status"]; isSet { + resources[i].Status = tmpParams["status"].(string) + } + if _, isSet := tmpParams["tags"]; isSet { + resources[i].Tags = strings.Split(tmpParams["tags"].(string), ";") + } + if _, isSet := tmpParams["template"]; isSet { + resources[i].Template = Itob(int(tmpParams["template"].(float64))) + } + if _, isSet := tmpParams["type"]; isSet { + resources[i].Type = GuestType(tmpParams["type"].(string)) + } + if _, isSet := tmpParams["uptime"]; isSet { + resources[i].UptimeInSeconds = uint(tmpParams["uptime"].(float64)) + } + } + return resources +} + +// Enum +type GuestFeature string + +const ( + GuestFeature_Clone GuestFeature = "clone" + GuestFeature_Copy GuestFeature = "copy" + GuestFeature_Snapshot GuestFeature = "snapshot" +) + +func (GuestFeature) Error() error { + return errors.New("value should be one of (" + string(GuestFeature_Clone) + " ," + string(GuestFeature_Copy) + " ," + string(GuestFeature_Snapshot) + ")") +} + +func (GuestFeature) mapToStruct(params map[string]interface{}) bool { + if value, isSet := params["hasFeature"]; isSet { + return Itob(int(value.(float64))) + } + return false +} + +func (feature GuestFeature) Validate() error { + switch feature { + case GuestFeature_Copy, GuestFeature_Clone, GuestFeature_Snapshot: + return nil + } + return GuestFeature("").Error() +} + +type GuestFeatures struct { + Clone bool `json:"clone"` + Copy bool `json:"copy"` + Snapshot bool `json:"snapshot"` +} + +type GuestType string + +const ( + GuestLXC GuestType = "lxc" + GuestQemu GuestType = "qemu" +) + +// check if the guest has the specified feature. +func GuestHasFeature(vmr *VmRef, client *Client, feature GuestFeature) (bool, error) { + err := feature.Validate() + if err != nil { + return false, err + } + err = client.CheckVmRef(vmr) + if err != nil { + return false, err + } + return guestHasFeature(vmr, client, feature) +} + +func guestHasFeature(vmr *VmRef, client *Client, feature GuestFeature) (bool, error) { + var params map[string]interface{} + params, err := client.GetItemConfigMapStringInterface("/nodes/"+vmr.node+"/"+vmr.vmType+"/"+strconv.Itoa(vmr.vmId)+"/feature?feature=snapshot", "guest", "FEATURES") + if err != nil { + return false, err + } + return GuestFeature("").mapToStruct(params), nil +} + +// Check if there are any pending changes that require a reboot to be applied. +func GuestHasPendingChanges(vmr *VmRef, client *Client) (bool, error) { + params, err := pendingGuestConfigFromApi(vmr, client) + if err != nil { + return false, err + } + return keyExists(params, "pending"), nil +} + +// Reboot the specified guest +func GuestReboot(vmr *VmRef, client *Client) (err error) { + _, err = client.ShutdownVm(vmr) + if err != nil { + return + } + _, err = client.StartVm(vmr) + return +} + +// List all features the guest has. +func ListGuestFeatures(vmr *VmRef, client *Client) (features GuestFeatures, err error) { + err = client.CheckVmRef(vmr) + if err != nil { + return + } + features.Clone, err = guestHasFeature(vmr, client, GuestFeature_Clone) + if err != nil { + return + } + features.Copy, err = guestHasFeature(vmr, client, GuestFeature_Copy) + if err != nil { + return + } + features.Snapshot, err = guestHasFeature(vmr, client, GuestFeature_Snapshot) + return +} + +// List all guest the user has viewing rights for in the cluster +func ListGuests(client *Client) ([]GuestResource, error) { + list, err := client.GetResourceList("vm") + if err != nil { + return nil, err + } + return GuestResource{}.mapToStruct(list), nil +} + +func pendingGuestConfigFromApi(vmr *VmRef, client *Client) ([]interface{}, error) { + if err := client.CheckVmRef(vmr); err != nil { + return nil, err + } + return client.GetItemConfigInterfaceArray("/nodes/"+vmr.node+"/"+vmr.vmType+"/"+strconv.Itoa(vmr.vmId)+"/pending", "Guest", "PENDING CONFIG") +} diff --git a/proxmox/config_guest_test.go b/proxmox/config_guest_test.go new file mode 100644 index 00000000..d22f5eec --- /dev/null +++ b/proxmox/config_guest_test.go @@ -0,0 +1,298 @@ +package proxmox + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_GuestResource_mapToStruct(t *testing.T) { + tests := []struct { + name string + input []interface{} + output []GuestResource + }{ + {name: "CpuCores", + input: []interface{}{map[string]interface{}{"maxcpu": float64(10)}}, + output: []GuestResource{{CpuCores: 10}}, + }, + {name: "CpuUsage", + input: []interface{}{map[string]interface{}{"cpu": float64(3.141592653589793)}}, + output: []GuestResource{{CpuUsage: 3.141592653589793}}, + }, + {name: "DiskReadTotal", + input: []interface{}{map[string]interface{}{"diskread": float64(1637428)}}, + output: []GuestResource{{DiskReadTotal: 1637428}}, + }, + {name: "DiskSizeInBytes", + input: []interface{}{map[string]interface{}{"maxdisk": float64(8589934592)}}, + output: []GuestResource{{DiskSizeInBytes: 8589934592}}, + }, + {name: "DiskUsedInBytes", + input: []interface{}{map[string]interface{}{"disk": float64(1073741824)}}, + output: []GuestResource{{DiskUsedInBytes: 1073741824}}, + }, + {name: "DiskWriteTotal", + input: []interface{}{map[string]interface{}{"diskwrite": float64(1690811)}}, + output: []GuestResource{{DiskWriteTotal: 1690811}}, + }, + {name: "HaState", + input: []interface{}{map[string]interface{}{"hastate": "started"}}, + output: []GuestResource{{HaState: "started"}}, + }, + {name: "Id", + input: []interface{}{map[string]interface{}{"vmid": float64(100)}}, + output: []GuestResource{{Id: 100}}, + }, + {name: "MemoryTotalInBytes", + input: []interface{}{map[string]interface{}{"maxmem": float64(2147483648)}}, + output: []GuestResource{{MemoryTotalInBytes: 2147483648}}, + }, + {name: "MemoryUsedInBytes", + input: []interface{}{map[string]interface{}{"mem": float64(1048576)}}, + output: []GuestResource{{MemoryUsedInBytes: 1048576}}, + }, + {name: "Name", + input: []interface{}{map[string]interface{}{"name": "test-vm1"}}, + output: []GuestResource{{Name: "test-vm1"}}, + }, + {name: "NetworkIn", + input: []interface{}{map[string]interface{}{"netin": float64(23884639)}}, + output: []GuestResource{{NetworkIn: 23884639}}, + }, + {name: "NetworkOut", + input: []interface{}{map[string]interface{}{"netout": float64(1000123465987)}}, + output: []GuestResource{{NetworkOut: 1000123465987}}, + }, + {name: "Node", + input: []interface{}{map[string]interface{}{"node": "pve1"}}, + output: []GuestResource{{Node: "pve1"}}, + }, + {name: "Pool", + input: []interface{}{map[string]interface{}{"pool": "Production"}}, + output: []GuestResource{{Pool: "Production"}}, + }, + {name: "Status", + input: []interface{}{map[string]interface{}{"status": "running"}}, + output: []GuestResource{{Status: "running"}}, + }, + {name: "Tags", + input: []interface{}{map[string]interface{}{"tags": "tag1;tag2;tag3"}}, + output: []GuestResource{{Tags: []string{"tag1", "tag2", "tag3"}}}, + }, + {name: "Template", + input: []interface{}{map[string]interface{}{"template": float64(1)}}, + output: []GuestResource{{Template: true}}, + }, + {name: "Type", + input: []interface{}{map[string]interface{}{"type": "qemu"}}, + output: []GuestResource{{Type: GuestQemu}}, + }, + {name: "UptimeInSeconds", + input: []interface{}{map[string]interface{}{"uptime": float64(72169)}}, + output: []GuestResource{{UptimeInSeconds: 72169}}, + }, + {name: "[]GuestResource", + input: []interface{}{ + map[string]interface{}{ + "maxcpu": float64(10), + "cpu": float64(3.141592653589793), + "diskread": float64(1637428), + "maxdisk": float64(8589934592), + "disk": float64(0), + "diskwrite": float64(1690811), + "hastate": "started", + "vmid": float64(100), + "maxmem": float64(2147483648), + "mem": float64(1048576), + "name": "test-vm1", + "netin": float64(23884639), + "netout": float64(1000123465987), + "node": "pve1", + "pool": "Production", + "status": "running", + "tags": "tag1;tag2;tag3", + "template": float64(0), + "type": "qemu", + "uptime": float64(72169), + }, + map[string]interface{}{ + "maxcpu": float64(50), + "cpu": float64(0.141592653589793), + "diskread": float64(857324), + "maxdisk": float64(9743424), + "disk": float64(23234), + "diskwrite": float64(78347843754), + "hastate": "", + "vmid": float64(100000), + "maxmem": float64(946856732535), + "mem": float64(1342), + "name": "dev-vm1", + "netin": float64(2331323424), + "netout": float64(88775378423476), + "node": "pve2", + "pool": "Development", + "status": "running", + "tags": "dev", + "template": float64(0), + "type": "lxc", + "uptime": float64(88678345), + }, + map[string]interface{}{ + "maxcpu": float64(1), + "cpu": float64(0), + "diskread": float64(846348234), + "disk": float64(0), + "maxdisk": float64(56742482484), + "diskwrite": float64(3432), + "hastate": "", + "vmid": float64(999), + "maxmem": float64(727345728374), + "mem": float64(68467234324), + "name": "template-linux", + "netin": float64(23884639), + "netout": float64(1000123465987), + "node": "node3", + "pool": "Templates", + "status": "stopped", + "tags": "template", + "template": float64(1), + "type": "qemu", + "uptime": float64(0), + }, + }, + output: []GuestResource{ + { + CpuCores: 10, + CpuUsage: 3.141592653589793, + DiskReadTotal: 1637428, + DiskUsedInBytes: 0, + DiskSizeInBytes: 8589934592, + DiskWriteTotal: 1690811, + HaState: "started", + Id: 100, + MemoryTotalInBytes: 2147483648, + MemoryUsedInBytes: 1048576, + Name: "test-vm1", + NetworkIn: 23884639, + NetworkOut: 1000123465987, + Node: "pve1", + Pool: "Production", + Status: "running", + Tags: []string{"tag1", "tag2", "tag3"}, + Template: false, + Type: GuestQemu, + UptimeInSeconds: 72169, + }, + { + CpuCores: 50, + CpuUsage: 0.141592653589793, + DiskReadTotal: 857324, + DiskUsedInBytes: 23234, + DiskSizeInBytes: 9743424, + DiskWriteTotal: 78347843754, + HaState: "", + Id: 100000, + MemoryTotalInBytes: 946856732535, + MemoryUsedInBytes: 1342, + Name: "dev-vm1", + NetworkIn: 2331323424, + NetworkOut: 88775378423476, + Node: "pve2", + Pool: "Development", + Status: "running", + Tags: []string{"dev"}, + Template: false, + Type: GuestLXC, + UptimeInSeconds: 88678345, + }, + { + CpuCores: 1, + CpuUsage: 0, + DiskReadTotal: 846348234, + DiskUsedInBytes: 0, + DiskSizeInBytes: 56742482484, + DiskWriteTotal: 3432, + HaState: "", + Id: 999, + MemoryTotalInBytes: 727345728374, + MemoryUsedInBytes: 68467234324, + Name: "template-linux", + NetworkIn: 23884639, + NetworkOut: 1000123465987, + Node: "node3", + Pool: "Templates", + Status: "stopped", + Tags: []string{"template"}, + Template: true, + Type: GuestQemu, + UptimeInSeconds: 0, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(*testing.T) { + require.Equal(t, test.output, GuestResource{}.mapToStruct(test.input), test.name) + }) + } +} + +func Test_GuestFeature_mapToStruct(t *testing.T) { + tests := []struct { + name string + input map[string]interface{} + output bool + }{ + {name: "false", + input: map[string]interface{}{"hasFeature": float64(0)}, + output: false, + }, + {name: "not set", + input: map[string]interface{}{}, + output: false, + }, + {name: "true", + input: map[string]interface{}{"hasFeature": float64(1)}, + output: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(*testing.T) { + require.Equal(t, test.output, GuestFeature("").mapToStruct(test.input), test.name) + }) + } +} + +func Test_GuestFeature_Validate(t *testing.T) { + tests := []struct { + name string + input GuestFeature + err error + }{ + // Invalid + {name: "Invalid empty", + input: "", + err: GuestFeature("").Error(), + }, + {name: "Invalid not enum", + input: "invalid", + err: GuestFeature("").Error(), + }, + // Valid + {name: "Valid GuestFeature_Clone", + input: GuestFeature_Clone, + }, + {name: "Valid GuestFeature_Copy", + input: GuestFeature_Copy, + }, + {name: "Valid GuestFeature_Snapshot", + input: GuestFeature_Snapshot, + }, + } + for _, test := range tests { + t.Run(test.name, func(*testing.T) { + require.Equal(t, test.err, test.input.Validate(), test.name) + }) + } +} diff --git a/proxmox/config_hagroup.go b/proxmox/config_hagroup.go new file mode 100644 index 00000000..67adfea1 --- /dev/null +++ b/proxmox/config_hagroup.go @@ -0,0 +1,56 @@ +package proxmox + +import ( + "errors" + "strings" +) + +type HAGroup struct { + Comment string // Description. + Group string // The HA group identifier. + Nodes []string // List of cluster node names with optional priority. LIKE: [:]{,[:]}* + NoFailback bool // The CRM tries to run services on the node with the highest priority. If a node with higher priority comes online, the CRM migrates the service to that node. Enabling nofailback prevents that behavior. + Restricted bool // Resources bound to restricted groups may only run on nodes defined by the group. + Type string // Group type +} + +func (c *Client) GetHAGroupList() (haGroups []HAGroup, err error) { + list, err := c.GetItemList("/cluster/ha/groups") + + if err != nil { + return nil, err + } + + haGroups = []HAGroup{} + + for _, item := range list["data"].([]interface{}) { + itemMap := item.(map[string]interface{}) + + haGroups = append(haGroups, HAGroup{ + Comment: itemMap["comment"].(string), + Group: itemMap["group"].(string), + Nodes: strings.Split(itemMap["nodes"].(string), ","), + NoFailback: itemMap["nofailback"].(float64) == 1, + Restricted: itemMap["restricted"].(float64) == 1, + Type: itemMap["type"].(string), + }) + } + + return haGroups, nil +} + +func (c *Client) GetHAGroupByName(GroupName string) (*HAGroup, error) { + groups, err := c.GetHAGroupList() + + if err != nil { + return nil, err + } + + for _, item := range groups { + if item.Group == GroupName { + return &item, nil + } + } + + return nil, errors.New("cannot find HaGroup by name " + GroupName) +} diff --git a/proxmox/config_lxc.go b/proxmox/config_lxc.go index cbfb1808..c91d6f87 100644 --- a/proxmox/config_lxc.go +++ b/proxmox/config_lxc.go @@ -473,6 +473,7 @@ func (config ConfigLxc) mapToApiValues() map[string]interface{} { // add mp to lxc parameters mpID := mpConfMap["slot"] mpName := fmt.Sprintf("mp%v", mpID) + paramMap[mpName] = FormatDiskParam(mpConfMap) } diff --git a/proxmox/config_qemu.go b/proxmox/config_qemu.go index 4256ce55..89d376f2 100644 --- a/proxmox/config_qemu.go +++ b/proxmox/config_qemu.go @@ -3,6 +3,7 @@ package proxmox import ( "bytes" "encoding/json" + "errors" "fmt" "log" "math/rand" @@ -35,57 +36,68 @@ type AgentNetworkInterface struct { // ConfigQemu - Proxmox API QEMU options type ConfigQemu struct { - VmID int `json:"vmid,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - Pool string `json:"pool,omitempty"` - Bios string `json:"bios,omitempty"` - EFIDisk QemuDevice `json:"efidisk,omitempty"` - Machine string `json:"machine,omitempty"` - Onboot *bool `json:"onboot,omitempty"` - Startup string `json:"startup,omitempty"` - Tablet *bool `json:"tablet,omitempty"` - Agent int `json:"agent,omitempty"` - Memory int `json:"memory,omitempty"` - Balloon int `json:"balloon,omitempty"` - QemuOs string `json:"ostype,omitempty"` - QemuCores int `json:"cores,omitempty"` - QemuSockets int `json:"sockets,omitempty"` - QemuVcpus int `json:"vcpus,omitempty"` - QemuCpu string `json:"cpu,omitempty"` - QemuNuma *bool `json:"numa,omitempty"` - QemuKVM *bool `json:"kvm,omitempty"` - Hotplug string `json:"hotplug,omitempty"` - QemuIso string `json:"iso,omitempty"` - QemuPxe bool `json:"pxe,omitempty"` - FullClone *int `json:"fullclone,omitempty"` - Boot string `json:"boot,omitempty"` - BootDisk string `json:"bootdisk,omitempty"` - Scsihw string `json:"scsihw,omitempty"` - QemuDisks QemuDevices `json:"disk,omitempty"` - QemuUnusedDisks QemuDevices `json:"unused,omitempty"` - QemuVga QemuDevice `json:"vga,omitempty"` - QemuNetworks QemuDevices `json:"network,omitempty"` - QemuSerials QemuDevices `json:"serial,omitempty"` - QemuUsbs QemuDevices `json:"usb,omitempty"` - QemuPCIDevices QemuDevices `json:"hostpci,omitempty"` - Hookscript string `json:"hookscript,omitempty"` - HaState string `json:"hastate,omitempty"` - HaGroup string `json:"hagroup,omitempty"` - Tags string `json:"tags,omitempty"` - Args string `json:"args,omitempty"` + Agent int `json:"agent,omitempty"` // TODO should probably be a bool + Args string `json:"args,omitempty"` + Balloon int `json:"balloon,omitempty"` // TODO should probably be a bool + Bios string `json:"bios,omitempty"` + Boot string `json:"boot,omitempty"` // TODO should be an array of custom enums + BootDisk string `json:"bootdisk,omitempty"` // TODO discuss deprecation? Only returned as it's deprecated in the proxmox api + CIcustom string `json:"cicustom,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) + CIpassword string `json:"cipassword,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) + CIuser string `json:"ciuser,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) + Description string `json:"description,omitempty"` + Disks *QemuStorages `json:"disks,omitempty"` + EFIDisk QemuDevice `json:"efidisk,omitempty"` // TODO should be a struct + FullClone *int `json:"fullclone,omitempty"` // TODO should probably be a bool + HaGroup string `json:"hagroup,omitempty"` + HaState string `json:"hastate,omitempty"` // TODO should be custom type with enum + Hookscript string `json:"hookscript,omitempty"` + Hotplug string `json:"hotplug,omitempty"` // TODO should be a struct + Ipconfig IpconfigMap `json:"ipconfig,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) + Iso *IsoFile `json:"iso,omitempty"` // Same as Disks.Ide.Disk_2.CdRom.Iso + LinkedVmId uint `json:"linked_id,omitempty"` // Only returned setting it has no effect + Machine string `json:"machine,omitempty"` // TODO should be custom type with enum + Memory int `json:"memory,omitempty"` // TODO should be uint + Name string `json:"name,omitempty"` // TODO should be custom type as there are character and length limitations + Nameserver string `json:"nameserver,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) + Node string `json:"node,omitempty"` + Onboot *bool `json:"onboot,omitempty"` + Pool string `json:"pool,omitempty"` // TODO should be custom type as there are character and length limitations + QemuCores int `json:"cores,omitempty"` // TODO should be uint + QemuCpu string `json:"cpu,omitempty"` // TODO should be custom type with enum + QemuDisks QemuDevices `json:"disk,omitempty"` // DEPRECATED use Disks *QemuStorages instead + QemuIso string `json:"qemuiso,omitempty"` // DEPRECATED use Iso *IsoFile instead + QemuKVM *bool `json:"kvm,omitempty"` + QemuNetworks QemuDevices `json:"network,omitempty"` // TODO should be a struct + QemuNuma *bool `json:"numa,omitempty"` + QemuOs string `json:"ostype,omitempty"` + QemuPCIDevices QemuDevices `json:"hostpci,omitempty"` // TODO should be a struct + QemuPxe bool `json:"pxe,omitempty"` + QemuSerials QemuDevices `json:"serial,omitempty"` // TODO should be a struct + QemuSockets int `json:"sockets,omitempty"` // TODO should be uint + QemuUnusedDisks QemuDevices `json:"unused,omitempty"` // TODO should be a struct + QemuUsbs QemuDevices `json:"usb,omitempty"` // TODO should be a struct + QemuVcpus int `json:"vcpus,omitempty"` // TODO should be uint + QemuVga QemuDevice `json:"vga,omitempty"` // TODO should be a struct + RNGDrive QemuDevice `json:"rng0,omitempty"` // TODO should be a struct + Scsihw string `json:"scsihw,omitempty"` // TODO should be custom type with enum + Searchdomain string `json:"searchdomain,omitempty"` // TODO should be part of a cloud-init struct (cloud-init option) + Smbios1 string `json:"smbios1,omitempty"` // TODO should be custom type with enum? + Sshkeys string `json:"sshkeys,omitempty"` // TODO should be an array of strings + Startup string `json:"startup,omitempty"` // TODO should be a struct? + Tablet *bool `json:"tablet,omitempty"` + Tags string `json:"tags,omitempty"` // TODO should be an array of a custom type as there are character and length limitations + VmID int `json:"vmid,omitempty"` // TODO should be a custom type as there are limitations +} - // cloud-init options - CIuser string `json:"ciuser,omitempty"` - CIpassword string `json:"cipassword,omitempty"` - CIcustom string `json:"cicustom,omitempty"` - Ipconfig IpconfigMap `json:"ipconfig,omitempty"` - - Searchdomain string `json:"searchdomain,omitempty"` - Nameserver string `json:"nameserver,omitempty"` - Sshkeys string `json:"sshkeys,omitempty"` +// Create - Tell Proxmox API to make the VM +func (config ConfigQemu) Create(vmr *VmRef, client *Client) (err error) { + _, err = config.setAdvanced(nil, false, vmr, client) + return } +// DEPRECATED use ConfigQemu{}.Create Instead. +// // CreateVm - Tell Proxmox API to make the VM func (config ConfigQemu) CreateVm(vmr *VmRef, client *Client) (err error) { if config.HasCloudInit() { @@ -157,67 +169,771 @@ func (config ConfigQemu) CreateVm(vmr *VmRef, client *Client) (err error) { params["scsihw"] = config.Scsihw } - err = config.CreateQemuMachineParam(params) - if err != nil { - log.Printf("[ERROR] %q", err) - } + err = config.CreateQemuMachineParam(params) + if err != nil { + log.Printf("[ERROR] %q", err) + } + + // Create disks config. + config.CreateQemuDisksParams(params, false) + + // Create EFI disk + config.CreateQemuEfiParams(params) + + // Create VirtIO RNG + config.CreateQemuRngParams(params) + + // Create vga config. + vgaParam := QemuDeviceParam{} + vgaParam = vgaParam.createDeviceParam(config.QemuVga, nil) + if len(vgaParam) > 0 { + params["vga"] = strings.Join(vgaParam, ",") + } + + // Create networks config. + config.CreateQemuNetworksParams(params) + + // Create ipconfig. + err = config.CreateIpconfigParams(params) + if err != nil { + log.Printf("[ERROR] %q", err) + } + + // Create serial interfaces + config.CreateQemuSerialsParams(params) + + config.CreateQemuPCIsParams(params) + + // Create usb interfaces + config.CreateQemuUsbsParams(params) + + exitStatus, err := client.CreateQemuVm(vmr.node, params) + if err != nil { + return fmt.Errorf("error creating VM: %v, error status: %s (params: %v)", err, exitStatus, params) + } + + _, err = client.UpdateVMHA(vmr, config.HaState, config.HaGroup) + if err != nil { + return fmt.Errorf("[ERROR] %q", err) + } + + return +} + +func (config *ConfigQemu) defaults() { + if config == nil { + return + } + if config.Boot == "" { + config.Boot = "cdn" + } + if config.Bios == "" { + config.Bios = "seabios" + } + if config.RNGDrive == nil { + config.RNGDrive = QemuDevice{} + } + if config.EFIDisk == nil { + config.EFIDisk = QemuDevice{} + } + if config.Onboot == nil { + config.Onboot = PointerBool(true) + } + if config.Hotplug == "" { + config.Hotplug = "network,disk,usb" + } + if config.Ipconfig == nil { + config.Ipconfig = IpconfigMap{} + } + if config.QemuCores == 0 { + config.QemuCores = 1 + } + if config.QemuCpu == "" { + config.QemuCpu = "host" + } + if config.QemuDisks == nil { + config.QemuDisks = QemuDevices{} + } + if config.QemuKVM == nil { + config.QemuKVM = PointerBool(true) + } + if config.QemuNetworks == nil { + config.QemuNetworks = QemuDevices{} + } + if config.QemuOs == "" { + config.QemuOs = "other" + } + if config.QemuPCIDevices == nil { + config.QemuPCIDevices = QemuDevices{} + } + if config.QemuSerials == nil { + config.QemuSerials = QemuDevices{} + } + if config.QemuSockets == 0 { + config.QemuSockets = 1 + } + if config.QemuUnusedDisks == nil { + config.QemuUnusedDisks = QemuDevices{} + } + if config.QemuUsbs == nil { + config.QemuUsbs = QemuDevices{} + } + if config.QemuVga == nil { + config.QemuVga = QemuDevice{} + } + if config.Scsihw == "" { + config.Scsihw = "lsi" + } + if config.Tablet == nil { + config.Tablet = PointerBool(true) + } + +} + +func (config ConfigQemu) mapToApiValues(currentConfig ConfigQemu) (rebootRequired bool, params map[string]interface{}, err error) { + // TODO check if cloudInit settings changed, they require a reboot to take effect. + var itemsToDelete string + + params = map[string]interface{}{} + + if config.VmID != 0 { + params["vmid"] = config.VmID + } + if config.Args != "" { + params["args"] = config.Args + } + if config.Agent != 0 { + params["agent"] = config.Agent + } + if config.Balloon >= 1 { + params["balloon"] = config.Balloon + } + if config.Bios != "" { + params["bios"] = config.Bios + } + if config.Boot != "" { + params["boot"] = config.Boot + } + if config.CIcustom != "" { + params["cicustom"] = config.CIcustom + } + if config.CIpassword != "" { + params["cipassword"] = config.CIpassword + } + if config.CIuser != "" { + params["ciuser"] = config.CIuser + } + if config.QemuCores != 0 { + params["cores"] = config.QemuCores + } + if config.QemuCpu != "" { + params["cpu"] = config.QemuCpu + } + if config.Description != "" { + params["description"] = config.Description + } + if config.Hookscript != "" { + params["hookscript"] = config.Hookscript + } + if config.Hotplug != "" { + params["hotplug"] = config.Hotplug + } + if config.QemuKVM != nil { + params["kvm"] = *config.QemuKVM + } + if config.Machine != "" { + params["machine"] = config.Machine + } + if config.Memory != 0 { + params["memory"] = config.Memory + } + if config.Name != "" { + params["name"] = config.Name + } + if config.Nameserver != "" { + params["nameserver"] = config.Nameserver + } + if config.QemuNuma != nil { + params["numa"] = *config.QemuNuma + } + if config.Onboot != nil { + params["onboot"] = *config.Onboot + } + if config.QemuOs != "" { + params["ostype"] = config.QemuOs + } + if config.Pool != "" { + params["pool"] = config.Pool + } + if config.Scsihw != "" { + params["scsihw"] = config.Scsihw + } + if config.Searchdomain != "" { + params["searchdomain"] = config.Searchdomain + } + if config.QemuSockets != 0 { + params["sockets"] = config.QemuSockets + } + if config.Sshkeys != "" { + params["sshkeys"] = sshKeyUrlEncode(config.Sshkeys) + } + if config.Startup != "" { + params["startup"] = config.Startup + } + if config.Tablet != nil { + params["tablet"] = *config.Tablet + } + if config.Tags != "" { + params["tags"] = config.Tags + } + if config.QemuVcpus >= 1 { + params["vcpus"] = config.QemuVcpus + } + if config.Smbios1 != "" { + params["smbios1"] = config.Smbios1 + } + + if config.Iso != nil { + if config.Disks == nil { + config.Disks = &QemuStorages{} + } + if config.Disks.Ide == nil { + config.Disks.Ide = &QemuIdeDisks{} + } + if config.Disks.Ide.Disk_2 == nil { + config.Disks.Ide.Disk_2 = &QemuIdeStorage{} + } + if config.Disks.Ide.Disk_2.CdRom == nil { + config.Disks.Ide.Disk_2.CdRom = &QemuCdRom{Iso: config.Iso} + } + } + // Disks + if currentConfig.Disks != nil { + if config.Disks != nil { + // Create,Update,Delete + delete := config.Disks.mapToApiValues(*currentConfig.Disks, uint(config.VmID), currentConfig.LinkedVmId, params) + if delete != "" { + itemsToDelete = AddToList(itemsToDelete, delete) + } + } + } else { + if config.Disks != nil { + // Create + config.Disks.mapToApiValues(QemuStorages{}, uint(config.VmID), 0, params) + } + } + + // Create EFI disk + config.CreateQemuEfiParams(params) + + // Create VirtIO RNG + config.CreateQemuRngParams(params) + + // Create networks config. + config.CreateQemuNetworksParams(params) + + // Create vga config. + vgaParam := QemuDeviceParam{} + vgaParam = vgaParam.createDeviceParam(config.QemuVga, nil) + if len(vgaParam) > 0 { + params["vga"] = strings.Join(vgaParam, ",") + } + // Create serial interfaces + config.CreateQemuSerialsParams(params) + + // Create usb interfaces + config.CreateQemuUsbsParams(params) + + config.CreateQemuPCIsParams(params) + + err = config.CreateIpconfigParams(params) + if err != nil { + log.Printf("[ERROR] %q", err) + } + + if itemsToDelete != "" { + params["delete"] = itemsToDelete + } + return +} + +func (ConfigQemu) mapToStruct(params map[string]interface{}) (*ConfigQemu, error) { + // vmConfig Sample: map[ cpu:host + // net0:virtio=62:DF:XX:XX:XX:XX,bridge=vmbr0 + // ide2:local:iso/xxx-xx.iso,media=cdrom memory:2048 + // smbios1:uuid=8b3bf833-aad8-4545-xxx-xxxxxxx digest:aa6ce5xxxxx1b9ce33e4aaeff564d4 sockets:1 + // name:terraform-ubuntu1404-template bootdisk:virtio0 + // virtio0:ProxmoxxxxISCSI:vm-1014-disk-2,size=4G + // description:Base image + // cores:2 ostype:l26 + + config := ConfigQemu{} + + if _, isSet := params["agent"]; isSet { + switch params["agent"].(type) { + case float64: + config.Agent = int(params["agent"].(float64)) + case string: + AgentConfList := strings.Split(params["agent"].(string), ",") + config.Agent, _ = strconv.Atoi(AgentConfList[0]) + } + } + if _, isSet := params["args"]; isSet { + config.Args = strings.TrimSpace(params["args"].(string)) + } + if _, isSet := params["balloon"]; isSet { + balloon := int(params["balloon"].(float64)) + if balloon > 0 { + config.Balloon = balloon + } + } + //boot by default from hard disk (c), CD-ROM (d), network (n). + if _, isSet := params["boot"]; isSet { + config.Boot = params["boot"].(string) + } + if _, isSet := params["bootdisk"]; isSet { + config.BootDisk = params["bootdisk"].(string) + } + if _, isSet := params["bios"]; isSet { + config.Bios = params["bios"].(string) + } + if _, isSet := params["cicustom"]; isSet { + config.CIcustom = params["cicustom"].(string) + } + if _, isSet := params["cipassword"]; isSet { + config.CIpassword = params["cipassword"].(string) + } + if _, isSet := params["ciuser"]; isSet { + config.CIuser = params["ciuser"].(string) + } + if _, isSet := params["description"]; isSet { + config.Description = strings.TrimSpace(params["description"].(string)) + } + //Can be network,disk,cpu,memory,usb + if _, isSet := params["hotplug"]; isSet { + config.Hotplug = params["hotplug"].(string) + } + if _, isSet := params["hookscript"]; isSet { + config.Hookscript = params["hookscript"].(string) + } + if _, isSet := params["memory"]; isSet { + switch params["memory"].(type) { + case float64: + config.Memory = int(params["memory"].(float64)) + case string: + mem, err := strconv.ParseFloat(params["memory"].(string), 64) + if err != nil { + return nil, err + } + config.Memory = int(mem) + } + } + if _, isSet := params["name"]; isSet { + config.Name = params["name"].(string) + } + if _, isSet := params["nameserver"]; isSet { + config.Nameserver = params["nameserver"].(string) + } + if _, isSet := params["onboot"]; isSet { + config.Onboot = PointerBool(Itob(int(params["onboot"].(float64)))) + } + if _, isSet := params["cores"]; isSet { + config.QemuCores = int(params["cores"].(float64)) + } + if _, isSet := params["cpu"]; isSet { + config.QemuCpu = params["cpu"].(string) + } + if _, isSet := params["kvm"]; isSet { + config.QemuKVM = PointerBool(Itob(int(params["kvm"].(float64)))) + } + if _, isSet := params["numa"]; isSet { + config.QemuNuma = PointerBool(Itob(int(params["numa"].(float64)))) + } + if _, isSet := params["ostype"]; isSet { + config.QemuOs = params["ostype"].(string) + } + if _, isSet := params["sockets"]; isSet { + config.QemuSockets = int(params["sockets"].(float64)) + } + if _, isSet := params["vcpus"]; isSet { + vCpu := int(params["vcpus"].(float64)) + if vCpu > 0 { + config.QemuVcpus = vCpu + } + } + if _, isSet := params["scsihw"]; isSet { + config.Scsihw = params["scsihw"].(string) + } + if _, isSet := params["searchdomain"]; isSet { + config.Searchdomain = params["searchdomain"].(string) + } + if _, isSet := params["sshkeys"]; isSet { + config.Sshkeys, _ = url.PathUnescape(params["sshkeys"].(string)) + } + if _, isSet := params["startup"]; isSet { + config.Startup = params["startup"].(string) + } + if _, isSet := params["tablet"]; isSet { + config.Tablet = PointerBool(Itob(int(params["tablet"].(float64)))) + } + if _, isSet := params["tags"]; isSet { + config.Tags = strings.TrimSpace(params["tags"].(string)) + } + if _, isSet := params["smbios1"]; isSet { + config.Smbios1 = params["smbios1"].(string) + } + + ipconfigNames := []string{} + + for k := range params { + if ipconfigName := rxIpconfigName.FindStringSubmatch(k); len(ipconfigName) > 0 { + ipconfigNames = append(ipconfigNames, ipconfigName[0]) + } + } + + if len(ipconfigNames) > 0 { + config.Ipconfig = IpconfigMap{} + for _, ipconfigName := range ipconfigNames { + ipConfStr := params[ipconfigName] + id := rxDeviceID.FindStringSubmatch(ipconfigName) + ipconfigID, _ := strconv.Atoi(id[0]) + config.Ipconfig[ipconfigID] = ipConfStr + } + } + + linkedVmId := uint(0) + config.Disks = QemuStorages{}.mapToStruct(params, &linkedVmId) + if linkedVmId != 0 { + config.LinkedVmId = linkedVmId + } + + if config.Disks != nil && config.Disks.Ide != nil && config.Disks.Ide.Disk_2 != nil && config.Disks.Ide.Disk_2.CdRom != nil { + config.Iso = config.Disks.Ide.Disk_2.CdRom.Iso + } + + // Add unused disks + // unused0:local:100/vm-100-disk-1.qcow2 + unusedDiskNames := []string{} + for k := range params { + // look for entries from the config in the format "unusedX:" where X is an integer + if unusedDiskName := rxUnusedDiskName.FindStringSubmatch(k); len(unusedDiskName) > 0 { + unusedDiskNames = append(unusedDiskNames, unusedDiskName[0]) + } + } + // if len(unusedDiskNames) > 0 { + // log.Printf("[DEBUG] unusedDiskNames: %v", unusedDiskNames) + // } + + if len(unusedDiskNames) > 0 { + config.QemuUnusedDisks = QemuDevices{} + for _, unusedDiskName := range unusedDiskNames { + unusedDiskConfStr := params[unusedDiskName].(string) + finalDiskConfMap := QemuDevice{} + + // parse "unused0" to get the id '0' as an int + id := rxDeviceID.FindStringSubmatch(unusedDiskName) + diskID, err := strconv.Atoi(id[0]) + if err != nil { + return nil, fmt.Errorf(fmt.Sprintf("Unable to parse unused disk id from input string '%v' tried to convert '%v' to integer.", unusedDiskName, diskID)) + } + finalDiskConfMap["slot"] = diskID + + // parse the attributes from the unused disk + // extract the storage and file path from the unused disk entry + parsedUnusedDiskMap := ParsePMConf(unusedDiskConfStr, "storage+file") + storageName, fileName := ParseSubConf(parsedUnusedDiskMap["storage+file"].(string), ":") + finalDiskConfMap["storage"] = storageName + finalDiskConfMap["file"] = fileName + + config.QemuUnusedDisks[diskID] = finalDiskConfMap + config.QemuUnusedDisks[diskID] = finalDiskConfMap + config.QemuUnusedDisks[diskID] = finalDiskConfMap + } + } + //Display + + if vga, isSet := params["vga"]; isSet { + vgaList := strings.Split(vga.(string), ",") + vgaMap := QemuDevice{} + + vgaMap.readDeviceConfig(vgaList) + if len(vgaMap) > 0 { + config.QemuVga = vgaMap + } + } + + // Add networks. + nicNames := []string{} + + for k := range params { + if nicName := rxNicName.FindStringSubmatch(k); len(nicName) > 0 { + nicNames = append(nicNames, nicName[0]) + } + } + + if len(nicNames) > 0 { + config.QemuNetworks = QemuDevices{} + for _, nicName := range nicNames { + nicConfStr := params[nicName] + nicConfList := strings.Split(nicConfStr.(string), ",") + + id := rxDeviceID.FindStringSubmatch(nicName) + nicID, _ := strconv.Atoi(id[0]) + model, macaddr := ParseSubConf(nicConfList[0], "=") + + // Add model and MAC address. + nicConfMap := QemuDevice{ + "id": nicID, + "model": model, + "macaddr": macaddr, + } + + // Add rest of device config. + nicConfMap.readDeviceConfig(nicConfList[1:]) + switch nicConfMap["firewall"] { + case 1: + nicConfMap["firewall"] = true + case 0: + nicConfMap["firewall"] = false + } + switch nicConfMap["link_down"] { + case 1: + nicConfMap["link_down"] = true + case 0: + nicConfMap["link_down"] = false + } + + // And device config to networks. + if len(nicConfMap) > 0 { + config.QemuNetworks[nicID] = nicConfMap + } + } + } + + // Add serials + serialNames := []string{} + + for k := range params { + if serialName := rxSerialName.FindStringSubmatch(k); len(serialName) > 0 { + serialNames = append(serialNames, serialName[0]) + } + } + + if len(serialNames) > 0 { + config.QemuSerials = QemuDevices{} + for _, serialName := range serialNames { + id := rxDeviceID.FindStringSubmatch(serialName) + serialID, _ := strconv.Atoi(id[0]) + + serialConfMap := QemuDevice{ + "id": serialID, + "type": params[serialName], + } + + // And device config to serials map. + if len(serialConfMap) > 0 { + config.QemuSerials[serialID] = serialConfMap + } + } + } + + // Add usbs + usbNames := []string{} + + for k := range params { + if usbName := rxUsbName.FindStringSubmatch(k); len(usbName) > 0 { + usbNames = append(usbNames, usbName[0]) + } + } + + if len(usbNames) > 0 { + config.QemuUsbs = QemuDevices{} + for _, usbName := range usbNames { + usbConfStr := params[usbName] + usbConfList := strings.Split(usbConfStr.(string), ",") + id := rxDeviceID.FindStringSubmatch(usbName) + usbID, _ := strconv.Atoi(id[0]) + _, host := ParseSubConf(usbConfList[0], "=") + + usbConfMap := QemuDevice{ + "id": usbID, + "host": host, + } + + usbConfMap.readDeviceConfig(usbConfList[1:]) + if usbConfMap["usb3"] == 1 { + usbConfMap["usb3"] = true + } + + // And device config to usbs map. + if len(usbConfMap) > 0 { + config.QemuUsbs[usbID] = usbConfMap + } + } + } + + // hostpci + hostPCInames := []string{} - // Create disks config. - err = config.CreateQemuDisksParams(vmr.vmId, params, false) - if err != nil { - log.Printf("[ERROR] %q", err) + for k := range params { + if hostPCIname := rxPCIName.FindStringSubmatch(k); len(hostPCIname) > 0 { + hostPCInames = append(hostPCInames, hostPCIname[0]) + } } - // Create EFI disk - err = config.CreateQemuEfiParams(params) - if err != nil { - log.Printf("[ERROR] %q", err) + if len(hostPCInames) > 0 { + config.QemuPCIDevices = QemuDevices{} + for _, hostPCIname := range hostPCInames { + hostPCIConfStr := params[hostPCIname] + hostPCIConfList := strings.Split(hostPCIConfStr.(string), ",") + id := rxPCIName.FindStringSubmatch(hostPCIname) + hostPCIID, _ := strconv.Atoi(id[0]) + hostPCIConfMap := QemuDevice{ + "id": hostPCIID, + } + hostPCIConfMap.readDeviceConfig(hostPCIConfList) + // And device config to usbs map. + if len(hostPCIConfMap) > 0 { + config.QemuPCIDevices[hostPCIID] = hostPCIConfMap + } + } } - // Create vga config. - vgaParam := QemuDeviceParam{} - vgaParam = vgaParam.createDeviceParam(config.QemuVga, nil) - if len(vgaParam) > 0 { - params["vga"] = strings.Join(vgaParam, ",") + // efidisk + if efidisk, isSet := params["efidisk0"].(string); isSet { + efiDiskConfMap := ParsePMConf(efidisk, "volume") + storageName, fileName := ParseSubConf(efiDiskConfMap["volume"].(string), ":") + efiDiskConfMap["storage"] = storageName + efiDiskConfMap["file"] = fileName + config.EFIDisk = efiDiskConfMap } - // Create networks config. - err = config.CreateQemuNetworksParams(vmr.vmId, params) - if err != nil { - log.Printf("[ERROR] %q", err) - } + return &config, nil +} - // Create ipconfig. - err = config.CreateIpconfigParams(vmr.vmId, params) +func (newConfig ConfigQemu) Update(rebootIfNeeded bool, vmr *VmRef, client *Client) (rebootRequired bool, err error) { + currentConfig, err := NewConfigQemuFromApi(vmr, client) if err != nil { - log.Printf("[ERROR] %q", err) + return } + return newConfig.setAdvanced(currentConfig, rebootIfNeeded, vmr, client) +} - // Create serial interfaces - err = config.CreateQemuSerialsParams(vmr.vmId, params) - if err != nil { - log.Printf("[ERROR] %q", err) +func (config *ConfigQemu) setVmr(vmr *VmRef) (err error) { + if config == nil { + return errors.New("config may not be nil") } + if err = vmr.nilCheck(); err != nil { + return + } + vmr.SetVmType("qemu") + config.VmID = vmr.vmId + return +} - err = config.CreateQemuPCIsParams(vmr.vmId, params) +func (newConfig ConfigQemu) setAdvanced(currentConfig *ConfigQemu, rebootIfNeeded bool, vmr *VmRef, client *Client) (rebootRequired bool, err error) { + err = newConfig.setVmr(vmr) if err != nil { - log.Printf("[ERROR] %q", err) + return } - - // Create usb interfaces - err = config.CreateQemuUsbsParams(vmr.vmId, params) + err = newConfig.Validate() if err != nil { - log.Printf("[ERROR] %q", err) + return + } + + var params map[string]interface{} + var exitStatus string + + if currentConfig != nil { + // Update + if newConfig.Disks != nil && currentConfig.Disks != nil { + markedDisks := newConfig.Disks.markDiskChanges(*currentConfig.Disks) + // move disk to different storage or change disk format + for _, e := range markedDisks.Move { + _, err = e.move(true, vmr, client) + if err != nil { + return + } + } + // increase Disks in size + for _, e := range markedDisks.Resize { + _, err = e.resize(vmr, client) + if err != nil { + return + } + } + // Moving disks changes the disk id. we need to get the config again if any disk was moved + if len(markedDisks.Move) != 0 { + currentConfig, err = NewConfigQemuFromApi(vmr, client) + if err != nil { + return + } + } + } + + // Migrate VM + if newConfig.Node != currentConfig.Node { + vmr.SetNode(currentConfig.Node) + _, err = client.MigrateNode(vmr, newConfig.Node, true) + if err != nil { + return + } + // Set node to the node the VM was migrated to + vmr.SetNode(newConfig.Node) + } + + rebootRequired, params, err = newConfig.mapToApiValues(*currentConfig) + if err != nil { + return + } + exitStatus, err = client.PutWithTask(params, "/nodes/"+vmr.node+"/"+vmr.vmType+"/"+strconv.Itoa(vmr.vmId)+"/config") + if err != nil { + return false, fmt.Errorf("error updating VM: %v, error status: %s (params: %v)", err, exitStatus, params) + } + + if !rebootRequired { + rebootRequired, err = GuestHasPendingChanges(vmr, client) + if err != nil { + return + } + } + + if rebootRequired && rebootIfNeeded { + if err = GuestReboot(vmr, client); err != nil { + return + } + rebootRequired = false + } + } else { + // Create + + _, params, err = newConfig.mapToApiValues(ConfigQemu{}) + if err != nil { + return + } + exitStatus, err = client.CreateQemuVm(vmr.node, params) + if err != nil { + return false, fmt.Errorf("error creating VM: %v, error status: %s (params: %v)", err, exitStatus, params) + } } - exitStatus, err := client.CreateQemuVm(vmr.node, params) + _, err = client.UpdateVMHA(vmr, newConfig.HaState, newConfig.HaGroup) if err != nil { - return fmt.Errorf("error creating VM: %v, error status: %s (params: %v)", err, exitStatus, params) + return } - _, err = client.UpdateVMHA(vmr, config.HaState, config.HaGroup) - if err != nil { - log.Printf("[ERROR] %q", err) + _, err = client.UpdateVMPool(vmr, newConfig.Pool) + return +} + +func (config ConfigQemu) Validate() (err error) { + // TODO test all other use cases + // TODO has no context about changes caused by updating the vm + if config.Disks != nil { + err = config.Disks.Validate() + if err != nil { + return + } } return @@ -253,9 +969,9 @@ storage:xxx func (config ConfigQemu) CloneVm(sourceVmr *VmRef, vmr *VmRef, client *Client) (err error) { vmr.SetVmType("qemu") var storage string - fullclone := "1" + fullClone := "1" if config.FullClone != nil { - fullclone = strconv.Itoa(*config.FullClone) + fullClone = strconv.Itoa(*config.FullClone) } if disk0Storage, ok := config.QemuDisks[0]["storage"].(string); ok && len(disk0Storage) > 0 { storage = disk0Storage @@ -264,13 +980,13 @@ func (config ConfigQemu) CloneVm(sourceVmr *VmRef, vmr *VmRef, client *Client) ( "newid": vmr.vmId, "target": vmr.node, "name": config.Name, - "full": fullclone, + "full": fullClone, } if vmr.pool != "" { params["pool"] = vmr.pool } - if fullclone == "1" && storage != "" { + if fullClone == "1" && storage != "" { params["storage"] = storage } @@ -278,6 +994,7 @@ func (config ConfigQemu) CloneVm(sourceVmr *VmRef, vmr *VmRef, client *Client) ( return err } +// DEPRECATED use ConfigQemu.Update instead func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) { configParams := map[string]interface{}{} @@ -386,11 +1103,7 @@ func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) { configParamsDisk := map[string]interface{}{ "vmid": vmr.vmId, } - // TODO keep going if error= - err = config.CreateQemuDisksParams(vmr.vmId, configParamsDisk, false) - if err != nil { - log.Printf("[ERROR] %q", err) - } + config.CreateQemuDisksParams(configParamsDisk, false) // TODO keep going if error= _, err = client.createVMDisks(vmr.node, configParamsDisk) if err != nil { @@ -405,10 +1118,8 @@ func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) { } // Create networks config. - err = config.CreateQemuNetworksParams(vmr.vmId, configParams) - if err != nil { - log.Printf("[ERROR] %q", err) - } + config.VmID = vmr.vmId + config.CreateQemuNetworksParams(configParams) // Create vga config. vgaParam := QemuDeviceParam{} @@ -417,21 +1128,13 @@ func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) { configParams["vga"] = strings.Join(vgaParam, ",") } // Create serial interfaces - err = config.CreateQemuSerialsParams(vmr.vmId, configParams) - if err != nil { - log.Printf("[ERROR] %q", err) - } + config.CreateQemuSerialsParams(configParams) // Create usb interfaces - err = config.CreateQemuUsbsParams(vmr.vmId, configParams) - if err != nil { - log.Printf("[ERROR] %q", err) - } + config.CreateQemuUsbsParams(configParams) + + config.CreateQemuPCIsParams(configParams) - err = config.CreateQemuPCIsParams(vmr.vmId, configParams) - if err != nil { - log.Printf("[ERROR] %q", err) - } // cloud-init options if config.CIuser != "" { configParams["ciuser"] = config.CIuser @@ -448,10 +1151,13 @@ func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) { if config.Nameserver != "" { configParams["nameserver"] = config.Nameserver } + if config.Smbios1 != "" { + configParams["smbios1"] = config.Smbios1 + } if config.Sshkeys != "" { configParams["sshkeys"] = sshKeyUrlEncode(config.Sshkeys) } - err = config.CreateIpconfigParams(vmr.vmId, configParams) + err = config.CreateIpconfigParams(configParams) if err != nil { log.Printf("[ERROR] %q", err) } @@ -486,16 +1192,12 @@ func NewConfigQemuFromJson(input []byte) (config *ConfigQemu, err error) { } var ( - rxIso = regexp.MustCompile(`(.*?),media`) rxDeviceID = regexp.MustCompile(`\d+`) - rxDiskName = regexp.MustCompile(`(virtio|scsi|ide|sata)\d+`) - rxDiskType = regexp.MustCompile(`\D+`) rxUnusedDiskName = regexp.MustCompile(`^(unused)\d+`) rxNicName = regexp.MustCompile(`net\d+`) rxMpName = regexp.MustCompile(`mp\d+`) rxSerialName = regexp.MustCompile(`serial\d+`) rxUsbName = regexp.MustCompile(`usb\d+`) - rxDiskPath = regexp.MustCompile(`^\/dev\/.*`) rxPCIName = regexp.MustCompile(`hostpci\d+`) rxIpconfigName = regexp.MustCompile(`ipconfig\d+`) ) @@ -732,268 +1434,31 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e if diskName := rxDiskName.FindStringSubmatch(k); len(diskName) > 0 { diskNames = append(diskNames, diskName[0]) } + config, err = ConfigQemu{}.mapToStruct(vmConfig) + if err != nil { + return } - for _, diskName := range diskNames { - var isDiskByID bool = false - diskConfStr := vmConfig[diskName].(string) - - id := rxDeviceID.FindStringSubmatch(diskName) - diskID, _ := strconv.Atoi(id[0]) - diskType := rxDiskType.FindStringSubmatch(diskName)[0] - - diskConfMap := ParsePMConf(diskConfStr, "volume") - diskByID := rxDiskPath.FindStringSubmatch(diskConfMap["volume"].(string)) - if len(diskByID) > 0 && diskByID[0] != "" { - isDiskByID = true - } - - if diskConfMap["volume"].(string) == "none" { - continue - } - - diskConfMap["slot"] = diskID - diskConfMap["type"] = diskType - - storageName, fileName := ParseSubConf(diskConfMap["volume"].(string), ":") - diskConfMap["storage"] = storageName - diskConfMap["file"] = fileName - - filePath := diskConfMap["volume"] - - // Get disk format - storageContent, err := client.GetStorageContent(vmr, storageName) + if config.EFIDisk != nil { + storageContent, err := client.GetStorageContent(vmr, config.EFIDisk["storage"].(string)) if err != nil { log.Fatal(err) return nil, err } - var storageFormat string + contents := storageContent["data"].([]interface{}) for content := range contents { storageContentMap := contents[content].(map[string]interface{}) - if storageContentMap["volid"] == filePath { - storageFormat = storageContentMap["format"].(string) + if storageContentMap["volid"] == config.EFIDisk["volume"].(string) { + config.EFIDisk["format"] = storageContentMap["format"].(string) break } } - diskConfMap["format"] = storageFormat - - // Get storage type for disk - var storageStatus map[string]interface{} - if !isDiskByID { - storageStatus, err = client.GetStorageStatus(vmr, storageName) - if err != nil { - log.Fatal(err) - return nil, err - } - storageType := storageStatus["type"] - - diskConfMap["storage_type"] = storageType - } - // cloud-init disks not always have the size sent by the API, which results in a crash - if diskConfMap["size"] == nil && strings.Contains(fileName.(string), "cloudinit") { - diskConfMap["size"] = "4M" // default cloud-init disk size - } - - var sizeInTerabytes = regexp.MustCompile(`[0-9]+T`) - // Convert to gigabytes if disk size was received in terabytes - matched := sizeInTerabytes.MatchString(diskConfMap["size"].(string)) - if matched { - diskConfMap["size"] = fmt.Sprintf("%.0fG", DiskSizeGB(diskConfMap["size"])) - } - - // And device config to disks map. - if len(diskConfMap) > 0 { - config.QemuDisks[diskID] = diskConfMap - } - } - - // Add unused disks - // unused0:local:100/vm-100-disk-1.qcow2 - unusedDiskNames := []string{} - for k := range vmConfig { - // look for entries from the config in the format "unusedX:" where X is an integer - if unusedDiskName := rxUnusedDiskName.FindStringSubmatch(k); len(unusedDiskName) > 0 { - unusedDiskNames = append(unusedDiskNames, unusedDiskName[0]) - } - } - // if len(unusedDiskNames) > 0 { - // log.Printf("[DEBUG] unusedDiskNames: %v", unusedDiskNames) - // } - - for _, unusedDiskName := range unusedDiskNames { - unusedDiskConfStr := vmConfig[unusedDiskName].(string) - finalDiskConfMap := QemuDevice{} - - // parse "unused0" to get the id '0' as an int - id := rxDeviceID.FindStringSubmatch(unusedDiskName) - diskID, err := strconv.Atoi(id[0]) - if err != nil { - return nil, fmt.Errorf(fmt.Sprintf("Unable to parse unused disk id from input string '%v' tried to convert '%v' to integer.", unusedDiskName, diskID)) - } - finalDiskConfMap["slot"] = diskID - - // parse the attributes from the unused disk - // extract the storage and file path from the unused disk entry - parsedUnusedDiskMap := ParsePMConf(unusedDiskConfStr, "storage+file") - storageName, fileName := ParseSubConf(parsedUnusedDiskMap["storage+file"].(string), ":") - finalDiskConfMap["storage"] = storageName - finalDiskConfMap["file"] = fileName - - config.QemuUnusedDisks[diskID] = finalDiskConfMap - } - - //Display - if vga, isSet := vmConfig["vga"]; isSet { - vgaList := strings.Split(vga.(string), ",") - vgaMap := QemuDevice{} - - // TODO: keep going if error? - err = vgaMap.readDeviceConfig(vgaList) - if err != nil { - log.Printf("[ERROR] %q", err) - } - if len(vgaMap) > 0 { - config.QemuVga = vgaMap - } - } - - // Add networks. - nicNames := []string{} - - for k := range vmConfig { - if nicName := rxNicName.FindStringSubmatch(k); len(nicName) > 0 { - nicNames = append(nicNames, nicName[0]) - } - } - - for _, nicName := range nicNames { - nicConfStr := vmConfig[nicName] - nicConfList := strings.Split(nicConfStr.(string), ",") - - id := rxDeviceID.FindStringSubmatch(nicName) - nicID, _ := strconv.Atoi(id[0]) - model, macaddr := ParseSubConf(nicConfList[0], "=") - - // Add model and MAC address. - nicConfMap := QemuDevice{ - "id": nicID, - "model": model, - "macaddr": macaddr, - } - - // Add rest of device config. - err = nicConfMap.readDeviceConfig(nicConfList[1:]) - if err != nil { - log.Printf("[ERROR] %q", err) - } - switch nicConfMap["firewall"] { - case 1: - nicConfMap["firewall"] = true - case 0: - nicConfMap["firewall"] = false - } - switch nicConfMap["link_down"] { - case 1: - nicConfMap["link_down"] = true - case 0: - nicConfMap["link_down"] = false - } - - // And device config to networks. - if len(nicConfMap) > 0 { - config.QemuNetworks[nicID] = nicConfMap - } - } - - // Add serials - serialNames := []string{} - - for k := range vmConfig { - if serialName := rxSerialName.FindStringSubmatch(k); len(serialName) > 0 { - serialNames = append(serialNames, serialName[0]) - } - } - - for _, serialName := range serialNames { - id := rxDeviceID.FindStringSubmatch(serialName) - serialID, _ := strconv.Atoi(id[0]) - - serialConfMap := QemuDevice{ - "id": serialID, - "type": vmConfig[serialName], - } - - // And device config to serials map. - if len(serialConfMap) > 0 { - config.QemuSerials[serialID] = serialConfMap - } - } - - // Add usbs - usbNames := []string{} - - for k := range vmConfig { - if usbName := rxUsbName.FindStringSubmatch(k); len(usbName) > 0 { - usbNames = append(usbNames, usbName[0]) - } - } - - for _, usbName := range usbNames { - usbConfStr := vmConfig[usbName] - usbConfList := strings.Split(usbConfStr.(string), ",") - id := rxDeviceID.FindStringSubmatch(usbName) - usbID, _ := strconv.Atoi(id[0]) - _, host := ParseSubConf(usbConfList[0], "=") - - usbConfMap := QemuDevice{ - "id": usbID, - "host": host, - } - - err = usbConfMap.readDeviceConfig(usbConfList[1:]) - if err != nil { - log.Printf("[ERROR] %q", err) - } - if usbConfMap["usb3"] == 1 { - usbConfMap["usb3"] = true - } - - // And device config to usbs map. - if len(usbConfMap) > 0 { - config.QemuUsbs[usbID] = usbConfMap - } - } - - // hostpci - hostPCInames := []string{} - - for k := range vmConfig { - if hostPCIname := rxPCIName.FindStringSubmatch(k); len(hostPCIname) > 0 { - hostPCInames = append(hostPCInames, hostPCIname[0]) - } } - for _, hostPCIname := range hostPCInames { - hostPCIConfStr := vmConfig[hostPCIname] - hostPCIConfList := strings.Split(hostPCIConfStr.(string), ",") - id := rxPCIName.FindStringSubmatch(hostPCIname) - hostPCIID, _ := strconv.Atoi(id[0]) - hostPCIConfMap := QemuDevice{ - "id": hostPCIID, - } - err = hostPCIConfMap.readDeviceConfig(hostPCIConfList) - if err != nil { - log.Printf("[ERROR] %q", err) - } - - // And device config to usbs map. - if len(hostPCIConfMap) > 0 { - config.QemuPCIDevices[hostPCIID] = hostPCIConfMap - } - } + config.defaults() - // hastate is return by the api for a vm resource type but not the hagroup + // HAstate is return by the api for a vm resource type but not the HAgroup err = client.ReadVMHA(vmr) if err == nil { config.HaState = vmr.HaState() @@ -1002,7 +1467,6 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e //log.Printf("[DEBUG] VM %d(%s) has no HA config", vmr.vmId, vmConfig["hostname"]) return config, nil } - return } @@ -1074,8 +1538,7 @@ func RemoveSshForwardUsernet(vmr *VmRef, client *Client) (err error) { } func MaxVmId(client *Client) (max int, err error) { - resp, err := client.GetVmList() - vms := resp["data"].([]interface{}) + vms, err := client.GetResourceList(resourceListGuest) max = 100 for vmii := range vms { vm := vms[vmii].(map[string]interface{}) @@ -1160,13 +1623,16 @@ func formatDeviceParam(device QemuDevice) string { return strings.Join(deviceConfParams, ",") } -// Given a QemuDevice (represesting a disk), return a param string to give to ProxMox +// Given a QemuDevice (representing a disk), return a param string to give to ProxMox func FormatDiskParam(disk QemuDevice) string { diskConfParam := QemuDeviceParam{} if volume, ok := disk["volume"]; ok && volume != "" { diskConfParam = append(diskConfParam, volume.(string)) - diskConfParam = append(diskConfParam, fmt.Sprintf("size=%v", disk["size"])) + + if size, ok := disk["size"]; ok && size != "" { + diskConfParam = append(diskConfParam, fmt.Sprintf("size=%v", disk["size"])) + } } else { volumeInit := fmt.Sprintf("%v:%v", disk["storage"], DiskSizeGB(disk["size"])) diskConfParam = append(diskConfParam, volumeInit) @@ -1208,7 +1674,7 @@ func FormatDiskParam(disk QemuDevice) string { return strings.Join(diskConfParam, ",") } -// Given a QemuDevice (represesting a usb), return a param string to give to ProxMox +// Given a QemuDevice (representing a usb), return a param string to give to ProxMox func FormatUsbParam(usb QemuDevice) string { usbConfParam := QemuDeviceParam{} @@ -1218,8 +1684,7 @@ func FormatUsbParam(usb QemuDevice) string { } // Create parameters for each Nic device. -func (c ConfigQemu) CreateQemuNetworksParams(vmID int, params map[string]interface{}) error { - +func (c ConfigQemu) CreateQemuNetworksParams(params map[string]interface{}) { // For new style with multi net device. for nicID, nicConfMap := range c.QemuNetworks { @@ -1245,7 +1710,7 @@ func (c ConfigQemu) CreateQemuNetworksParams(vmID int, params map[string]interfa // Generate deterministic Mac based on VmID and NicID // Assume that rare VM has more than 32 nics macaddr := make(net.HardwareAddr, 6) - pairing := vmID<<5 | nicID + pairing := c.VmID<<5 | nicID // Linux MAC vendor - 00:18:59 macaddr[0] = 0x00 macaddr[1] = 0x18 @@ -1262,7 +1727,7 @@ func (c ConfigQemu) CreateQemuNetworksParams(vmID int, params map[string]interfa macAddr = nicConfMap["macaddr"].(string) } - // use model=mac format for older proxmox compatability as the parameters which will be sent to Proxmox API. + // use model=mac format for older proxmox compatibility as the parameters which will be sent to Proxmox API. nicConfParam = append(nicConfParam, fmt.Sprintf("%v=%v", nicConfMap["model"], macAddr)) // Set bridge if not nat. @@ -1280,12 +1745,10 @@ func (c ConfigQemu) CreateQemuNetworksParams(vmID int, params map[string]interfa // Add nic to Qemu prams. params[qemuNicName] = strings.Join(nicConfParam, ",") } - - return nil } // Create parameters for each Cloud-Init ipconfig entry. -func (c ConfigQemu) CreateIpconfigParams(vmID int, params map[string]interface{}) error { +func (c ConfigQemu) CreateIpconfigParams(params map[string]interface{}) error { for ID, config := range c.Ipconfig { if ID > 15 { @@ -1301,10 +1764,27 @@ func (c ConfigQemu) CreateIpconfigParams(vmID int, params map[string]interface{} return nil } +// Create RNG parameter. +func (c ConfigQemu) CreateQemuRngParams(params map[string]interface{}) { + rngParam := QemuDeviceParam{} + rngParam = rngParam.createDeviceParam(c.RNGDrive, nil) + + if len(rngParam) > 0 { + rng_info := []string{} + rng := "" + for _, param := range rngParam { + key := strings.Split(param, "=") + rng_info = append(rng_info, fmt.Sprintf("%s=%s", key[0], key[1])) + } + if len(rng_info) > 0 { + rng = strings.Join(rng_info, ",") + params["rng0"] = rng + } + } +} + // Create efi parameter. -func (c ConfigQemu) CreateQemuEfiParams( - params map[string]interface{}, -) error { +func (c ConfigQemu) CreateQemuEfiParams(params map[string]interface{}) { efiParam := QemuDeviceParam{} efiParam = efiParam.createDeviceParam(c.EFIDisk, nil) @@ -1325,16 +1805,10 @@ func (c ConfigQemu) CreateQemuEfiParams( } params["efidisk0"] = storage } - return nil } // Create parameters for each disk. -func (c ConfigQemu) CreateQemuDisksParams( - vmID int, - params map[string]interface{}, - cloned bool, -) error { - +func (c ConfigQemu) CreateQemuDisksParams(params map[string]interface{}, cloned bool) { // For new style with multi disk device. for diskID, diskConfMap := range c.QemuDisks { // skip the first disk for clones (may not always be right, but a template probably has at least 1 disk) @@ -1349,16 +1823,10 @@ func (c ConfigQemu) CreateQemuDisksParams( // Add back to Qemu prams. params[qemuDiskName] = FormatDiskParam(diskConfMap) } - - return nil } // Create parameters for each PCI Device -func (c ConfigQemu) CreateQemuPCIsParams( - vmID int, - params map[string]interface{}, -) error { - +func (c ConfigQemu) CreateQemuPCIsParams(params map[string]interface{}) { // For new style with multi pci device. for pciConfID, pciConfMap := range c.QemuPCIDevices { qemuPCIName := "hostpci" + strconv.Itoa(pciConfID) @@ -1373,15 +1841,10 @@ func (c ConfigQemu) CreateQemuPCIsParams( // Add back to Qemu prams. params[qemuPCIName] = strings.TrimSuffix(pcistring.String(), ",") } - return nil } // Create parameters for serial interface -func (c ConfigQemu) CreateQemuSerialsParams( - vmID int, - params map[string]interface{}, -) error { - +func (c ConfigQemu) CreateQemuSerialsParams(params map[string]interface{}) { // For new style with multi disk device. for serialID, serialConfMap := range c.QemuSerials { // Device name. @@ -1391,23 +1854,16 @@ func (c ConfigQemu) CreateQemuSerialsParams( // Add back to Qemu prams. params[qemuSerialName] = deviceType } - - return nil } // Create parameters for usb interface -func (c ConfigQemu) CreateQemuUsbsParams( - vmID int, - params map[string]interface{}, -) error { +func (c ConfigQemu) CreateQemuUsbsParams(params map[string]interface{}) { for usbID, usbConfMap := range c.QemuUsbs { qemuUsbName := "usb" + strconv.Itoa(usbID) // Add back to Qemu prams. params[qemuUsbName] = FormatUsbParam(usbConfMap) } - - return nil } // Create parameters for serial interface @@ -1438,6 +1894,8 @@ func (p QemuDeviceParam) createDeviceParam( confValue = sValue } else if iValue, ok := value.(int); ok && iValue > 0 { confValue = iValue + } else if iValue, ok := value.(float64); ok && iValue > 0 { + confValue = iValue } if confValue != nil { deviceConf := fmt.Sprintf("%v=%v", key, confValue) @@ -1450,13 +1908,12 @@ func (p QemuDeviceParam) createDeviceParam( } // readDeviceConfig - get standard sub-conf strings where `key=value` and update conf map. -func (confMap QemuDevice) readDeviceConfig(confList []string) error { +func (confMap QemuDevice) readDeviceConfig(confList []string) { // Add device config. for _, conf := range confList { key, value := ParseSubConf(conf, "=") confMap[key] = value } - return nil } func (c ConfigQemu) String() string { diff --git a/proxmox/config_qemu_disk.go b/proxmox/config_qemu_disk.go new file mode 100644 index 00000000..8539e1c5 --- /dev/null +++ b/proxmox/config_qemu_disk.go @@ -0,0 +1,1052 @@ +package proxmox + +import ( + "errors" + "fmt" + "math" + "regexp" + "strconv" + "strings" +) + +type IsoFile struct { + File string `json:"file"` + Storage string `json:"storage"` + // Size can only be retrieved, setting it has no effect + Size string `json:"size"` +} + +const ( + Error_IsoFile_File string = "file may not be empty" + Error_IsoFile_Storage string = "storage may not be empty" +) + +func (iso IsoFile) Validate() error { + if iso.File == "" { + return errors.New(Error_IsoFile_File) + } + if iso.Storage == "" { + return errors.New(Error_IsoFile_Storage) + } + return nil +} + +type QemuCdRom struct { + Iso *IsoFile `json:"iso,omitempty"` + // Passthrough and File are mutually exclusive + Passthrough bool `json:"passthrough,omitempty"` +} + +const ( + Error_QemuCdRom_MutuallyExclusive string = "iso and passthrough are mutually exclusive" +) + +func (cdRom QemuCdRom) mapToApiValues() string { + if cdRom.Passthrough { + return "cdrom,media=cdrom" + } + if cdRom.Iso != nil { + return cdRom.Iso.Storage + ":iso/" + cdRom.Iso.File + ",media=cdrom" + } + return "none,media=cdrom" +} + +func (QemuCdRom) mapToStruct(settings qemuCdRom) *QemuCdRom { + if settings.File != "" { + return &QemuCdRom{ + Iso: &IsoFile{ + Storage: settings.Storage, + File: settings.File, + Size: settings.Size, + }, + } + } + return &QemuCdRom{Passthrough: settings.Passthrough} +} + +func (cdRom QemuCdRom) Validate() error { + if cdRom.Iso != nil { + err := cdRom.Iso.Validate() + if err != nil { + return err + } + if cdRom.Passthrough { + return errors.New(Error_QemuCdRom_MutuallyExclusive) + } + } + return nil +} + +type qemuCdRom struct { + CdRom bool + // "local:iso/debian-11.0.0-amd64-netinst.iso,media=cdrom,size=377M" + Passthrough bool + Storage string + Format QemuDiskFormat // Only set for Cloud-init drives + File string + Size string +} + +func (qemuCdRom) mapToStruct(diskData string, settings map[string]interface{}) *qemuCdRom { + var isCdRom bool + if setting, isSet := settings["media"]; isSet { + isCdRom = setting.(string) == "cdrom" + } + if !isCdRom { + return nil + } + if _, isSet := settings["none"]; isSet { + return &qemuCdRom{CdRom: true} + } + if _, isSet := settings["cdrom"]; isSet { + return &qemuCdRom{CdRom: true, Passthrough: true} + } + tmpStorage := strings.Split(diskData, ":") + if len(tmpStorage) > 1 { + tmpFile := strings.Split(diskData, "/") + if len(tmpFile) == 2 { + tmpFileType := strings.Split(tmpFile[1], ".") + if len(tmpFileType) > 1 { + fileType := QemuDiskFormat(tmpFileType[len(tmpFileType)-1]) + if fileType == "iso" { + if setting, isSet := settings["size"]; isSet { + return &qemuCdRom{ + CdRom: true, + Storage: tmpStorage[0], + File: tmpFile[1], + Size: setting.(string), + } + } + } else { + return &qemuCdRom{ + Storage: tmpStorage[0], + File: tmpFile[1], + Format: fileType, + } + } + } + } + } + return nil +} + +type QemuCloudInitDisk struct { + Format QemuDiskFormat `json:"format,omitempty"` + Storage string `json:"storage,omitempty"` +} + +const ( + Error_QemuCloudInitDisk_Storage string = "storage should not be empty" + Error_QemuCloudInitDisk_OnlyOne string = "only one cloud init disk may exist" +) + +func (QemuCloudInitDisk) checkDuplicates(numberOFCloudInitDrives uint8) error { + if numberOFCloudInitDrives > 1 { + return errors.New(Error_QemuCloudInitDisk_OnlyOne) + } + return nil +} + +func (cloudInit QemuCloudInitDisk) mapToApiValues() string { + return cloudInit.Storage + ":cloudinit,format=" + string(cloudInit.Format) +} + +func (QemuCloudInitDisk) mapToStruct(settings qemuCdRom) *QemuCloudInitDisk { + return &QemuCloudInitDisk{ + Storage: settings.Storage, + Format: settings.Format, + } +} + +func (cloudInit QemuCloudInitDisk) Validate() error { + if err := cloudInit.Format.Validate(); err != nil { + return err + } + if cloudInit.Storage == "" { + return errors.New(Error_QemuCloudInitDisk_Storage) + } + return nil +} + +type qemuDisk struct { + AsyncIO QemuDiskAsyncIO + Backup bool + Bandwidth QemuDiskBandwidth + Cache QemuDiskCache + Discard bool + Disk bool // true = disk, false = passthrough + EmulateSSD bool // Only set for ide,sata,scsi + // TODO custom type + File string // Only set for Passthrough. + Format QemuDiskFormat // Only set for Disk + Id uint // Only set for Disk + IOThread bool // Only set for scsi,virtio + LinkedDiskId *uint // Only set for Disk + ReadOnly bool // Only set for scsi,virtio + Replicate bool + Serial QemuDiskSerial + Size uint + // TODO custom type + Storage string // Only set for Disk + Type qemuDiskType + WorldWideName QemuWorldWideName +} + +const ( + Error_QemuDisk_File string = "file may not be empty" + Error_QemuDisk_MutuallyExclusive string = "settings cdrom,cloudinit,disk,passthrough are mutually exclusive" + Error_QemuDisk_Size string = "size must be greater then 0" + Error_QemuDisk_Storage string = "storage may not be empty" +) + +// Maps all the disk related settings to api values proxmox understands. +func (disk qemuDisk) mapToApiValues(vmID, LinkedVmId uint, currentStorage string, currentFormat QemuDiskFormat, create bool) (settings string) { + if disk.Storage != "" { + if create { + settings = disk.Storage + ":" + strconv.Itoa(int(disk.Size)) + } else { + tmpId := strconv.Itoa(int(vmID)) + settings = tmpId + "/vm-" + tmpId + "-disk-" + strconv.Itoa(int(disk.Id)) + "." + string(disk.Format) + // storage:100/vm-100-disk-0.raw + if disk.LinkedDiskId != nil && disk.Storage == currentStorage && disk.Format == currentFormat { + // storage:110/base-110-disk-1.raw/100/vm-100-disk-0.raw + tmpId = strconv.Itoa(int(LinkedVmId)) + settings = tmpId + "/base-" + tmpId + "-disk-" + strconv.Itoa(int(*disk.LinkedDiskId)) + "." + string(disk.Format) + "/" + settings + } + settings = disk.Storage + ":" + settings + } + } + + if disk.File != "" { + settings = disk.File + } + // Set File + + if disk.AsyncIO != "" { + settings = settings + ",aio=" + string(disk.AsyncIO) + } + if !disk.Backup { + settings = settings + ",backup=0" + } + if disk.Cache != "" { + settings = settings + ",cache=" + string(disk.Cache) + } + if disk.Discard { + settings = settings + ",discard=on" + } + if disk.Format != "" && create { + settings = settings + ",format=" + string(disk.Format) + } + // media + + if disk.Bandwidth.Iops.ReadLimit.Concurrent != 0 { + settings = settings + ",iops_rd=" + strconv.Itoa(int(disk.Bandwidth.Iops.ReadLimit.Concurrent)) + } + if disk.Bandwidth.Iops.ReadLimit.Burst != 0 { + settings = settings + ",iops_rd_max=" + strconv.Itoa(int(disk.Bandwidth.Iops.ReadLimit.Burst)) + } + if disk.Bandwidth.Iops.ReadLimit.BurstDuration != 0 { + settings = settings + ",iops_rd_max_length=" + strconv.Itoa(int(disk.Bandwidth.Iops.ReadLimit.BurstDuration)) + } + + if disk.Bandwidth.Iops.WriteLimit.Concurrent != 0 { + settings = settings + ",iops_wr=" + strconv.Itoa(int(disk.Bandwidth.Iops.WriteLimit.Concurrent)) + } + if disk.Bandwidth.Iops.WriteLimit.Burst != 0 { + settings = settings + ",iops_wr_max=" + strconv.Itoa(int(disk.Bandwidth.Iops.WriteLimit.Burst)) + } + if disk.Bandwidth.Iops.WriteLimit.BurstDuration != 0 { + settings = settings + ",iops_wr_max_length=" + strconv.Itoa(int(disk.Bandwidth.Iops.WriteLimit.BurstDuration)) + } + + if (disk.Type == scsi || disk.Type == virtIO) && disk.IOThread { + settings = settings + ",iothread=1" + } + + if disk.Bandwidth.MBps.ReadLimit.Concurrent != 0 { + settings = settings + fmt.Sprintf(",mbps_rd=%.2f", disk.Bandwidth.MBps.ReadLimit.Concurrent) + } + if disk.Bandwidth.MBps.ReadLimit.Burst != 0 { + settings = settings + fmt.Sprintf(",mbps_rd_max=%.2f", disk.Bandwidth.MBps.ReadLimit.Burst) + } + + if disk.Bandwidth.MBps.WriteLimit.Concurrent != 0 { + settings = settings + fmt.Sprintf(",mbps_wr=%.2f", disk.Bandwidth.MBps.WriteLimit.Concurrent) + } + if disk.Bandwidth.MBps.WriteLimit.Burst != 0 { + settings = settings + fmt.Sprintf(",mbps_wr_max=%.2f", disk.Bandwidth.MBps.WriteLimit.Burst) + } + + if !disk.Replicate { + settings = settings + ",replicate=0" + } + if (disk.Type == scsi || disk.Type == virtIO) && disk.ReadOnly { + settings = settings + ",ro=1" + } + if disk.Serial != "" { + settings = settings + ",serial=" + string(disk.Serial) + } + if disk.Type != virtIO && disk.EmulateSSD { + settings = settings + ",ssd=1" + } + if disk.WorldWideName != "" { + settings = settings + ",wwn=" + string(disk.WorldWideName) + } + return +} + +// Maps all the disk related settings to our own data structure. +func (qemuDisk) mapToStruct(diskData string, settings map[string]interface{}, linkedVmId *uint) *qemuDisk { + disk := qemuDisk{Backup: true} + + if diskData[0:1] == "/" { + disk.File = diskData + } else { + // storage:110/base-110-disk-1.qcow2/100/vm-100-disk-0.qcow2 + // storage:100/vm-100-disk-0.qcow2 + diskAndPath := strings.Split(diskData, ":") + disk.Storage = diskAndPath[0] + if len(diskAndPath) == 2 { + pathParts := strings.Split(diskAndPath[1], "/") + if len(pathParts) == 4 { + var tmpDiskId int + tmpVmId, _ := strconv.Atoi(pathParts[0]) + *linkedVmId = uint(tmpVmId) + tmp := strings.Split(strings.Split(pathParts[1], ".")[0], "-") + if len(tmp) > 1 { + tmpDiskId, _ = strconv.Atoi(tmp[len(tmp)-1]) + tmpDiskIdPointer := uint(tmpDiskId) + disk.LinkedDiskId = &tmpDiskIdPointer + } + } + if len(pathParts) > 1 { + diskNameAndFormat := strings.Split(pathParts[len(pathParts)-1], ".") + if len(diskNameAndFormat) == 2 { + disk.Format = QemuDiskFormat(diskNameAndFormat[1]) + tmp := strings.Split(diskNameAndFormat[0], "-") + if len(tmp) > 1 { + tmpDiskId, _ := strconv.Atoi(tmp[len(tmp)-1]) + disk.Id = uint(tmpDiskId) + } + } + } + } + } + + if len(settings) == 0 { + return nil + } + + // Replicate defaults to true + disk.Replicate = true + + if value, isSet := settings["aio"]; isSet { + disk.AsyncIO = QemuDiskAsyncIO(value.(string)) + } + if value, isSet := settings["backup"]; isSet { + disk.Backup, _ = strconv.ParseBool(value.(string)) + } + if value, isSet := settings["cache"]; isSet { + disk.Cache = QemuDiskCache(value.(string)) + } + if value, isSet := settings["discard"]; isSet { + if value.(string) == "on" { + disk.Discard = true + } + } + if value, isSet := settings["iops_rd"]; isSet { + tmp, _ := strconv.Atoi(value.(string)) + disk.Bandwidth.Iops.ReadLimit.Concurrent = QemuDiskBandwidthIopsLimitConcurrent(tmp) + } + if value, isSet := settings["iops_rd_max"]; isSet { + tmp, _ := strconv.Atoi(value.(string)) + disk.Bandwidth.Iops.ReadLimit.Burst = QemuDiskBandwidthIopsLimitBurst(tmp) + } + if value, isSet := settings["iops_rd_max_length"]; isSet { + tmp, _ := strconv.Atoi(value.(string)) + disk.Bandwidth.Iops.ReadLimit.BurstDuration = uint(tmp) + } + if value, isSet := settings["iops_wr"]; isSet { + tmp, _ := strconv.Atoi(value.(string)) + disk.Bandwidth.Iops.WriteLimit.Concurrent = QemuDiskBandwidthIopsLimitConcurrent(tmp) + } + if value, isSet := settings["iops_wr_max"]; isSet { + tmp, _ := strconv.Atoi(value.(string)) + disk.Bandwidth.Iops.WriteLimit.Burst = QemuDiskBandwidthIopsLimitBurst(tmp) + } + if value, isSet := settings["iops_wr_max_length"]; isSet { + tmp, _ := strconv.Atoi(value.(string)) + disk.Bandwidth.Iops.WriteLimit.BurstDuration = uint(tmp) + } + if value, isSet := settings["iothread"]; isSet { + disk.IOThread, _ = strconv.ParseBool(value.(string)) + } + if value, isSet := settings["mbps_rd"]; isSet { + tmp, _ := strconv.ParseFloat(value.(string), 32) + disk.Bandwidth.MBps.ReadLimit.Concurrent = QemuDiskBandwidthMBpsLimitConcurrent(math.Round(tmp*100) / 100) + } + if value, isSet := settings["mbps_rd_max"]; isSet { + tmp, _ := strconv.ParseFloat(value.(string), 32) + disk.Bandwidth.MBps.ReadLimit.Burst = QemuDiskBandwidthMBpsLimitBurst(math.Round(tmp*100) / 100) + } + if value, isSet := settings["mbps_wr"]; isSet { + tmp, _ := strconv.ParseFloat(value.(string), 32) + disk.Bandwidth.MBps.WriteLimit.Concurrent = QemuDiskBandwidthMBpsLimitConcurrent(math.Round(tmp*100) / 100) + } + if value, isSet := settings["mbps_wr_max"]; isSet { + tmp, _ := strconv.ParseFloat(value.(string), 32) + disk.Bandwidth.MBps.WriteLimit.Burst = QemuDiskBandwidthMBpsLimitBurst(math.Round(tmp*100) / 100) + } + if value, isSet := settings["replicate"]; isSet { + disk.Replicate, _ = strconv.ParseBool(value.(string)) + } + if value, isSet := settings["ro"]; isSet { + disk.ReadOnly, _ = strconv.ParseBool(value.(string)) + } + if value, isSet := settings["serial"]; isSet { + disk.Serial = QemuDiskSerial(value.(string)) + } + if value, isSet := settings["size"]; isSet { + sizeString := value.(string) + if len(sizeString) > 1 { + diskSize, _ := strconv.Atoi(sizeString[0 : len(sizeString)-1]) + switch sizeString[len(sizeString)-1:] { + case "G": + disk.Size = uint(diskSize) + case "T": + disk.Size = uint(diskSize * 1024) + } + } + } + if value, isSet := settings["ssd"]; isSet { + disk.EmulateSSD, _ = strconv.ParseBool(value.(string)) + } + if value, isSet := settings["wwn"]; isSet { + disk.WorldWideName = QemuWorldWideName(value.(string)) + } + return &disk +} + +func (disk *qemuDisk) validate() (err error) { + if disk == nil { + return + } + if err = disk.AsyncIO.Validate(); err != nil { + return + } + if err = disk.Bandwidth.Validate(); err != nil { + return + } + if err = disk.Cache.Validate(); err != nil { + return + } + if err = disk.Serial.Validate(); err != nil { + return + } + if err = disk.WorldWideName.Validate(); err != nil { + return + } + if disk.Disk { + // disk + if err = disk.Format.Validate(); err != nil { + return + } + if disk.Size == 0 { + return errors.New(Error_QemuDisk_Size) + } + if disk.Storage == "" { + return errors.New(Error_QemuDisk_Storage) + } + } else { + // passthrough + if disk.File == "" { + return errors.New(Error_QemuDisk_File) + } + } + return +} + +type QemuDiskAsyncIO string + +const ( + QemuDiskAsyncIO_Native QemuDiskAsyncIO = "native" + QemuDiskAsyncIO_Threads QemuDiskAsyncIO = "threads" + QemuDiskAsyncIO_IOuring QemuDiskAsyncIO = "io_uring" +) + +func (QemuDiskAsyncIO) Error() error { + return fmt.Errorf("asyncio can only be one of the following values: %s,%s,%s", QemuDiskAsyncIO_Native, QemuDiskAsyncIO_Threads, QemuDiskAsyncIO_IOuring) +} + +func (asyncIO QemuDiskAsyncIO) Validate() error { + switch asyncIO { + case "", QemuDiskAsyncIO_Native, QemuDiskAsyncIO_Threads, QemuDiskAsyncIO_IOuring: + return nil + } + return QemuDiskAsyncIO("").Error() +} + +type QemuDiskBandwidth struct { + MBps QemuDiskBandwidthMBps `json:"mbps,omitempty"` + Iops QemuDiskBandwidthIops `json:"iops,omitempty"` +} + +func (bandwidth QemuDiskBandwidth) Validate() error { + err := bandwidth.MBps.Validate() + if err != nil { + return err + } + return bandwidth.Iops.Validate() +} + +type QemuDiskBandwidthIops struct { + ReadLimit QemuDiskBandwidthIopsLimit `json:"read,omitempty"` + WriteLimit QemuDiskBandwidthIopsLimit `json:"write,omitempty"` +} + +func (iops QemuDiskBandwidthIops) Validate() error { + err := iops.ReadLimit.Validate() + if err != nil { + return err + } + return iops.WriteLimit.Validate() +} + +type QemuDiskBandwidthIopsLimit struct { + Burst QemuDiskBandwidthIopsLimitBurst `json:"burst,omitempty"` // 0 = unlimited + BurstDuration uint `json:"burst_duration,omitempty"` // burst duration in seconds + Concurrent QemuDiskBandwidthIopsLimitConcurrent `json:"concurrent,omitempty"` // 0 = unlimited +} + +func (limit QemuDiskBandwidthIopsLimit) Validate() (err error) { + if err = limit.Burst.Validate(); err != nil { + return + } + err = limit.Concurrent.Validate() + return +} + +type QemuDiskBandwidthIopsLimitBurst uint + +const ( + Error_QemuDiskBandwidthIopsLimitBurst string = "burst may not be lower then 10 except for 0" +) + +func (limit QemuDiskBandwidthIopsLimitBurst) Validate() error { + if limit != 0 && limit < 10 { + return errors.New(Error_QemuDiskBandwidthIopsLimitBurst) + } + return nil +} + +type QemuDiskBandwidthIopsLimitConcurrent uint + +const ( + Error_QemuDiskBandwidthIopsLimitConcurrent string = "concurrent may not be lower then 10 except for 0" +) + +func (limit QemuDiskBandwidthIopsLimitConcurrent) Validate() error { + if limit != 0 && limit < 10 { + return errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) + } + return nil +} + +type QemuDiskBandwidthMBps struct { + ReadLimit QemuDiskBandwidthMBpsLimit `json:"read,omitempty"` + WriteLimit QemuDiskBandwidthMBpsLimit `json:"write,omitempty"` +} + +func (data QemuDiskBandwidthMBps) Validate() error { + err := data.ReadLimit.Validate() + if err != nil { + return err + } + return data.WriteLimit.Validate() +} + +type QemuDiskBandwidthMBpsLimit struct { + Burst QemuDiskBandwidthMBpsLimitBurst `json:"burst,omitempty"` // 0 = unlimited + Concurrent QemuDiskBandwidthMBpsLimitConcurrent `json:"concurrent,omitempty"` // 0 = unlimited +} + +func (limit QemuDiskBandwidthMBpsLimit) Validate() (err error) { + if err = limit.Burst.Validate(); err != nil { + return + } + err = limit.Concurrent.Validate() + return +} + +const ( + Error_QemuDiskBandwidthMBpsLimitBurst string = "burst may not be lower then 1 except for 0" +) + +type QemuDiskBandwidthMBpsLimitBurst float32 + +func (limit QemuDiskBandwidthMBpsLimitBurst) Validate() error { + if limit != 0 && limit < 1 { + return errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) + } + return nil +} + +const ( + Error_QemuDiskBandwidthMBpsLimitConcurrent string = "concurrent may not be lower then 1 except for 0" +) + +type QemuDiskBandwidthMBpsLimitConcurrent float32 + +func (limit QemuDiskBandwidthMBpsLimitConcurrent) Validate() error { + if limit != 0 && limit < 1 { + return errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) + } + return nil +} + +type QemuDiskCache string + +const ( + QemuDiskCache_None QemuDiskCache = "none" + QemuDiskCache_WriteThrough QemuDiskCache = "writethrough" + QemuDiskCache_WriteBack QemuDiskCache = "writeback" + QemuDiskCache_Unsafe QemuDiskCache = "unsafe" + QemuDiskCache_DirectSync QemuDiskCache = "directsync" +) + +func (QemuDiskCache) Error() error { + return fmt.Errorf("cache can only be one of the following values: %s,%s,%s,%s,%s", QemuDiskCache_None, QemuDiskCache_WriteThrough, QemuDiskCache_WriteBack, QemuDiskCache_Unsafe, QemuDiskCache_DirectSync) +} + +func (cache QemuDiskCache) Validate() error { + switch cache { + case "", QemuDiskCache_None, QemuDiskCache_WriteThrough, QemuDiskCache_WriteBack, QemuDiskCache_Unsafe, QemuDiskCache_DirectSync: + return nil + } + return QemuDiskCache("").Error() +} + +type QemuDiskFormat string + +const ( + QemuDiskFormat_Cow QemuDiskFormat = "cow" + QemuDiskFormat_Cloop QemuDiskFormat = "cloop" + QemuDiskFormat_Qcow QemuDiskFormat = "qcow" + QemuDiskFormat_Qcow2 QemuDiskFormat = "qcow2" + QemuDiskFormat_Qed QemuDiskFormat = "qed" + QemuDiskFormat_Vmdk QemuDiskFormat = "vmdk" + QemuDiskFormat_Raw QemuDiskFormat = "raw" +) + +func (QemuDiskFormat) Error() error { + return fmt.Errorf("format can only be one of the following values: %s,%s,%s,%s,%s,%s,%s", QemuDiskFormat_Cow, QemuDiskFormat_Cloop, QemuDiskFormat_Qcow, QemuDiskFormat_Qcow2, QemuDiskFormat_Qed, QemuDiskFormat_Vmdk, QemuDiskFormat_Raw) +} + +func (format QemuDiskFormat) Validate() error { + switch format { + case QemuDiskFormat_Cow, QemuDiskFormat_Cloop, QemuDiskFormat_Qcow, QemuDiskFormat_Qcow2, QemuDiskFormat_Qed, QemuDiskFormat_Vmdk, QemuDiskFormat_Raw: + return nil + } + return QemuDiskFormat("").Error() +} + +type QemuDiskId string + +const ( + ERROR_QemuDiskId_Invalid string = "invalid Disk ID" +) + +func (id QemuDiskId) Validate() error { + if len(id) >= 7 { + if id[0:6] == "virtio" { + if id[6:] != "0" && strings.HasPrefix(string(id[6:]), "0") { + return errors.New(ERROR_QemuDiskId_Invalid) + } + number, err := strconv.Atoi(string(id[6:])) + if err != nil { + return errors.New(ERROR_QemuDiskId_Invalid) + } + if number >= 0 && number <= 15 { + return nil + } + } + } + if len(id) >= 5 { + if id[0:4] == "sata" { + if id[4:] != "0" && strings.HasPrefix(string(id[4:]), "0") { + return errors.New(ERROR_QemuDiskId_Invalid) + } + number, err := strconv.Atoi(string(id[4:])) + if err != nil { + return errors.New(ERROR_QemuDiskId_Invalid) + } + if number >= 0 && number <= 5 { + return nil + } + } + if id[0:4] == "scsi" { + if id[4:] != "0" && strings.HasPrefix(string(id[4:]), "0") { + return errors.New(ERROR_QemuDiskId_Invalid) + } + number, err := strconv.Atoi(string(id[4:])) + if err != nil { + return errors.New(ERROR_QemuDiskId_Invalid) + } + if number >= 0 && number <= 30 { + return nil + } + } + } + if len(id) == 4 { + if id[0:3] == "ide" { + number, err := strconv.Atoi(string(id[3])) + if err != nil { + return errors.New(ERROR_QemuDiskId_Invalid) + } + if number >= 0 && number <= 3 { + return nil + } + } + } + return errors.New(ERROR_QemuDiskId_Invalid) +} + +type qemuDiskMark struct { + Format QemuDiskFormat + Id QemuDiskId + Size uint + Storage string + Type qemuDiskType +} + +// Generate lists of disks that need to be moved and or resized +func (disk *qemuDiskMark) markChanges(currentDisk *qemuDiskMark, id QemuDiskId, changes *qemuUpdateChanges) { + if disk == nil || currentDisk == nil { + return + } + // Disk + if disk.Size >= currentDisk.Size { + // Update + if disk.Size > currentDisk.Size { + changes.Resize = append(changes.Resize, qemuDiskResize{ + Id: id, + SizeInGigaBytes: disk.Size, + }) + } + if disk.Storage != currentDisk.Storage || disk.Format != currentDisk.Format { + var format *QemuDiskFormat + if disk.Format != currentDisk.Format { + format = &disk.Format + } + changes.Move = append(changes.Move, qemuDiskMove{ + Format: format, + Id: id, + Storage: disk.Storage, + }) + } + } +} + +type QemuDiskSerial string + +const ( + Error_QemuDiskSerial_IllegalCharacter string = "serial may only contain the following characters: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_" + Error_QemuDiskSerial_IllegalLength string = "serial may only be 60 characters long" +) + +// QemuDiskSerial may only contain the following characters: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_ +// And has a max length of 60 characters +func (serial QemuDiskSerial) Validate() error { + regex, _ := regexp.Compile(`^([a-z]|[A-Z]|[0-9]|_|-)*$`) + if !regex.Match([]byte(serial)) { + return errors.New(Error_QemuDiskSerial_IllegalCharacter) + } + if len(serial) > 60 { + return errors.New(Error_QemuDiskSerial_IllegalLength) + } + return nil +} + +type qemuDiskResize struct { + Id QemuDiskId + SizeInGigaBytes uint +} + +// Increase the disk size to the specified amount in gigabytes +// Decrease of disk size is not permitted. +func (disk qemuDiskResize) resize(vmr *VmRef, client *Client) (exitStatus string, err error) { + return client.PutWithTask(map[string]interface{}{"disk": disk.Id, "size": strconv.Itoa(int(disk.SizeInGigaBytes)) + "G"}, fmt.Sprintf("/nodes/%s/%s/%d/resize", vmr.node, vmr.vmType, vmr.vmId)) +} + +type qemuDiskMove struct { + Format *QemuDiskFormat + Id QemuDiskId + Storage string +} + +func (disk qemuDiskMove) mapToApiValues(delete bool) (params map[string]interface{}) { + params = map[string]interface{}{"disk": string(disk.Id), "storage": string(disk.Storage)} + if delete { + params["delete"] = "1" + } + if disk.Format != nil { + params["format"] = string(*disk.Format) + } + return +} + +func (disk qemuDiskMove) move(delete bool, vmr *VmRef, client *Client) (exitStatus interface{}, err error) { + return client.PostWithTask(disk.mapToApiValues(delete), fmt.Sprintf("/nodes/%s/%s/%d/move_disk", vmr.node, vmr.vmType, vmr.vmId)) +} + +func (disk qemuDiskMove) Validate() (err error) { + if disk.Format != nil { + err = disk.Format.Validate() + if err != nil { + return + } + } + err = disk.Id.Validate() + // TODO validate storage when it has custom type + return +} + +type qemuDiskType int + +const ( + ide qemuDiskType = 0 + sata qemuDiskType = 1 + scsi qemuDiskType = 2 + virtIO qemuDiskType = 3 +) + +type qemuStorage struct { + CdRom *QemuCdRom + CloudInit *QemuCloudInitDisk + Disk *qemuDisk + Passthrough *qemuDisk +} + +func (storage *qemuStorage) mapToApiValues(currentStorage *qemuStorage, vmID, linkedVmId uint, id QemuDiskId, params map[string]interface{}, delete string) string { + if storage == nil { + return delete + } + // CDROM + if storage.CdRom != nil { + if currentStorage == nil || currentStorage.CdRom == nil { + // Create + params[string(id)] = storage.CdRom.mapToApiValues() + } else { + // Update + cdRom := storage.CdRom.mapToApiValues() + if cdRom != currentStorage.CdRom.mapToApiValues() { + params[string(id)] = cdRom + } + } + return delete + } else if currentStorage != nil && currentStorage.CdRom != nil && storage.CloudInit == nil && storage.Disk == nil && storage.Passthrough == nil { + // Delete + return AddToList(delete, string(id)) + } + // CloudInit + if storage.CloudInit != nil { + if currentStorage == nil || currentStorage.CloudInit == nil { + // Create + params[string(id)] = storage.CloudInit.mapToApiValues() + } else { + // Update + cloudInit := storage.CloudInit.mapToApiValues() + if cloudInit != currentStorage.CloudInit.mapToApiValues() { + params[string(id)] = cloudInit + } + } + return delete + } else if currentStorage != nil && currentStorage.CloudInit != nil && storage.Disk == nil && storage.Passthrough == nil { + // Delete + return AddToList(delete, string(id)) + } + // Disk + if storage.Disk != nil { + if currentStorage == nil || currentStorage.Disk == nil { + // Create + params[string(id)] = storage.Disk.mapToApiValues(vmID, 0, "", "", true) + } else { + if storage.Disk.Size >= currentStorage.Disk.Size { + // Update + storage.Disk.Id = currentStorage.Disk.Id + storage.Disk.LinkedDiskId = currentStorage.Disk.LinkedDiskId + disk := storage.Disk.mapToApiValues(vmID, linkedVmId, currentStorage.Disk.Storage, currentStorage.Disk.Format, false) + if disk != currentStorage.Disk.mapToApiValues(vmID, linkedVmId, currentStorage.Disk.Storage, currentStorage.Disk.Format, false) { + params[string(id)] = disk + } + } else { + // Delete and Create + // creating a disk on top of an existing disk is the same as detaching the disk and creating a new one. + params[string(id)] = storage.Disk.mapToApiValues(vmID, 0, "", "", true) + } + } + return delete + } else if currentStorage != nil && currentStorage.Disk != nil && storage.Passthrough == nil { + // Delete + return AddToList(delete, string(id)) + } + // Passthrough + if storage.Passthrough != nil { + if currentStorage == nil || currentStorage.Passthrough == nil { + // Create + params[string(id)] = storage.Passthrough.mapToApiValues(0, 0, "", "", false) + } else { + // Update + passthrough := storage.Passthrough.mapToApiValues(0, 0, "", "", false) + if passthrough != currentStorage.Passthrough.mapToApiValues(0, 0, "", "", false) { + params[string(id)] = passthrough + } + } + return delete + } else if currentStorage != nil && currentStorage.Passthrough != nil { + // Delete + return AddToList(delete, string(id)) + } + // Delete if no subtype was specified + if currentStorage != nil { + return AddToList(delete, string(id)) + } + return delete +} + +type QemuStorages struct { + Ide *QemuIdeDisks `json:"ide,omitempty"` + Sata *QemuSataDisks `json:"sata,omitempty"` + Scsi *QemuScsiDisks `json:"scsi,omitempty"` + VirtIO *QemuVirtIODisks `json:"virtio,omitempty"` +} + +func (storages QemuStorages) mapToApiValues(currentStorages QemuStorages, vmID, linkedVmId uint, params map[string]interface{}) (delete string) { + if storages.Ide != nil { + delete = storages.Ide.mapToApiValues(currentStorages.Ide, vmID, linkedVmId, params, delete) + } + if storages.Sata != nil { + delete = storages.Sata.mapToApiValues(currentStorages.Sata, vmID, linkedVmId, params, delete) + } + if storages.Scsi != nil { + delete = storages.Scsi.mapToApiValues(currentStorages.Scsi, vmID, linkedVmId, params, delete) + } + if storages.VirtIO != nil { + delete = storages.VirtIO.mapToApiValues(currentStorages.VirtIO, vmID, linkedVmId, params, delete) + } + return delete +} + +func (QemuStorages) mapToStruct(params map[string]interface{}, linkedVmId *uint) *QemuStorages { + storage := QemuStorages{ + Ide: QemuIdeDisks{}.mapToStruct(params, linkedVmId), + Sata: QemuSataDisks{}.mapToStruct(params, linkedVmId), + Scsi: QemuScsiDisks{}.mapToStruct(params, linkedVmId), + VirtIO: QemuVirtIODisks{}.mapToStruct(params, linkedVmId), + } + if storage.Ide != nil || storage.Sata != nil || storage.Scsi != nil || storage.VirtIO != nil { + return &storage + } + return nil +} + +// mark disk that need to be moved or resized +func (storages QemuStorages) markDiskChanges(currentStorages QemuStorages) *qemuUpdateChanges { + changes := &qemuUpdateChanges{} + if storages.Ide != nil { + storages.Ide.markDiskChanges(currentStorages.Ide, changes) + } + if storages.Sata != nil { + storages.Sata.markDiskChanges(currentStorages.Sata, changes) + } + if storages.Scsi != nil { + storages.Scsi.markDiskChanges(currentStorages.Scsi, changes) + } + if storages.VirtIO != nil { + storages.VirtIO.markDiskChanges(currentStorages.VirtIO, changes) + } + return changes +} + +func (storages QemuStorages) Validate() (err error) { + var numberOfCloudInitDevices uint8 + var CloudInit uint8 + if storages.Ide != nil { + CloudInit, err = storages.Ide.validate() + if err != nil { + return + } + numberOfCloudInitDevices += CloudInit + if err = (QemuCloudInitDisk{}.checkDuplicates(numberOfCloudInitDevices)); err != nil { + return + } + } + if storages.Sata != nil { + CloudInit, err = storages.Sata.validate() + if err != nil { + return + } + numberOfCloudInitDevices += CloudInit + if err = (QemuCloudInitDisk{}.checkDuplicates(numberOfCloudInitDevices)); err != nil { + return + } + } + if storages.Scsi != nil { + CloudInit, err = storages.Scsi.validate() + if err != nil { + return + } + numberOfCloudInitDevices += CloudInit + if err = (QemuCloudInitDisk{}.checkDuplicates(numberOfCloudInitDevices)); err != nil { + return + } + } + if storages.VirtIO != nil { + CloudInit, err = storages.VirtIO.validate() + if err != nil { + return + } + numberOfCloudInitDevices += CloudInit + err = QemuCloudInitDisk{}.checkDuplicates(numberOfCloudInitDevices) + } + return +} + +type qemuUpdateChanges struct { + Move []qemuDiskMove + Resize []qemuDiskResize +} + +type QemuWorldWideName string + +const Error_QemuWorldWideName_Invalid string = "world wide name should be prefixed with 0x followed by 8 hexadecimal values" + +var regexp_QemuWorldWideName = regexp.MustCompile(`^0x[0-9A-Fa-f]{16}$`) + +func (wwn QemuWorldWideName) Validate() error { + if wwn == "" || regexp_QemuWorldWideName.MatchString(string(wwn)) { + return nil + } + return errors.New(Error_QemuWorldWideName_Invalid) +} + +func diskSubtypeSet(set bool) error { + if set { + return errors.New(Error_QemuDisk_MutuallyExclusive) + } + return nil +} + +func MoveQemuDisk(format *QemuDiskFormat, diskId QemuDiskId, storage string, deleteAfterMove bool, vmr *VmRef, client *Client) (err error) { + disk := qemuDiskMove{ + Format: format, + Id: diskId, + Storage: storage, + } + err = disk.Validate() + if err != nil { + return + } + _, err = disk.move(deleteAfterMove, vmr, client) + return +} diff --git a/proxmox/config_qemu_disk_ide.go b/proxmox/config_qemu_disk_ide.go new file mode 100644 index 00000000..52b495f5 --- /dev/null +++ b/proxmox/config_qemu_disk_ide.go @@ -0,0 +1,312 @@ +package proxmox + +import ( + "strconv" + "strings" +) + +type QemuIdeDisk struct { + AsyncIO QemuDiskAsyncIO `json:"asyncio,omitempty"` + Backup bool `json:"backup"` + Bandwidth QemuDiskBandwidth `json:"bandwidth,omitempty"` + Cache QemuDiskCache `json:"cache,omitempty"` + Discard bool `json:"discard"` + EmulateSSD bool `json:"emulatessd"` + Format QemuDiskFormat `json:"format"` + Id uint `json:"id"` //Id is only returned and setting it has no effect + LinkedDiskId *uint `json:"linked"` //LinkedClone is only returned and setting it has no effect + Replicate bool `json:"replicate"` + Serial QemuDiskSerial `json:"serial,omitempty"` + Size uint `json:"size"` + Storage string `json:"storage"` + WorldWideName QemuWorldWideName `json:"wwn"` +} + +func (disk *QemuIdeDisk) convertDataStructure() *qemuDisk { + return &qemuDisk{ + AsyncIO: disk.AsyncIO, + Backup: disk.Backup, + Bandwidth: disk.Bandwidth, + Cache: disk.Cache, + Discard: disk.Discard, + Disk: true, + EmulateSSD: disk.EmulateSSD, + Format: disk.Format, + Id: disk.Id, + LinkedDiskId: disk.LinkedDiskId, + Replicate: disk.Replicate, + Serial: disk.Serial, + Size: disk.Size, + Storage: disk.Storage, + Type: ide, + WorldWideName: disk.WorldWideName, + } +} + +func (disk QemuIdeDisk) Validate() error { + return disk.convertDataStructure().validate() +} + +type QemuIdeDisks struct { + Disk_0 *QemuIdeStorage `json:"0,omitempty"` + Disk_1 *QemuIdeStorage `json:"1,omitempty"` + Disk_2 *QemuIdeStorage `json:"2,omitempty"` + Disk_3 *QemuIdeStorage `json:"3,omitempty"` +} + +func (disks QemuIdeDisks) mapToApiValues(currentDisks *QemuIdeDisks, vmID, LinkedVmId uint, params map[string]interface{}, delete string) string { + tmpCurrentDisks := QemuIdeDisks{} + if currentDisks != nil { + tmpCurrentDisks = *currentDisks + } + diskMap := disks.mapToIntMap() + currentDiskMap := tmpCurrentDisks.mapToIntMap() + for i := range diskMap { + delete = diskMap[i].convertDataStructure().mapToApiValues(currentDiskMap[i].convertDataStructure(), vmID, LinkedVmId, QemuDiskId("ide"+strconv.Itoa(int(i))), params, delete) + } + return delete +} + +func (disks QemuIdeDisks) mapToIntMap() map[uint8]*QemuIdeStorage { + return map[uint8]*QemuIdeStorage{ + 0: disks.Disk_0, + 1: disks.Disk_1, + 2: disks.Disk_2, + 3: disks.Disk_3, + } +} + +func (QemuIdeDisks) mapToStruct(params map[string]interface{}, linkedVmId *uint) *QemuIdeDisks { + disks := QemuIdeDisks{} + var structPopulated bool + if _, isSet := params["ide0"]; isSet { + disks.Disk_0 = QemuIdeStorage{}.mapToStruct(params["ide0"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["ide1"]; isSet { + disks.Disk_1 = QemuIdeStorage{}.mapToStruct(params["ide1"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["ide2"]; isSet { + disks.Disk_2 = QemuIdeStorage{}.mapToStruct(params["ide2"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["ide3"]; isSet { + disks.Disk_3 = QemuIdeStorage{}.mapToStruct(params["ide3"].(string), linkedVmId) + structPopulated = true + } + if structPopulated { + return &disks + } + return nil +} + +func (disks QemuIdeDisks) markDiskChanges(currentDisks *QemuIdeDisks, changes *qemuUpdateChanges) { + tmpCurrentDisks := QemuIdeDisks{} + if currentDisks != nil { + tmpCurrentDisks = *currentDisks + } + diskMap := disks.mapToIntMap() + currentDiskMap := tmpCurrentDisks.mapToIntMap() + for i := range diskMap { + diskMap[i].convertDataStructureMark().markChanges(currentDiskMap[i].convertDataStructureMark(), QemuDiskId("ide"+strconv.Itoa(int(i))), changes) + } +} + +func (disks QemuIdeDisks) Validate() (err error) { + _, err = disks.validate() + return +} + +func (disks QemuIdeDisks) validate() (numberOfCloudInitDevices uint8, err error) { + diskMap := disks.mapToIntMap() + var cloudInit uint8 + for _, e := range diskMap { + if e != nil { + cloudInit, err = e.validate() + if err != nil { + return + } + numberOfCloudInitDevices += cloudInit + if err = (QemuCloudInitDisk{}.checkDuplicates(numberOfCloudInitDevices)); err != nil { + return + } + } + } + return +} + +type QemuIdePassthrough struct { + AsyncIO QemuDiskAsyncIO `json:"asyncio,omitempty"` + Backup bool `json:"backup"` + Bandwidth QemuDiskBandwidth `json:"bandwidth,omitempty"` + Cache QemuDiskCache `json:"cache,omitempty"` + Discard bool `json:"discard"` + EmulateSSD bool `json:"emulatessd"` + File string `json:"file"` + Replicate bool `json:"replicate"` + Serial QemuDiskSerial `json:"serial,omitempty"` + Size uint `json:"size"` //size is only returned and setting it has no effect + WorldWideName QemuWorldWideName `json:"wwn"` +} + +func (passthrough *QemuIdePassthrough) convertDataStructure() *qemuDisk { + return &qemuDisk{ + AsyncIO: passthrough.AsyncIO, + Backup: passthrough.Backup, + Bandwidth: passthrough.Bandwidth, + Cache: passthrough.Cache, + Discard: passthrough.Discard, + EmulateSSD: passthrough.EmulateSSD, + File: passthrough.File, + Replicate: passthrough.Replicate, + Serial: passthrough.Serial, + Type: ide, + WorldWideName: passthrough.WorldWideName, + } +} + +func (passthrough QemuIdePassthrough) Validate() error { + return passthrough.convertDataStructure().validate() +} + +type QemuIdeStorage struct { + CdRom *QemuCdRom `json:"cdrom,omitempty"` + CloudInit *QemuCloudInitDisk `json:"cloudinit,omitempty"` + Disk *QemuIdeDisk `json:"disk,omitempty"` + Passthrough *QemuIdePassthrough `json:"passthrough,omitempty"` +} + +// converts to qemuStorage +func (storage *QemuIdeStorage) convertDataStructure() *qemuStorage { + if storage == nil { + return nil + } + generalizedStorage := qemuStorage{ + CdRom: storage.CdRom, + CloudInit: storage.CloudInit, + } + if storage.Disk != nil { + generalizedStorage.Disk = storage.Disk.convertDataStructure() + } + if storage.Passthrough != nil { + generalizedStorage.Passthrough = storage.Passthrough.convertDataStructure() + } + return &generalizedStorage +} + +// converts to qemuDiskMark +func (storage *QemuIdeStorage) convertDataStructureMark() *qemuDiskMark { + if storage == nil { + return nil + } + if storage.Disk != nil { + return &qemuDiskMark{ + Format: storage.Disk.Format, + Size: storage.Disk.Size, + Storage: storage.Disk.Storage, + Type: ide, + } + } + return nil +} + +func (QemuIdeStorage) mapToStruct(param string, LinkedVmId *uint) *QemuIdeStorage { + diskData, _, _ := strings.Cut(param, ",") + settings := splitStringOfSettings(param) + tmpCdRom := qemuCdRom{}.mapToStruct(diskData, settings) + if tmpCdRom != nil { + if tmpCdRom.CdRom { + return &QemuIdeStorage{CdRom: QemuCdRom{}.mapToStruct(*tmpCdRom)} + } else { + return &QemuIdeStorage{CloudInit: QemuCloudInitDisk{}.mapToStruct(*tmpCdRom)} + } + } + + tmpDisk := qemuDisk{}.mapToStruct(diskData, settings, LinkedVmId) + if tmpDisk == nil { + return nil + } + if tmpDisk.File == "" { + return &QemuIdeStorage{Disk: &QemuIdeDisk{ + AsyncIO: tmpDisk.AsyncIO, + Backup: tmpDisk.Backup, + Bandwidth: tmpDisk.Bandwidth, + Cache: tmpDisk.Cache, + Discard: tmpDisk.Discard, + EmulateSSD: tmpDisk.EmulateSSD, + Format: tmpDisk.Format, + Id: tmpDisk.Id, + LinkedDiskId: tmpDisk.LinkedDiskId, + Replicate: tmpDisk.Replicate, + Serial: tmpDisk.Serial, + Size: tmpDisk.Size, + Storage: tmpDisk.Storage, + WorldWideName: tmpDisk.WorldWideName, + }} + } + return &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + AsyncIO: tmpDisk.AsyncIO, + Backup: tmpDisk.Backup, + Bandwidth: tmpDisk.Bandwidth, + Cache: tmpDisk.Cache, + Discard: tmpDisk.Discard, + EmulateSSD: tmpDisk.EmulateSSD, + File: tmpDisk.File, + Replicate: tmpDisk.Replicate, + Serial: tmpDisk.Serial, + Size: tmpDisk.Size, + WorldWideName: tmpDisk.WorldWideName, + }} +} + +func (storage QemuIdeStorage) Validate() (err error) { + _, err = storage.validate() + return +} + +func (storage QemuIdeStorage) validate() (CloudInit uint8, err error) { + // First check if more than one item is nil + var subTypeSet bool + if storage.CdRom != nil { + subTypeSet = true + } + if storage.CloudInit != nil { + if err = diskSubtypeSet(subTypeSet); err != nil { + return + } + subTypeSet = true + CloudInit = 1 + } + if storage.Disk != nil { + if err = diskSubtypeSet(subTypeSet); err != nil { + return + } + subTypeSet = true + } + if storage.Passthrough != nil { + if err = diskSubtypeSet(subTypeSet); err != nil { + return + } + } + // Validate sub items + if storage.CdRom != nil { + if err = storage.CdRom.Validate(); err != nil { + return + } + } + if storage.CloudInit != nil { + if err = storage.CloudInit.Validate(); err != nil { + return + } + } + if storage.Disk != nil { + if err = storage.Disk.Validate(); err != nil { + return + } + } + if storage.Passthrough != nil { + err = storage.Passthrough.Validate() + } + return +} diff --git a/proxmox/config_qemu_disk_sata.go b/proxmox/config_qemu_disk_sata.go new file mode 100644 index 00000000..e4e89c42 --- /dev/null +++ b/proxmox/config_qemu_disk_sata.go @@ -0,0 +1,324 @@ +package proxmox + +import ( + "strconv" + "strings" +) + +type QemuSataDisk struct { + AsyncIO QemuDiskAsyncIO `json:"asyncio,omitempty"` + Backup bool `json:"backup"` + Bandwidth QemuDiskBandwidth `json:"bandwidth,omitempty"` + Cache QemuDiskCache `json:"cache,omitempty"` + Discard bool `json:"discard"` + EmulateSSD bool `json:"emulatessd"` + Format QemuDiskFormat `json:"format"` + Id uint `json:"id"` //Id is only returned and setting it has no effect + LinkedDiskId *uint `json:"linked"` //LinkedClone is only returned and setting it has no effect + Replicate bool `json:"replicate"` + Serial QemuDiskSerial `json:"serial,omitempty"` + Size uint `json:"size"` + Storage string `json:"storage"` + WorldWideName QemuWorldWideName `json:"wwn"` +} + +func (disk *QemuSataDisk) convertDataStructure() *qemuDisk { + return &qemuDisk{ + AsyncIO: disk.AsyncIO, + Backup: disk.Backup, + Bandwidth: disk.Bandwidth, + Cache: disk.Cache, + Discard: disk.Discard, + Disk: true, + EmulateSSD: disk.EmulateSSD, + Format: disk.Format, + Id: disk.Id, + LinkedDiskId: disk.LinkedDiskId, + Replicate: disk.Replicate, + Serial: disk.Serial, + Size: disk.Size, + Storage: disk.Storage, + Type: sata, + WorldWideName: disk.WorldWideName, + } +} + +func (disk QemuSataDisk) Validate() error { + return disk.convertDataStructure().validate() +} + +type QemuSataDisks struct { + Disk_0 *QemuSataStorage `json:"0,omitempty"` + Disk_1 *QemuSataStorage `json:"1,omitempty"` + Disk_2 *QemuSataStorage `json:"2,omitempty"` + Disk_3 *QemuSataStorage `json:"3,omitempty"` + Disk_4 *QemuSataStorage `json:"4,omitempty"` + Disk_5 *QemuSataStorage `json:"5,omitempty"` +} + +func (disks QemuSataDisks) mapToApiValues(currentDisks *QemuSataDisks, vmID, LinkedVmId uint, params map[string]interface{}, delete string) string { + tmpCurrentDisks := QemuSataDisks{} + if currentDisks != nil { + tmpCurrentDisks = *currentDisks + } + diskMap := disks.mapToIntMap() + currentDiskMap := tmpCurrentDisks.mapToIntMap() + for i := range diskMap { + delete = diskMap[i].convertDataStructure().mapToApiValues(currentDiskMap[i].convertDataStructure(), vmID, LinkedVmId, QemuDiskId("sata"+strconv.Itoa(int(i))), params, delete) + } + return delete +} + +func (disks QemuSataDisks) mapToIntMap() map[uint8]*QemuSataStorage { + return map[uint8]*QemuSataStorage{ + 0: disks.Disk_0, + 1: disks.Disk_1, + 2: disks.Disk_2, + 3: disks.Disk_3, + 4: disks.Disk_4, + 5: disks.Disk_5, + } +} + +func (QemuSataDisks) mapToStruct(params map[string]interface{}, linkedVmId *uint) *QemuSataDisks { + disks := QemuSataDisks{} + var structPopulated bool + if _, isSet := params["sata0"]; isSet { + disks.Disk_0 = QemuSataStorage{}.mapToStruct(params["sata0"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["sata1"]; isSet { + disks.Disk_1 = QemuSataStorage{}.mapToStruct(params["sata1"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["sata2"]; isSet { + disks.Disk_2 = QemuSataStorage{}.mapToStruct(params["sata2"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["sata3"]; isSet { + disks.Disk_3 = QemuSataStorage{}.mapToStruct(params["sata3"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["sata4"]; isSet { + disks.Disk_4 = QemuSataStorage{}.mapToStruct(params["sata4"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["sata5"]; isSet { + disks.Disk_5 = QemuSataStorage{}.mapToStruct(params["sata5"].(string), linkedVmId) + structPopulated = true + } + if structPopulated { + return &disks + } + return nil +} + +func (disks QemuSataDisks) markDiskChanges(currentDisks *QemuSataDisks, changes *qemuUpdateChanges) { + tmpCurrentDisks := QemuSataDisks{} + if currentDisks != nil { + tmpCurrentDisks = *currentDisks + } + diskMap := disks.mapToIntMap() + currentDiskMap := tmpCurrentDisks.mapToIntMap() + for i := range diskMap { + diskMap[i].convertDataStructureMark().markChanges(currentDiskMap[i].convertDataStructureMark(), QemuDiskId("sata"+strconv.Itoa(int(i))), changes) + } +} + +func (disks QemuSataDisks) Validate() (err error) { + _, err = disks.validate() + return +} + +func (disks QemuSataDisks) validate() (numberOfCloudInitDevices uint8, err error) { + diskMap := disks.mapToIntMap() + var cloudInit uint8 + for _, e := range diskMap { + if e != nil { + cloudInit, err = e.validate() + if err != nil { + return + } + numberOfCloudInitDevices += cloudInit + if err = (QemuCloudInitDisk{}.checkDuplicates(numberOfCloudInitDevices)); err != nil { + return + } + } + } + return +} + +type QemuSataPassthrough struct { + AsyncIO QemuDiskAsyncIO `json:"asyncio,omitempty"` + Backup bool `json:"backup"` + Bandwidth QemuDiskBandwidth `json:"bandwidth,omitempty"` + Cache QemuDiskCache `json:"cache,omitempty"` + Discard bool `json:"discard"` + EmulateSSD bool `json:"emulatessd"` + File string `json:"file"` + Replicate bool `json:"replicate"` + Serial QemuDiskSerial `json:"serial,omitempty"` + Size uint `json:"size"` //size is only returned and setting it has no effect + WorldWideName QemuWorldWideName `json:"wwn"` +} + +func (passthrough *QemuSataPassthrough) convertDataStructure() *qemuDisk { + return &qemuDisk{ + AsyncIO: passthrough.AsyncIO, + Backup: passthrough.Backup, + Bandwidth: passthrough.Bandwidth, + Cache: passthrough.Cache, + Discard: passthrough.Discard, + EmulateSSD: passthrough.EmulateSSD, + File: passthrough.File, + Replicate: passthrough.Replicate, + Serial: passthrough.Serial, + Type: sata, + WorldWideName: passthrough.WorldWideName, + } +} + +func (passthrough QemuSataPassthrough) Validate() error { + return passthrough.convertDataStructure().validate() +} + +type QemuSataStorage struct { + CdRom *QemuCdRom `json:"cdrom,omitempty"` + CloudInit *QemuCloudInitDisk `json:"cloudinit,omitempty"` + Disk *QemuSataDisk `json:"disk,omitempty"` + Passthrough *QemuSataPassthrough `json:"passthrough,omitempty"` +} + +// converts to qemuStorage +func (storage *QemuSataStorage) convertDataStructure() *qemuStorage { + if storage == nil { + return nil + } + generalizedStorage := qemuStorage{ + CdRom: storage.CdRom, + CloudInit: storage.CloudInit, + } + if storage.Disk != nil { + generalizedStorage.Disk = storage.Disk.convertDataStructure() + } + if storage.Passthrough != nil { + generalizedStorage.Passthrough = storage.Passthrough.convertDataStructure() + } + return &generalizedStorage +} + +// converts to qemuDiskMark +func (storage *QemuSataStorage) convertDataStructureMark() *qemuDiskMark { + if storage == nil { + return nil + } + if storage.Disk != nil { + return &qemuDiskMark{ + Format: storage.Disk.Format, + Size: storage.Disk.Size, + Storage: storage.Disk.Storage, + Type: ide, + } + } + return nil +} + +func (QemuSataStorage) mapToStruct(param string, LinkedVmId *uint) *QemuSataStorage { + diskData, _, _ := strings.Cut(param, ",") + settings := splitStringOfSettings(param) + tmpCdRom := qemuCdRom{}.mapToStruct(diskData, settings) + if tmpCdRom != nil { + if tmpCdRom.CdRom { + return &QemuSataStorage{CdRom: QemuCdRom{}.mapToStruct(*tmpCdRom)} + } else { + return &QemuSataStorage{CloudInit: QemuCloudInitDisk{}.mapToStruct(*tmpCdRom)} + } + } + + tmpDisk := qemuDisk{}.mapToStruct(diskData, settings, LinkedVmId) + if tmpDisk == nil { + return nil + } + if tmpDisk.File == "" { + return &QemuSataStorage{Disk: &QemuSataDisk{ + AsyncIO: tmpDisk.AsyncIO, + Backup: tmpDisk.Backup, + Bandwidth: tmpDisk.Bandwidth, + Cache: tmpDisk.Cache, + Discard: tmpDisk.Discard, + EmulateSSD: tmpDisk.EmulateSSD, + Format: tmpDisk.Format, + Id: tmpDisk.Id, + LinkedDiskId: tmpDisk.LinkedDiskId, + Replicate: tmpDisk.Replicate, + Serial: tmpDisk.Serial, + Size: tmpDisk.Size, + Storage: tmpDisk.Storage, + WorldWideName: tmpDisk.WorldWideName, + }} + } + return &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + AsyncIO: tmpDisk.AsyncIO, + Backup: tmpDisk.Backup, + Bandwidth: tmpDisk.Bandwidth, + Cache: tmpDisk.Cache, + Discard: tmpDisk.Discard, + EmulateSSD: tmpDisk.EmulateSSD, + File: tmpDisk.File, + Replicate: tmpDisk.Replicate, + Serial: tmpDisk.Serial, + Size: tmpDisk.Size, + WorldWideName: tmpDisk.WorldWideName, + }} +} + +func (storage QemuSataStorage) Validate() (err error) { + _, err = storage.validate() + return +} + +func (storage QemuSataStorage) validate() (CloudInit uint8, err error) { + // First check if more than one item is nil + var subTypeSet bool + if storage.CdRom != nil { + subTypeSet = true + } + if storage.CloudInit != nil { + if err = diskSubtypeSet(subTypeSet); err != nil { + return + } + subTypeSet = true + CloudInit = 1 + } + if storage.Disk != nil { + if err = diskSubtypeSet(subTypeSet); err != nil { + return + } + subTypeSet = true + } + if storage.Passthrough != nil { + if err = diskSubtypeSet(subTypeSet); err != nil { + return + } + } + // Validate sub items + if storage.CdRom != nil { + if err = storage.CdRom.Validate(); err != nil { + return + } + } + if storage.CloudInit != nil { + if err = storage.CloudInit.Validate(); err != nil { + return + } + } + if storage.Disk != nil { + if err = storage.Disk.Validate(); err != nil { + return + } + } + if storage.Passthrough != nil { + err = storage.Passthrough.Validate() + } + return +} diff --git a/proxmox/config_qemu_disk_scsi.go b/proxmox/config_qemu_disk_scsi.go new file mode 100644 index 00000000..e68ee370 --- /dev/null +++ b/proxmox/config_qemu_disk_scsi.go @@ -0,0 +1,486 @@ +package proxmox + +import ( + "strconv" + "strings" +) + +type QemuScsiDisk struct { + AsyncIO QemuDiskAsyncIO `json:"asyncio,omitempty"` + Backup bool `json:"backup"` + Bandwidth QemuDiskBandwidth `json:"bandwidth,omitempty"` + Cache QemuDiskCache `json:"cache,omitempty"` + Discard bool `json:"discard"` + EmulateSSD bool `json:"emulatessd"` + Format QemuDiskFormat `json:"format"` + Id uint `json:"id"` //Id is only returned and setting it has no effect + IOThread bool `json:"iothread"` + LinkedDiskId *uint `json:"linked"` //LinkedCloneId is only returned and setting it has no effect + ReadOnly bool `json:"readonly"` + Replicate bool `json:"replicate"` + Serial QemuDiskSerial `json:"serial,omitempty"` + Size uint `json:"size"` + Storage string `json:"storage"` + WorldWideName QemuWorldWideName `json:"wwn"` +} + +func (disk *QemuScsiDisk) convertDataStructure() *qemuDisk { + return &qemuDisk{ + AsyncIO: disk.AsyncIO, + Backup: disk.Backup, + Bandwidth: disk.Bandwidth, + Cache: disk.Cache, + Discard: disk.Discard, + Disk: true, + EmulateSSD: disk.EmulateSSD, + Format: disk.Format, + Id: disk.Id, + IOThread: disk.IOThread, + LinkedDiskId: disk.LinkedDiskId, + ReadOnly: disk.ReadOnly, + Replicate: disk.Replicate, + Serial: disk.Serial, + Size: disk.Size, + Storage: disk.Storage, + Type: scsi, + WorldWideName: disk.WorldWideName, + } +} + +func (disk QemuScsiDisk) Validate() error { + return disk.convertDataStructure().validate() +} + +type QemuScsiDisks struct { + Disk_0 *QemuScsiStorage `json:"0,omitempty"` + Disk_1 *QemuScsiStorage `json:"1,omitempty"` + Disk_2 *QemuScsiStorage `json:"2,omitempty"` + Disk_3 *QemuScsiStorage `json:"3,omitempty"` + Disk_4 *QemuScsiStorage `json:"4,omitempty"` + Disk_5 *QemuScsiStorage `json:"5,omitempty"` + Disk_6 *QemuScsiStorage `json:"6,omitempty"` + Disk_7 *QemuScsiStorage `json:"7,omitempty"` + Disk_8 *QemuScsiStorage `json:"8,omitempty"` + Disk_9 *QemuScsiStorage `json:"9,omitempty"` + Disk_10 *QemuScsiStorage `json:"10,omitempty"` + Disk_11 *QemuScsiStorage `json:"11,omitempty"` + Disk_12 *QemuScsiStorage `json:"12,omitempty"` + Disk_13 *QemuScsiStorage `json:"13,omitempty"` + Disk_14 *QemuScsiStorage `json:"14,omitempty"` + Disk_15 *QemuScsiStorage `json:"15,omitempty"` + Disk_16 *QemuScsiStorage `json:"16,omitempty"` + Disk_17 *QemuScsiStorage `json:"17,omitempty"` + Disk_18 *QemuScsiStorage `json:"18,omitempty"` + Disk_19 *QemuScsiStorage `json:"19,omitempty"` + Disk_20 *QemuScsiStorage `json:"20,omitempty"` + Disk_21 *QemuScsiStorage `json:"21,omitempty"` + Disk_22 *QemuScsiStorage `json:"22,omitempty"` + Disk_23 *QemuScsiStorage `json:"23,omitempty"` + Disk_24 *QemuScsiStorage `json:"24,omitempty"` + Disk_25 *QemuScsiStorage `json:"25,omitempty"` + Disk_26 *QemuScsiStorage `json:"26,omitempty"` + Disk_27 *QemuScsiStorage `json:"27,omitempty"` + Disk_28 *QemuScsiStorage `json:"28,omitempty"` + Disk_29 *QemuScsiStorage `json:"29,omitempty"` + Disk_30 *QemuScsiStorage `json:"30,omitempty"` +} + +func (disks QemuScsiDisks) mapToApiValues(currentDisks *QemuScsiDisks, vmID, linkedVmId uint, params map[string]interface{}, delete string) string { + tmpCurrentDisks := QemuScsiDisks{} + if currentDisks != nil { + tmpCurrentDisks = *currentDisks + } + diskMap := disks.mapToIntMap() + currentDiskMap := tmpCurrentDisks.mapToIntMap() + for i := range diskMap { + delete = diskMap[i].convertDataStructure().mapToApiValues(currentDiskMap[i].convertDataStructure(), vmID, linkedVmId, QemuDiskId("scsi"+strconv.Itoa(int(i))), params, delete) + } + return delete +} + +func (disks QemuScsiDisks) mapToIntMap() map[uint8]*QemuScsiStorage { + return map[uint8]*QemuScsiStorage{ + 0: disks.Disk_0, + 1: disks.Disk_1, + 2: disks.Disk_2, + 3: disks.Disk_3, + 4: disks.Disk_4, + 5: disks.Disk_5, + 6: disks.Disk_6, + 7: disks.Disk_7, + 8: disks.Disk_8, + 9: disks.Disk_9, + 10: disks.Disk_10, + 11: disks.Disk_11, + 12: disks.Disk_12, + 13: disks.Disk_13, + 14: disks.Disk_14, + 15: disks.Disk_15, + 16: disks.Disk_16, + 17: disks.Disk_17, + 18: disks.Disk_18, + 19: disks.Disk_19, + 20: disks.Disk_20, + 21: disks.Disk_21, + 22: disks.Disk_22, + 23: disks.Disk_23, + 24: disks.Disk_24, + 25: disks.Disk_25, + 26: disks.Disk_26, + 27: disks.Disk_27, + 28: disks.Disk_28, + 29: disks.Disk_29, + 30: disks.Disk_30, + } +} + +func (QemuScsiDisks) mapToStruct(params map[string]interface{}, linkedVmId *uint) *QemuScsiDisks { + disks := QemuScsiDisks{} + var structPopulated bool + if _, isSet := params["scsi0"]; isSet { + disks.Disk_0 = QemuScsiStorage{}.mapToStruct(params["scsi0"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi1"]; isSet { + disks.Disk_1 = QemuScsiStorage{}.mapToStruct(params["scsi1"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi2"]; isSet { + disks.Disk_2 = QemuScsiStorage{}.mapToStruct(params["scsi2"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi3"]; isSet { + disks.Disk_3 = QemuScsiStorage{}.mapToStruct(params["scsi3"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi4"]; isSet { + disks.Disk_4 = QemuScsiStorage{}.mapToStruct(params["scsi4"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi5"]; isSet { + disks.Disk_5 = QemuScsiStorage{}.mapToStruct(params["scsi5"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi6"]; isSet { + disks.Disk_6 = QemuScsiStorage{}.mapToStruct(params["scsi6"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi7"]; isSet { + disks.Disk_7 = QemuScsiStorage{}.mapToStruct(params["scsi7"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi8"]; isSet { + disks.Disk_8 = QemuScsiStorage{}.mapToStruct(params["scsi8"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi9"]; isSet { + disks.Disk_9 = QemuScsiStorage{}.mapToStruct(params["scsi9"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi10"]; isSet { + disks.Disk_10 = QemuScsiStorage{}.mapToStruct(params["scsi10"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi11"]; isSet { + disks.Disk_11 = QemuScsiStorage{}.mapToStruct(params["scsi11"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi12"]; isSet { + disks.Disk_12 = QemuScsiStorage{}.mapToStruct(params["scsi12"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi13"]; isSet { + disks.Disk_13 = QemuScsiStorage{}.mapToStruct(params["scsi13"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi14"]; isSet { + disks.Disk_14 = QemuScsiStorage{}.mapToStruct(params["scsi14"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi15"]; isSet { + disks.Disk_15 = QemuScsiStorage{}.mapToStruct(params["scsi15"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi16"]; isSet { + disks.Disk_16 = QemuScsiStorage{}.mapToStruct(params["scsi16"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi17"]; isSet { + disks.Disk_17 = QemuScsiStorage{}.mapToStruct(params["scsi17"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi18"]; isSet { + disks.Disk_18 = QemuScsiStorage{}.mapToStruct(params["scsi18"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi19"]; isSet { + disks.Disk_19 = QemuScsiStorage{}.mapToStruct(params["scsi19"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi20"]; isSet { + disks.Disk_20 = QemuScsiStorage{}.mapToStruct(params["scsi20"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi21"]; isSet { + disks.Disk_21 = QemuScsiStorage{}.mapToStruct(params["scsi21"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi22"]; isSet { + disks.Disk_22 = QemuScsiStorage{}.mapToStruct(params["scsi22"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi23"]; isSet { + disks.Disk_23 = QemuScsiStorage{}.mapToStruct(params["scsi23"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi24"]; isSet { + disks.Disk_24 = QemuScsiStorage{}.mapToStruct(params["scsi24"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi25"]; isSet { + disks.Disk_25 = QemuScsiStorage{}.mapToStruct(params["scsi25"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi26"]; isSet { + disks.Disk_26 = QemuScsiStorage{}.mapToStruct(params["scsi26"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi27"]; isSet { + disks.Disk_27 = QemuScsiStorage{}.mapToStruct(params["scsi27"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi28"]; isSet { + disks.Disk_28 = QemuScsiStorage{}.mapToStruct(params["scsi28"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi29"]; isSet { + disks.Disk_29 = QemuScsiStorage{}.mapToStruct(params["scsi29"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["scsi30"]; isSet { + disks.Disk_30 = QemuScsiStorage{}.mapToStruct(params["scsi30"].(string), linkedVmId) + structPopulated = true + } + if structPopulated { + return &disks + } + return nil +} + +func (disks QemuScsiDisks) markDiskChanges(currentDisks *QemuScsiDisks, changes *qemuUpdateChanges) { + tmpCurrentDisks := QemuScsiDisks{} + if currentDisks != nil { + tmpCurrentDisks = *currentDisks + } + diskMap := disks.mapToIntMap() + currentDiskMap := tmpCurrentDisks.mapToIntMap() + for i := range diskMap { + diskMap[i].convertDataStructureMark().markChanges(currentDiskMap[i].convertDataStructureMark(), QemuDiskId("scsi"+strconv.Itoa(int(i))), changes) + } +} + +func (disks QemuScsiDisks) Validate() (err error) { + _, err = disks.validate() + return +} + +func (disks QemuScsiDisks) validate() (numberOfCloudInitDevices uint8, err error) { + diskMap := disks.mapToIntMap() + var cloudInit uint8 + for _, e := range diskMap { + if e != nil { + cloudInit, err = e.validate() + if err != nil { + return + } + numberOfCloudInitDevices += cloudInit + if err = (QemuCloudInitDisk{}.checkDuplicates(numberOfCloudInitDevices)); err != nil { + return + } + } + } + return +} + +type QemuScsiPassthrough struct { + AsyncIO QemuDiskAsyncIO `json:"asyncio,omitempty"` + Backup bool `json:"backup"` + Bandwidth QemuDiskBandwidth `json:"bandwidth,omitempty"` + Cache QemuDiskCache `json:"cache,omitempty"` + Discard bool `json:"discard"` + EmulateSSD bool `json:"emulatessd"` + File string `json:"file"` + IOThread bool `json:"iothread"` + ReadOnly bool `json:"readonly"` + Replicate bool `json:"replicate"` + Serial QemuDiskSerial `json:"serial,omitempty"` + Size uint `json:"size"` //size is only returned and setting it has no effect + WorldWideName QemuWorldWideName `json:"wwn"` +} + +func (passthrough *QemuScsiPassthrough) convertDataStructure() *qemuDisk { + return &qemuDisk{ + AsyncIO: passthrough.AsyncIO, + Backup: passthrough.Backup, + Bandwidth: passthrough.Bandwidth, + Cache: passthrough.Cache, + Discard: passthrough.Discard, + EmulateSSD: passthrough.EmulateSSD, + File: passthrough.File, + IOThread: passthrough.IOThread, + ReadOnly: passthrough.ReadOnly, + Replicate: passthrough.Replicate, + Serial: passthrough.Serial, + Type: scsi, + WorldWideName: passthrough.WorldWideName, + } +} + +func (passthrough QemuScsiPassthrough) Validate() error { + return passthrough.convertDataStructure().validate() +} + +type QemuScsiStorage struct { + CdRom *QemuCdRom `json:"cdrom,omitempty"` + CloudInit *QemuCloudInitDisk `json:"cloudinit,omitempty"` + Disk *QemuScsiDisk `json:"disk,omitempty"` + Passthrough *QemuScsiPassthrough `json:"passthrough,omitempty"` +} + +// converts to qemuStorage +func (storage *QemuScsiStorage) convertDataStructure() *qemuStorage { + if storage == nil { + return nil + } + generalizedStorage := qemuStorage{ + CdRom: storage.CdRom, + CloudInit: storage.CloudInit, + } + if storage.Disk != nil { + generalizedStorage.Disk = storage.Disk.convertDataStructure() + } + if storage.Passthrough != nil { + generalizedStorage.Passthrough = storage.Passthrough.convertDataStructure() + } + return &generalizedStorage +} + +// converts to qemuDiskMark +func (storage *QemuScsiStorage) convertDataStructureMark() *qemuDiskMark { + if storage == nil { + return nil + } + if storage.Disk != nil { + return &qemuDiskMark{ + Format: storage.Disk.Format, + Size: storage.Disk.Size, + Storage: storage.Disk.Storage, + Type: ide, + } + } + return nil +} + +func (QemuScsiStorage) mapToStruct(param string, LinkedVmId *uint) *QemuScsiStorage { + diskData, _, _ := strings.Cut(param, ",") + settings := splitStringOfSettings(param) + tmpCdRom := qemuCdRom{}.mapToStruct(diskData, settings) + if tmpCdRom != nil { + if tmpCdRom.CdRom { + return &QemuScsiStorage{CdRom: QemuCdRom{}.mapToStruct(*tmpCdRom)} + } else { + return &QemuScsiStorage{CloudInit: QemuCloudInitDisk{}.mapToStruct(*tmpCdRom)} + } + } + + tmpDisk := qemuDisk{}.mapToStruct(diskData, settings, LinkedVmId) + if tmpDisk == nil { + return nil + } + if tmpDisk.File == "" { + return &QemuScsiStorage{Disk: &QemuScsiDisk{ + AsyncIO: tmpDisk.AsyncIO, + Backup: tmpDisk.Backup, + Bandwidth: tmpDisk.Bandwidth, + Cache: tmpDisk.Cache, + Discard: tmpDisk.Discard, + EmulateSSD: tmpDisk.EmulateSSD, + Format: tmpDisk.Format, + Id: tmpDisk.Id, + IOThread: tmpDisk.IOThread, + LinkedDiskId: tmpDisk.LinkedDiskId, + ReadOnly: tmpDisk.ReadOnly, + Replicate: tmpDisk.Replicate, + Serial: tmpDisk.Serial, + Size: tmpDisk.Size, + Storage: tmpDisk.Storage, + WorldWideName: tmpDisk.WorldWideName, + }} + } + return &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + AsyncIO: tmpDisk.AsyncIO, + Backup: tmpDisk.Backup, + Bandwidth: tmpDisk.Bandwidth, + Cache: tmpDisk.Cache, + Discard: tmpDisk.Discard, + EmulateSSD: tmpDisk.EmulateSSD, + File: tmpDisk.File, + IOThread: tmpDisk.IOThread, + ReadOnly: tmpDisk.ReadOnly, + Replicate: tmpDisk.Replicate, + Serial: tmpDisk.Serial, + Size: tmpDisk.Size, + WorldWideName: tmpDisk.WorldWideName, + }} +} + +func (storage QemuScsiStorage) Validate() (err error) { + _, err = storage.validate() + return +} + +func (storage QemuScsiStorage) validate() (CloudInit uint8, err error) { + // First check if more than one item is nil + var subTypeSet bool + if storage.CdRom != nil { + subTypeSet = true + } + if storage.CloudInit != nil { + if err = diskSubtypeSet(subTypeSet); err != nil { + return + } + subTypeSet = true + CloudInit = 1 + } + if storage.Disk != nil { + if err = diskSubtypeSet(subTypeSet); err != nil { + return + } + subTypeSet = true + } + if storage.Passthrough != nil { + if err = diskSubtypeSet(subTypeSet); err != nil { + return + } + } + // Validate sub items + if storage.CdRom != nil { + if err = storage.CdRom.Validate(); err != nil { + return + } + } + if storage.CloudInit != nil { + if err = storage.CloudInit.Validate(); err != nil { + return + } + } + if storage.Disk != nil { + if err = storage.Disk.Validate(); err != nil { + return + } + } + if storage.Passthrough != nil { + err = storage.Passthrough.Validate() + } + return +} diff --git a/proxmox/config_qemu_disk_test.go b/proxmox/config_qemu_disk_test.go new file mode 100644 index 00000000..4080e082 --- /dev/null +++ b/proxmox/config_qemu_disk_test.go @@ -0,0 +1,860 @@ +package proxmox + +import ( + "errors" + "fmt" + "testing" + + "github.com/Telmate/proxmox-api-go/test/data/test_data_qemu" + "github.com/stretchr/testify/require" +) + +func Test_IsoFile_Validate(t *testing.T) { + testData := []struct { + name string + input IsoFile + err error + }{ + // Valid + {name: "Valid 00", input: IsoFile{File: "anything", Storage: "something"}}, + // Invalid + {name: "Invalid 00", input: IsoFile{}, err: errors.New(Error_IsoFile_File)}, + {name: "Invalid 01", input: IsoFile{File: "anything"}, err: errors.New(Error_IsoFile_Storage)}, + {name: "Invalid 02", input: IsoFile{Storage: "something"}, err: errors.New(Error_IsoFile_File)}, + {name: "Invalid 03", input: IsoFile{Size: "something"}, err: errors.New(Error_IsoFile_File)}, + } + for _, test := range testData { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_QemuCdRom_Validate(t *testing.T) { + testData := []struct { + name string + input QemuCdRom + err error + }{ + // Valid + {name: "Valid 00", input: QemuCdRom{}}, + {name: "Valid 01", input: QemuCdRom{Iso: &IsoFile{File: "anything", Storage: "Something"}}}, + {name: "Valid 02", input: QemuCdRom{Passthrough: true}}, + // Invalid + {name: "Invalid 00", input: QemuCdRom{Iso: &IsoFile{}}, err: errors.New(Error_IsoFile_File)}, + {name: "Invalid 01", input: QemuCdRom{Iso: &IsoFile{File: "anything"}}, err: errors.New(Error_IsoFile_Storage)}, + {name: "Invalid 02", input: QemuCdRom{Iso: &IsoFile{Storage: "something"}}, err: errors.New(Error_IsoFile_File)}, + {name: "Invalid 03", input: QemuCdRom{Iso: &IsoFile{Size: "something"}}, err: errors.New(Error_IsoFile_File)}, + {name: "Invalid 04", input: QemuCdRom{Iso: &IsoFile{File: "anything", Storage: "something"}, Passthrough: true}, err: errors.New(Error_QemuCdRom_MutuallyExclusive)}, + } + for _, test := range testData { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_QemuCloudInitDisk_Validate(t *testing.T) { + testData := []struct { + name string + input QemuCloudInitDisk + err error + }{ + // Valid + {name: "Valid 00", input: QemuCloudInitDisk{Storage: "anything", Format: QemuDiskFormat_Raw}}, + // Invalid + {name: "Invalid 00", input: QemuCloudInitDisk{}, err: QemuDiskFormat("").Error()}, + {name: "Invalid 01", input: QemuCloudInitDisk{Format: QemuDiskFormat_Raw}, err: errors.New(Error_QemuCloudInitDisk_Storage)}, + {name: "Invalid 02", input: QemuCloudInitDisk{Storage: "anything", Format: QemuDiskFormat("")}, err: QemuDiskFormat("").Error()}, + {name: "Invalid 03", input: QemuCloudInitDisk{Storage: "anything", Format: QemuDiskFormat("invalid")}, err: QemuDiskFormat("").Error()}, + } + for _, test := range testData { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_qemuDisk_mapToStruct(t *testing.T) { + tests := []struct { + name string + input string + linkedVmId uint + output uint + }{ + {name: "Don't Update LinkedVmId", + input: "storage:100/vm-100-disk-0.qcow2", + linkedVmId: 110, + output: 110, + }, + {name: "Update LinkedVmId", + input: "storage:110/base-110-disk-1.qcow2/100/vm-100-disk-0.qcow2", + output: 110, + }, + } + for _, test := range tests { + t.Run(test.name, func(*testing.T) { + linkedVmId := uint(test.linkedVmId) + qemuDisk{}.mapToStruct(test.input, nil, &linkedVmId) + require.Equal(t, test.output, linkedVmId, test.name) + }) + } +} + +func Test_QemuDiskAsyncIO_Validate(t *testing.T) { + testData := []struct { + name string + input QemuDiskAsyncIO + err error + }{ + // Valid + {name: "Valid 00", input: ""}, + {name: "Valid 01", input: QemuDiskAsyncIO_Native}, + {name: "Valid 02", input: QemuDiskAsyncIO_Threads}, + {name: "Valid 03", input: QemuDiskAsyncIO_IOuring}, + // Invalid + {name: "Invalid 00", input: "bla", err: QemuDiskAsyncIO("").Error()}, + {name: "Invalid 01", input: "invalid value", err: QemuDiskAsyncIO("").Error()}, + {name: "Invalid 02", input: "!@#$", err: QemuDiskAsyncIO("").Error()}, + } + for _, test := range testData { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_QemuDiskBandwidth_Validate(t *testing.T) { + testData := []struct { + name string + input QemuDiskBandwidth + err error + }{ + // Valid + {name: "Valid 00", input: QemuDiskBandwidth{}}, + {name: "Valid 01", input: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{}}}, + {name: "Valid 02", input: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{}}}}, + {name: "Valid 03", input: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 0}}}}, + {name: "Valid 04", input: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 1}}}}, + {name: "Valid 05", input: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0}}}}, + {name: "Valid 06", input: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 1}}}}, + {name: "Valid 07", input: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{}}}}, + {name: "Valid 08", input: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 0}}}}, + {name: "Valid 09", input: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 1}}}}, + {name: "Valid 10", input: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0}}}}, + {name: "Valid 11", input: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 1}}}}, + {name: "Valid 12", input: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{}}}, + {name: "Valid 13", input: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{}}}}, + {name: "Valid 14", input: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 0}}}}, + {name: "Valid 15", input: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 10}}}}, + {name: "Valid 16", input: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 0}}}}, + {name: "Valid 17", input: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 10}}}}, + {name: "Valid 18", input: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{}}}}, + {name: "Valid 19", input: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 0}}}}, + {name: "Valid 20", input: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 10}}}}, + {name: "Valid 21", input: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 0}}}}, + {name: "Valid 22", input: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 10}}}}, + // Invalid + {name: "Invalid 00", input: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}, err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst)}, + {name: "Invalid 01", input: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}, err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent)}, + {name: "Invalid 02", input: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}, err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst)}, + {name: "Invalid 03", input: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}, err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent)}, + {name: "Invalid 04", input: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 9}}}, err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst)}, + {name: "Invalid 05", input: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 9}}}, err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent)}, + {name: "Invalid 06", input: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 9}}}, err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst)}, + {name: "Invalid 07", input: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 9}}}, err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent)}, + } + for _, test := range testData { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_QemuDiskBandwidthIops_Validate(t *testing.T) { + testData := []struct { + name string + input QemuDiskBandwidthIops + err error + }{ + // Valid + {name: "Valid 00", input: QemuDiskBandwidthIops{}}, + {name: "Valid 01", input: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{}}}, + {name: "Valid 02", input: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 10}}}, + {name: "Valid 03", input: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 10}}}, + {name: "Valid 04", input: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 0}}}, + {name: "Valid 05", input: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 10}}}, + {name: "Valid 06", input: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{}}}, + {name: "Valid 07", input: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 0}}}, + {name: "Valid 08", input: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 10}}}, + {name: "Valid 09", input: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 0}}}, + {name: "Valid 10", input: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 10}}}, + // Invalid + {name: "Invalid 00", input: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 9}}, err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst)}, + {name: "Invalid 01", input: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 9}}, err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent)}, + {name: "Invalid 02", input: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 9}}, err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst)}, + {name: "Invalid 03", input: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 9}}, err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent)}, + } + for _, test := range testData { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_QemuDiskBandwidthIopsLimit_Validate(t *testing.T) { + testData := []struct { + name string + input QemuDiskBandwidthIopsLimit + err error + }{ + // Valid + {name: "Valid 00", input: QemuDiskBandwidthIopsLimit{}}, + {name: "Valid 01", input: QemuDiskBandwidthIopsLimit{Burst: 0}}, + {name: "Valid 02", input: QemuDiskBandwidthIopsLimit{Burst: 10}}, + {name: "Valid 03", input: QemuDiskBandwidthIopsLimit{Concurrent: 0}}, + {name: "Valid 04", input: QemuDiskBandwidthIopsLimit{Concurrent: 10}}, + // Invalid + {name: "Invalid 00", input: QemuDiskBandwidthIopsLimit{Burst: 9}, err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst)}, + {name: "Invalid 01", input: QemuDiskBandwidthIopsLimit{Concurrent: 9}, err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent)}, + } + for _, test := range testData { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_QemuDiskBandwidthIopsLimitBurst_Validate(t *testing.T) { + testData := []struct { + name string + input QemuDiskBandwidthIopsLimitBurst + err error + }{ + // Valid + {name: "Valid 03", input: 0}, + {name: "Valid 04", input: 10}, + // Invalid + {name: "Invalid 01", input: 9, err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst)}, + } + for _, test := range testData { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_QemuDiskBandwidthIopsLimitConcurrent_Validate(t *testing.T) { + testData := []struct { + name string + input QemuDiskBandwidthIopsLimitConcurrent + err error + }{ + // Valid + {name: "Valid 03", input: 0}, + {name: "Valid 04", input: 10}, + // Invalid + {name: "Invalid 01", input: 9, err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent)}, + } + for _, test := range testData { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_QemuDiskBandwidthMBps_Validate(t *testing.T) { + testData := []struct { + name string + input QemuDiskBandwidthMBps + err error + }{ + // Valid + {name: "Valid 00", input: QemuDiskBandwidthMBps{}}, + {name: "Valid 01", input: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{}}}, + {name: "Valid 02", input: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 0}}}, + {name: "Valid 03", input: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 1}}}, + {name: "Valid 04", input: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0}}}, + {name: "Valid 05", input: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 1}}}, + {name: "Valid 06", input: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{}}}, + {name: "Valid 07", input: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 0}}}, + {name: "Valid 08", input: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 1}}}, + {name: "Valid 09", input: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0}}}, + {name: "Valid 10", input: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 1}}}, + // Invalid + {name: "Invalid 00", input: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}, err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst)}, + {name: "Invalid 01", input: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}, err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent)}, + {name: "Invalid 02", input: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}, err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst)}, + {name: "Invalid 03", input: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}, err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent)}, + } + for _, test := range testData { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_QemuDiskBandwidthMBpsLimit_Validate(t *testing.T) { + testData := []struct { + name string + input QemuDiskBandwidthMBpsLimit + err error + }{ + // Valid + {name: "Valid 00", input: QemuDiskBandwidthMBpsLimit{}}, + {name: "Valid 01", input: QemuDiskBandwidthMBpsLimit{Burst: 0}}, + {name: "Valid 02", input: QemuDiskBandwidthMBpsLimit{Burst: 1}}, + {name: "Valid 03", input: QemuDiskBandwidthMBpsLimit{Concurrent: 0}}, + {name: "Valid 04", input: QemuDiskBandwidthMBpsLimit{Concurrent: 1}}, + // Invalid + {name: "Invalid 00", input: QemuDiskBandwidthMBpsLimit{Burst: 0.99}, err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst)}, + {name: "Invalid 01", input: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}, err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent)}, + } + for _, test := range testData { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_QemuDiskBandwidthMBpsLimitBurst_Validate(t *testing.T) { + testData := []struct { + name string + input QemuDiskBandwidthMBpsLimitBurst + err error + }{ + // Valid + {name: "Valid 01", input: 0}, + {name: "Valid 02", input: 1}, + // Invalid + {name: "Invalid 00", input: 0.99, err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst)}, + } + for _, test := range testData { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_QemuDiskBandwidthMBpsLimitConcurrent_Validate(t *testing.T) { + testData := []struct { + name string + input QemuDiskBandwidthMBpsLimitConcurrent + err error + }{ + // Valid + {name: "Valid 01", input: 0}, + {name: "Valid 02", input: 1}, + // Invalid + {name: "Invalid 00", input: 0.99, err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent)}, + } + for _, test := range testData { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_QemuDiskCache_Validate(t *testing.T) { + testData := []struct { + name string + input QemuDiskCache + err error + }{ + // Valid + {name: "Valid 00", input: ""}, + {name: "Valid 01", input: QemuDiskCache_None}, + {name: "Valid 02", input: QemuDiskCache_WriteThrough}, + {name: "Valid 03", input: QemuDiskCache_WriteBack}, + {name: "Valid 04", input: QemuDiskCache_Unsafe}, + {name: "Valid 05", input: QemuDiskCache_DirectSync}, + // Invalid + {name: "Invalid 00", input: "bla", err: QemuDiskCache("").Error()}, + {name: "Invalid 01", input: "invalid value", err: QemuDiskCache("").Error()}, + {name: "Invalid 02", input: "!@#$", err: QemuDiskCache("").Error()}, + } + for _, test := range testData { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_QemuDiskFormat_Validate(t *testing.T) { + tests := []struct { + name string + input QemuDiskFormat + err error + }{ + // Valid + {name: "Valid 00", input: QemuDiskFormat_Cow}, + {name: "Valid 01", input: QemuDiskFormat_Cloop}, + {name: "Valid 02", input: QemuDiskFormat_Qcow}, + {name: "Valid 03", input: QemuDiskFormat_Qcow2}, + {name: "Valid 04", input: QemuDiskFormat_Qed}, + {name: "Valid 05", input: QemuDiskFormat_Vmdk}, + {name: "Valid 06", input: QemuDiskFormat_Raw}, + // Invalid + {name: "Invalid 00", input: "bla", err: QemuDiskFormat("").Error()}, + {name: "Invalid 01", input: "invalid value", err: QemuDiskFormat("").Error()}, + {name: "Invalid 02", input: "!@#$", err: QemuDiskFormat("").Error()}, + } + for _, test := range tests { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_QemuDiskId_Validate(t *testing.T) { + testData := []struct { + name string + input QemuDiskId + err error + }{ + // Invalid + {name: "Invalid 00", input: "ide4", err: errors.New(ERROR_QemuDiskId_Invalid)}, + {name: "Invalid 01", input: "ide01", err: errors.New(ERROR_QemuDiskId_Invalid)}, + {name: "Invalid 02", input: "ide10", err: errors.New(ERROR_QemuDiskId_Invalid)}, + {name: "Invalid 03", input: "sata6", err: errors.New(ERROR_QemuDiskId_Invalid)}, + {name: "Invalid 04", input: "sata01", err: errors.New(ERROR_QemuDiskId_Invalid)}, + {name: "Invalid 05", input: "sata10", err: errors.New(ERROR_QemuDiskId_Invalid)}, + {name: "Invalid 06", input: "scsi31", err: errors.New(ERROR_QemuDiskId_Invalid)}, + {name: "Invalid 07", input: "scsi01", err: errors.New(ERROR_QemuDiskId_Invalid)}, + {name: "Invalid 08", input: "scsi100", err: errors.New(ERROR_QemuDiskId_Invalid)}, + {name: "Invalid 09", input: "virtio16", err: errors.New(ERROR_QemuDiskId_Invalid)}, + {name: "Invalid 10", input: "virtio01", err: errors.New(ERROR_QemuDiskId_Invalid)}, + {name: "Invalid 11", input: "virtio100", err: errors.New(ERROR_QemuDiskId_Invalid)}, + {name: "Invalid 12", input: "bla", err: errors.New(ERROR_QemuDiskId_Invalid)}, + {name: "Invalid 13", input: "invalid value", err: errors.New(ERROR_QemuDiskId_Invalid)}, + {name: "Invalid 14", input: "!@#$", err: errors.New(ERROR_QemuDiskId_Invalid)}, + // Valid + {name: "Valid 01", input: "ide0"}, + {name: "Valid 02", input: "ide1"}, + {name: "Valid 03", input: "ide2"}, + {name: "Valid 04", input: "ide3"}, + {name: "Valid 05", input: "sata0"}, + {name: "Valid 06", input: "sata1"}, + {name: "Valid 07", input: "sata2"}, + {name: "Valid 08", input: "sata3"}, + {name: "Valid 09", input: "sata4"}, + {name: "Valid 10", input: "sata5"}, + {name: "Valid 11", input: "scsi0"}, + {name: "Valid 12", input: "scsi1"}, + {name: "Valid 13", input: "scsi2"}, + {name: "Valid 14", input: "scsi3"}, + {name: "Valid 15", input: "scsi4"}, + {name: "Valid 16", input: "scsi5"}, + {name: "Valid 17", input: "scsi6"}, + {name: "Valid 18", input: "scsi7"}, + {name: "Valid 19", input: "scsi8"}, + {name: "Valid 20", input: "scsi9"}, + {name: "Valid 21", input: "scsi10"}, + {name: "Valid 22", input: "scsi11"}, + {name: "Valid 23", input: "scsi12"}, + {name: "Valid 24", input: "scsi13"}, + {name: "Valid 25", input: "scsi14"}, + {name: "Valid 26", input: "scsi15"}, + {name: "Valid 27", input: "scsi16"}, + {name: "Valid 28", input: "scsi17"}, + {name: "Valid 29", input: "scsi18"}, + {name: "Valid 30", input: "scsi19"}, + {name: "Valid 31", input: "scsi20"}, + {name: "Valid 32", input: "scsi21"}, + {name: "Valid 33", input: "scsi22"}, + {name: "Valid 34", input: "scsi23"}, + {name: "Valid 35", input: "scsi24"}, + {name: "Valid 36", input: "scsi25"}, + {name: "Valid 37", input: "scsi26"}, + {name: "Valid 38", input: "scsi27"}, + {name: "Valid 39", input: "scsi28"}, + {name: "Valid 40", input: "scsi29"}, + {name: "Valid 41", input: "scsi30"}, + {name: "Valid 42", input: "virtio0"}, + {name: "Valid 43", input: "virtio1"}, + {name: "Valid 44", input: "virtio2"}, + {name: "Valid 45", input: "virtio3"}, + {name: "Valid 46", input: "virtio4"}, + {name: "Valid 47", input: "virtio5"}, + {name: "Valid 48", input: "virtio6"}, + {name: "Valid 49", input: "virtio7"}, + {name: "Valid 50", input: "virtio8"}, + {name: "Valid 51", input: "virtio9"}, + {name: "Valid 52", input: "virtio10"}, + {name: "Valid 53", input: "virtio11"}, + {name: "Valid 54", input: "virtio12"}, + {name: "Valid 55", input: "virtio13"}, + {name: "Valid 56", input: "virtio14"}, + {name: "Valid 57", input: "virtio15"}, + } + for _, test := range testData { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_QemuDiskSerial_Validate(t *testing.T) { + testRunes := struct { + legal []string + illegal []string + }{ + legal: test_data_qemu.QemuDiskSerial_Legal(), + illegal: test_data_qemu.QemuDiskSerial_Illegal(), + } + for i, test := range testRunes.legal { + name := fmt.Sprintf("legal%03d", i) + t.Run(name, func(*testing.T) { + require.NoError(t, QemuDiskSerial(test).Validate(), name) + }) + } + for i, test := range testRunes.illegal { + name := fmt.Sprintf("illegal%03d", i) + t.Run(name, func(*testing.T) { + require.Error(t, QemuDiskSerial(test).Validate(), name) + }) + } +} + +func Test_qemuDiskShort_mapToApiValues(t *testing.T) { + format_Raw := QemuDiskFormat_Raw + format_Qcow2 := QemuDiskFormat_Qcow2 + tests := []struct { + name string + delete bool + input qemuDiskMove + output map[string]interface{} + }{ + {name: "ALL", + delete: true, + input: qemuDiskMove{ + Format: &format_Raw, + Id: "ide0", + Storage: "test0", + }, + output: map[string]interface{}{ + "disk": "ide0", + "storage": "test0", + "delete": "1", + "format": "raw", + }, + }, + {name: "Format nil", + delete: true, + input: qemuDiskMove{ + Id: "sata4", + Storage: "aaa0", + }, + output: map[string]interface{}{ + "disk": "sata4", + "storage": "aaa0", + "delete": "1", + }, + }, + {name: "Delete false", + input: qemuDiskMove{ + Format: &format_Qcow2, + Id: "scsi10", + Storage: "test0", + }, + output: map[string]interface{}{ + "format": "qcow2", + "disk": "scsi10", + "storage": "test0", + }, + }, + {name: "MINIMAL", + input: qemuDiskMove{ + Id: "virtio13", + Storage: "Test0", + }, + output: map[string]interface{}{ + "disk": "virtio13", + "storage": "Test0", + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(*testing.T) { + require.Equal(t, test.output, test.input.mapToApiValues(test.delete), test.name) + }) + } +} + +func Test_qemuDiskShort_Validate(t *testing.T) { + format_Raw := QemuDiskFormat_Raw + format_Invalid := QemuDiskFormat("invalid") + format_Empty := QemuDiskFormat("") + tests := []struct { + name string + input qemuDiskMove + err error + }{ + // TODO Add cases when Storage has a custom type + // Invalid + {name: "Invalid 00", input: qemuDiskMove{Format: &format_Invalid}, + err: QemuDiskFormat("").Error(), + }, + {name: "Invalid 01", input: qemuDiskMove{Format: &format_Empty}, + err: QemuDiskFormat("").Error(), + }, + {name: "Invalid 02", input: qemuDiskMove{Id: "invalid"}, + err: errors.New(ERROR_QemuDiskId_Invalid), + }, + // Valid + {name: "Valid 00", input: qemuDiskMove{ + Format: &format_Raw, + Id: "ide0", + }}, + {name: "Valid 01", input: qemuDiskMove{ + Id: "ide0", + }}, + } + for _, test := range tests { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +func Test_QemuStorages_markDiskChanges(t *testing.T) { + format_Raw := QemuDiskFormat_Raw + tests := []struct { + name string + storages QemuStorages + currentStorages QemuStorages + output *qemuUpdateChanges + }{ + {name: "Disk CHANGE", + storages: QemuStorages{ + Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{Format: format_Raw, Size: 100, Storage: "NewStorage"}}}, + Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{Format: format_Raw, Size: 50, Storage: "NewStorage"}}}, + Scsi: &QemuScsiDisks{Disk_2: &QemuScsiStorage{Disk: &QemuScsiDisk{Format: format_Raw, Size: 33, Storage: "NewStorage"}}}, + VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: format_Raw, Size: 99, Storage: "NewStorage"}}}, + }, + currentStorages: QemuStorages{ + Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{Format: QemuDiskFormat_Vmdk, Size: 32, Storage: "Test"}}}, + Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{Format: QemuDiskFormat_Vmdk, Size: 32, Storage: "Test"}}}, + Scsi: &QemuScsiDisks{Disk_2: &QemuScsiStorage{Disk: &QemuScsiDisk{Format: QemuDiskFormat_Vmdk, Size: 32, Storage: "Test"}}}, + VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: QemuDiskFormat_Vmdk, Size: 32, Storage: "Test"}}}, + }, + output: &qemuUpdateChanges{ + Move: []qemuDiskMove{ + {Format: &format_Raw, Id: "ide0", Storage: "NewStorage"}, + {Format: &format_Raw, Id: "sata1", Storage: "NewStorage"}, + {Format: &format_Raw, Id: "scsi2", Storage: "NewStorage"}, + {Format: &format_Raw, Id: "virtio3", Storage: "NewStorage"}, + }, + Resize: []qemuDiskResize{ + {Id: "ide0", SizeInGigaBytes: 100}, + {Id: "sata1", SizeInGigaBytes: 50}, + {Id: "scsi2", SizeInGigaBytes: 33}, + {Id: "virtio3", SizeInGigaBytes: 99}, + }, + }, + }, + {name: "Disk NO CHANGE", + storages: QemuStorages{ + Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{Format: format_Raw, Size: 100, Storage: "NewStorage"}}}, + Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{Format: format_Raw, Size: 50, Storage: "NewStorage"}}}, + Scsi: &QemuScsiDisks{Disk_2: &QemuScsiStorage{Disk: &QemuScsiDisk{Format: format_Raw, Size: 33, Storage: "NewStorage"}}}, + VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: format_Raw, Size: 99, Storage: "NewStorage"}}}, + }, + currentStorages: QemuStorages{ + Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{Format: QemuDiskFormat_Vmdk, Size: 32, Storage: "Test"}}}, + Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Disk: &QemuSataDisk{Format: QemuDiskFormat_Vmdk, Size: 32, Storage: "Test"}}}, + Scsi: &QemuScsiDisks{Disk_6: &QemuScsiStorage{Disk: &QemuScsiDisk{Format: QemuDiskFormat_Vmdk, Size: 32, Storage: "Test"}}}, + VirtIO: &QemuVirtIODisks{Disk_8: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: QemuDiskFormat_Vmdk, Size: 32, Storage: "Test"}}}, + }, + output: &qemuUpdateChanges{}, + }, + {name: "Disk_X.Disk SAME", + storages: QemuStorages{ + Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Disk: &QemuSataDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + Scsi: &QemuScsiDisks{Disk_3: &QemuScsiStorage{Disk: &QemuScsiDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + VirtIO: &QemuVirtIODisks{Disk_4: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + }, + currentStorages: QemuStorages{ + Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Disk: &QemuSataDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + Scsi: &QemuScsiDisks{Disk_3: &QemuScsiStorage{Disk: &QemuScsiDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + VirtIO: &QemuVirtIODisks{Disk_4: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + }, + output: &qemuUpdateChanges{}, + }, + {name: "Disk_X.Disk.Format CHANGE", + storages: QemuStorages{ + Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + Scsi: &QemuScsiDisks{Disk_4: &QemuScsiStorage{Disk: &QemuScsiDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + }, + currentStorages: QemuStorages{ + Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{Format: QemuDiskFormat_Vmdk, Size: 32, Storage: "Test"}}}, + Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{Format: QemuDiskFormat_Vmdk, Size: 32, Storage: "Test"}}}, + Scsi: &QemuScsiDisks{Disk_4: &QemuScsiStorage{Disk: &QemuScsiDisk{Format: QemuDiskFormat_Vmdk, Size: 32, Storage: "Test"}}}, + VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: QemuDiskFormat_Vmdk, Size: 32, Storage: "Test"}}}, + }, + output: &qemuUpdateChanges{Move: []qemuDiskMove{ + {Format: &format_Raw, Id: "ide2", Storage: "Test"}, + {Format: &format_Raw, Id: "sata3", Storage: "Test"}, + {Format: &format_Raw, Id: "scsi4", Storage: "Test"}, + {Format: &format_Raw, Id: "virtio5", Storage: "Test"}, + }}, + }, + {name: "Disk.Size BIGGER", + storages: QemuStorages{ + Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{Format: QemuDiskFormat_Raw, Size: 90, Storage: "Test"}}}, + Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Disk: &QemuSataDisk{Format: QemuDiskFormat_Raw, Size: 80, Storage: "Test"}}}, + Scsi: &QemuScsiDisks{Disk_5: &QemuScsiStorage{Disk: &QemuScsiDisk{Format: QemuDiskFormat_Raw, Size: 50, Storage: "Test"}}}, + VirtIO: &QemuVirtIODisks{Disk_6: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: QemuDiskFormat_Raw, Size: 33, Storage: "Test"}}}, + }, + currentStorages: QemuStorages{ + Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Disk: &QemuSataDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + Scsi: &QemuScsiDisks{Disk_5: &QemuScsiStorage{Disk: &QemuScsiDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + VirtIO: &QemuVirtIODisks{Disk_6: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + }, + output: &qemuUpdateChanges{Resize: []qemuDiskResize{ + {Id: "ide3", SizeInGigaBytes: 90}, + {Id: "sata4", SizeInGigaBytes: 80}, + {Id: "scsi5", SizeInGigaBytes: 50}, + {Id: "virtio6", SizeInGigaBytes: 33}, + }}, + }, + {name: "Disk.Size SMALLER", + storages: QemuStorages{ + Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{Format: QemuDiskFormat_Raw, Size: 1, Storage: "Test"}}}, + Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Disk: &QemuSataDisk{Format: QemuDiskFormat_Raw, Size: 10, Storage: "Test"}}}, + Scsi: &QemuScsiDisks{Disk_6: &QemuScsiStorage{Disk: &QemuScsiDisk{Format: QemuDiskFormat_Raw, Size: 20, Storage: "Test"}}}, + VirtIO: &QemuVirtIODisks{Disk_7: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: QemuDiskFormat_Raw, Size: 31, Storage: "Test"}}}, + }, + currentStorages: QemuStorages{ + Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Disk: &QemuSataDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + Scsi: &QemuScsiDisks{Disk_6: &QemuScsiStorage{Disk: &QemuScsiDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + VirtIO: &QemuVirtIODisks{Disk_7: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + }, + output: &qemuUpdateChanges{}, + }, + {name: "Disk.Storage CHANGE", + storages: QemuStorages{ + Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "NewStorage"}}}, + Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "NewStorage"}}}, + Scsi: &QemuScsiDisks{Disk_7: &QemuScsiStorage{Disk: &QemuScsiDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "NewStorage"}}}, + VirtIO: &QemuVirtIODisks{Disk_8: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "NewStorage"}}}, + }, + currentStorages: QemuStorages{ + Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + Scsi: &QemuScsiDisks{Disk_7: &QemuScsiStorage{Disk: &QemuScsiDisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + VirtIO: &QemuVirtIODisks{Disk_8: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: QemuDiskFormat_Raw, Size: 32, Storage: "Test"}}}, + }, + output: &qemuUpdateChanges{Move: []qemuDiskMove{ + {Id: "ide1", Storage: "NewStorage"}, + {Id: "sata0", Storage: "NewStorage"}, + {Id: "scsi7", Storage: "NewStorage"}, + {Id: "virtio8", Storage: "NewStorage"}, + }}, + }, + {name: "nil", + output: &qemuUpdateChanges{}, + }, + } + for _, test := range tests { + t.Run(test.name, func(*testing.T) { + require.Equal(t, test.output, test.storages.markDiskChanges(test.currentStorages), test.name) + }) + } +} + +func Test_QemuWorldWideName_Validate(t *testing.T) { + testRunes := struct { + legal []string + illegal []string + }{ + legal: test_data_qemu.QemuWorldWideName_Legal(), + illegal: test_data_qemu.QemuWorldWideName_Illegal(), + } + for _, test := range testRunes.legal { + name := "legal:" + test + t.Run(name, func(*testing.T) { + require.NoError(t, QemuWorldWideName(test).Validate(), name) + }) + } + for _, test := range testRunes.illegal { + name := "illegal:" + test + t.Run(name, func(*testing.T) { + require.Error(t, QemuWorldWideName(test).Validate(), name) + }) + } +} diff --git a/proxmox/config_qemu_disk_virtio.go b/proxmox/config_qemu_disk_virtio.go new file mode 100644 index 00000000..40973ca7 --- /dev/null +++ b/proxmox/config_qemu_disk_virtio.go @@ -0,0 +1,390 @@ +package proxmox + +import ( + "strconv" + "strings" +) + +type QemuVirtIODisk struct { + AsyncIO QemuDiskAsyncIO `json:"asyncio,omitempty"` + Backup bool `json:"backup"` + Bandwidth QemuDiskBandwidth `json:"bandwidth,omitempty"` + Cache QemuDiskCache `json:"cache,omitempty"` + Discard bool `json:"discard"` + Format QemuDiskFormat `json:"format"` + Id uint `json:"id"` //Id is only returned and setting it has no effect + IOThread bool `json:"iothread"` + LinkedDiskId *uint `json:"linked"` //LinkedCloneId is only returned and setting it has no effect + ReadOnly bool `json:"readonly"` + Replicate bool `json:"replicate"` + Serial QemuDiskSerial `json:"serial,omitempty"` + Size uint `json:"size"` + Storage string `json:"storage"` + WorldWideName QemuWorldWideName `json:"wwn"` +} + +func (disk *QemuVirtIODisk) convertDataStructure() *qemuDisk { + return &qemuDisk{ + AsyncIO: disk.AsyncIO, + Backup: disk.Backup, + Bandwidth: disk.Bandwidth, + Cache: disk.Cache, + Discard: disk.Discard, + Disk: true, + Format: disk.Format, + Id: disk.Id, + IOThread: disk.IOThread, + LinkedDiskId: disk.LinkedDiskId, + ReadOnly: disk.ReadOnly, + Replicate: disk.Replicate, + Serial: disk.Serial, + Size: disk.Size, + Storage: disk.Storage, + Type: virtIO, + WorldWideName: disk.WorldWideName, + } +} + +func (disk QemuVirtIODisk) Validate() error { + return disk.convertDataStructure().validate() +} + +type QemuVirtIODisks struct { + Disk_0 *QemuVirtIOStorage `json:"0,omitempty"` + Disk_1 *QemuVirtIOStorage `json:"1,omitempty"` + Disk_2 *QemuVirtIOStorage `json:"2,omitempty"` + Disk_3 *QemuVirtIOStorage `json:"3,omitempty"` + Disk_4 *QemuVirtIOStorage `json:"4,omitempty"` + Disk_5 *QemuVirtIOStorage `json:"5,omitempty"` + Disk_6 *QemuVirtIOStorage `json:"6,omitempty"` + Disk_7 *QemuVirtIOStorage `json:"7,omitempty"` + Disk_8 *QemuVirtIOStorage `json:"8,omitempty"` + Disk_9 *QemuVirtIOStorage `json:"9,omitempty"` + Disk_10 *QemuVirtIOStorage `json:"10,omitempty"` + Disk_11 *QemuVirtIOStorage `json:"11,omitempty"` + Disk_12 *QemuVirtIOStorage `json:"12,omitempty"` + Disk_13 *QemuVirtIOStorage `json:"13,omitempty"` + Disk_14 *QemuVirtIOStorage `json:"14,omitempty"` + Disk_15 *QemuVirtIOStorage `json:"15,omitempty"` +} + +func (disks QemuVirtIODisks) mapToApiValues(currentDisks *QemuVirtIODisks, vmID, linkedVmId uint, params map[string]interface{}, delete string) string { + tmpCurrentDisks := QemuVirtIODisks{} + if currentDisks != nil { + tmpCurrentDisks = *currentDisks + } + diskMap := disks.mapToIntMap() + currentDiskMap := tmpCurrentDisks.mapToIntMap() + for i := range diskMap { + delete = diskMap[i].convertDataStructure().mapToApiValues(currentDiskMap[i].convertDataStructure(), vmID, linkedVmId, QemuDiskId("virtio"+strconv.Itoa(int(i))), params, delete) + } + return delete +} + +func (disks QemuVirtIODisks) mapToIntMap() map[uint8]*QemuVirtIOStorage { + return map[uint8]*QemuVirtIOStorage{ + 0: disks.Disk_0, + 1: disks.Disk_1, + 2: disks.Disk_2, + 3: disks.Disk_3, + 4: disks.Disk_4, + 5: disks.Disk_5, + 6: disks.Disk_6, + 7: disks.Disk_7, + 8: disks.Disk_8, + 9: disks.Disk_9, + 10: disks.Disk_10, + 11: disks.Disk_11, + 12: disks.Disk_12, + 13: disks.Disk_13, + 14: disks.Disk_14, + 15: disks.Disk_15, + } +} + +func (QemuVirtIODisks) mapToStruct(params map[string]interface{}, linkedVmId *uint) *QemuVirtIODisks { + disks := QemuVirtIODisks{} + var structPopulated bool + if _, isSet := params["virtio0"]; isSet { + disks.Disk_0 = QemuVirtIOStorage{}.mapToStruct(params["virtio0"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["virtio1"]; isSet { + disks.Disk_1 = QemuVirtIOStorage{}.mapToStruct(params["virtio1"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["virtio2"]; isSet { + disks.Disk_2 = QemuVirtIOStorage{}.mapToStruct(params["virtio2"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["virtio3"]; isSet { + disks.Disk_3 = QemuVirtIOStorage{}.mapToStruct(params["virtio3"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["virtio4"]; isSet { + disks.Disk_4 = QemuVirtIOStorage{}.mapToStruct(params["virtio4"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["virtio5"]; isSet { + disks.Disk_5 = QemuVirtIOStorage{}.mapToStruct(params["virtio5"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["virtio6"]; isSet { + disks.Disk_6 = QemuVirtIOStorage{}.mapToStruct(params["virtio6"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["virtio7"]; isSet { + disks.Disk_7 = QemuVirtIOStorage{}.mapToStruct(params["virtio7"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["virtio8"]; isSet { + disks.Disk_8 = QemuVirtIOStorage{}.mapToStruct(params["virtio8"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["virtio9"]; isSet { + disks.Disk_9 = QemuVirtIOStorage{}.mapToStruct(params["virtio9"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["virtio10"]; isSet { + disks.Disk_10 = QemuVirtIOStorage{}.mapToStruct(params["virtio10"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["virtio11"]; isSet { + disks.Disk_11 = QemuVirtIOStorage{}.mapToStruct(params["virtio11"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["virtio12"]; isSet { + disks.Disk_12 = QemuVirtIOStorage{}.mapToStruct(params["virtio12"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["virtio13"]; isSet { + disks.Disk_13 = QemuVirtIOStorage{}.mapToStruct(params["virtio13"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["virtio14"]; isSet { + disks.Disk_14 = QemuVirtIOStorage{}.mapToStruct(params["virtio14"].(string), linkedVmId) + structPopulated = true + } + if _, isSet := params["virtio15"]; isSet { + disks.Disk_15 = QemuVirtIOStorage{}.mapToStruct(params["virtio15"].(string), linkedVmId) + structPopulated = true + } + if structPopulated { + return &disks + } + return nil +} + +func (disks QemuVirtIODisks) markDiskChanges(currentDisks *QemuVirtIODisks, changes *qemuUpdateChanges) { + tmpCurrentDisks := QemuVirtIODisks{} + if currentDisks != nil { + tmpCurrentDisks = *currentDisks + } + diskMap := disks.mapToIntMap() + currentDiskMap := tmpCurrentDisks.mapToIntMap() + for i := range diskMap { + diskMap[i].convertDataStructureMark().markChanges(currentDiskMap[i].convertDataStructureMark(), QemuDiskId("virtio"+strconv.Itoa(int(i))), changes) + } +} + +func (disks QemuVirtIODisks) Validate() (err error) { + _, err = disks.validate() + return +} + +func (disks QemuVirtIODisks) validate() (numberOfCloudInitDevices uint8, err error) { + diskMap := disks.mapToIntMap() + var cloudInit uint8 + for _, e := range diskMap { + if e != nil { + cloudInit, err = e.validate() + if err != nil { + return + } + numberOfCloudInitDevices += cloudInit + if err = (QemuCloudInitDisk{}.checkDuplicates(numberOfCloudInitDevices)); err != nil { + return + } + } + } + return +} + +type QemuVirtIOPassthrough struct { + AsyncIO QemuDiskAsyncIO `json:"asyncio,omitempty"` + Backup bool `json:"backup"` + Bandwidth QemuDiskBandwidth `json:"bandwidth,omitempty"` + Cache QemuDiskCache `json:"cache,omitempty"` + Discard bool `json:"discard"` + File string `json:"file"` + IOThread bool `json:"iothread"` + ReadOnly bool `json:"readonly"` + Replicate bool `json:"replicate"` + Serial QemuDiskSerial `json:"serial,omitempty"` + Size uint `json:"size"` //size is only returned and setting it has no effect + WorldWideName QemuWorldWideName `json:"wwn"` +} + +func (passthrough *QemuVirtIOPassthrough) convertDataStructure() *qemuDisk { + return &qemuDisk{ + AsyncIO: passthrough.AsyncIO, + Backup: passthrough.Backup, + Bandwidth: passthrough.Bandwidth, + Cache: passthrough.Cache, + Discard: passthrough.Discard, + File: passthrough.File, + IOThread: passthrough.IOThread, + ReadOnly: passthrough.ReadOnly, + Replicate: passthrough.Replicate, + Serial: passthrough.Serial, + Type: virtIO, + WorldWideName: passthrough.WorldWideName, + } +} + +func (passthrough QemuVirtIOPassthrough) Validate() error { + return passthrough.convertDataStructure().validate() +} + +type QemuVirtIOStorage struct { + CdRom *QemuCdRom `json:"cdrom,omitempty"` + CloudInit *QemuCloudInitDisk `json:"cloudinit,omitempty"` + Disk *QemuVirtIODisk `json:"disk,omitempty"` + Passthrough *QemuVirtIOPassthrough `json:"passthrough,omitempty"` +} + +// converts to qemuStorage +func (storage *QemuVirtIOStorage) convertDataStructure() *qemuStorage { + if storage == nil { + return nil + } + generalizedStorage := qemuStorage{ + CdRom: storage.CdRom, + CloudInit: storage.CloudInit, + } + if storage.Disk != nil { + generalizedStorage.Disk = storage.Disk.convertDataStructure() + } + if storage.Passthrough != nil { + generalizedStorage.Passthrough = storage.Passthrough.convertDataStructure() + } + return &generalizedStorage +} + +// converts to qemuDiskMark +func (storage *QemuVirtIOStorage) convertDataStructureMark() *qemuDiskMark { + if storage == nil { + return nil + } + if storage.Disk != nil { + return &qemuDiskMark{ + Format: storage.Disk.Format, + Size: storage.Disk.Size, + Storage: storage.Disk.Storage, + Type: ide, + } + } + return nil +} + +func (QemuVirtIOStorage) mapToStruct(param string, LinkedVmId *uint) *QemuVirtIOStorage { + diskData, _, _ := strings.Cut(param, ",") + settings := splitStringOfSettings(param) + tmpCdRom := qemuCdRom{}.mapToStruct(diskData, settings) + if tmpCdRom != nil { + if tmpCdRom.CdRom { + return &QemuVirtIOStorage{CdRom: QemuCdRom{}.mapToStruct(*tmpCdRom)} + } else { + return &QemuVirtIOStorage{CloudInit: QemuCloudInitDisk{}.mapToStruct(*tmpCdRom)} + } + } + + tmpDisk := qemuDisk{}.mapToStruct(diskData, settings, LinkedVmId) + if tmpDisk == nil { + return nil + } + if tmpDisk.File == "" { + return &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + AsyncIO: tmpDisk.AsyncIO, + Backup: tmpDisk.Backup, + Bandwidth: tmpDisk.Bandwidth, + Cache: tmpDisk.Cache, + Discard: tmpDisk.Discard, + Format: tmpDisk.Format, + Id: tmpDisk.Id, + IOThread: tmpDisk.IOThread, + LinkedDiskId: tmpDisk.LinkedDiskId, + ReadOnly: tmpDisk.ReadOnly, + Replicate: tmpDisk.Replicate, + Serial: tmpDisk.Serial, + Size: tmpDisk.Size, + Storage: tmpDisk.Storage, + WorldWideName: tmpDisk.WorldWideName, + }} + } + return &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + AsyncIO: tmpDisk.AsyncIO, + Backup: tmpDisk.Backup, + Bandwidth: tmpDisk.Bandwidth, + Cache: tmpDisk.Cache, + Discard: tmpDisk.Discard, + File: tmpDisk.File, + IOThread: tmpDisk.IOThread, + ReadOnly: tmpDisk.ReadOnly, + Replicate: tmpDisk.Replicate, + Serial: tmpDisk.Serial, + Size: tmpDisk.Size, + WorldWideName: tmpDisk.WorldWideName, + }} +} + +func (storage QemuVirtIOStorage) Validate() (err error) { + _, err = storage.validate() + return +} + +func (storage QemuVirtIOStorage) validate() (CloudInit uint8, err error) { + // First check if more than one item is nil + var subTypeSet bool + if storage.CdRom != nil { + subTypeSet = true + } + if storage.CloudInit != nil { + if err = diskSubtypeSet(subTypeSet); err != nil { + return + } + subTypeSet = true + CloudInit = 1 + } + if storage.Disk != nil { + if err = diskSubtypeSet(subTypeSet); err != nil { + return + } + subTypeSet = true + } + if storage.Passthrough != nil { + if err = diskSubtypeSet(subTypeSet); err != nil { + return + } + } + // Validate sub items + if storage.CdRom != nil { + if err = storage.CdRom.Validate(); err != nil { + return + } + } + if storage.CloudInit != nil { + if err = storage.CloudInit.Validate(); err != nil { + return + } + } + if storage.Disk != nil { + if err = storage.Disk.Validate(); err != nil { + return + } + } + if storage.Passthrough != nil { + err = storage.Passthrough.Validate() + } + return +} diff --git a/proxmox/config_qemu_test.go b/proxmox/config_qemu_test.go index 9ac99e58..a019cd9a 100644 --- a/proxmox/config_qemu_test.go +++ b/proxmox/config_qemu_test.go @@ -1,12 +1,5826 @@ package proxmox import ( + "errors" "strings" "testing" + "github.com/Telmate/proxmox-api-go/test/data/test_data_qemu" "github.com/stretchr/testify/require" ) +func Test_ConfigQemu_mapToApiValues(t *testing.T) { + format_Raw := QemuDiskFormat_Raw + float10 := QemuDiskBandwidthMBpsLimitConcurrent(10.3) + float45 := QemuDiskBandwidthMBpsLimitConcurrent(45.23) + float79 := QemuDiskBandwidthMBpsLimitBurst(79.23) + float99 := QemuDiskBandwidthMBpsLimitBurst(99.2) + uint1 := uint(1) + uint23 := QemuDiskBandwidthIopsLimitConcurrent(23) + uint34 := QemuDiskBandwidthIopsLimitConcurrent(34) + uint78 := QemuDiskBandwidthIopsLimitBurst(78) + uint89 := QemuDiskBandwidthIopsLimitBurst(89) + update_CloudInit := &QemuCloudInitDisk{Format: QemuDiskFormat_Raw, Storage: "test"} + ideBase := &QemuIdeStorage{Disk: &QemuIdeDisk{Format: QemuDiskFormat_Raw, Id: 23, Size: 10, Storage: "test"}} + sataBase := &QemuSataStorage{Disk: &QemuSataDisk{Format: QemuDiskFormat_Raw, Id: 23, Size: 10, Storage: "test"}} + scsiBase := &QemuScsiStorage{Disk: &QemuScsiDisk{Format: QemuDiskFormat_Raw, Id: 23, Size: 10, Storage: "test"}} + virtioBase := &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: QemuDiskFormat_Raw, Id: 23, Size: 10, Storage: "test"}} + tests := []struct { + name string + config *ConfigQemu + currentConfig ConfigQemu + reboot bool + vmr *VmRef + output map[string]interface{} + }{ + // Create Disks + + // Create Disks.Ide + {name: "Create Disks.Ide.Disk_X.CdRom none", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{CdRom: &QemuCdRom{}}}}}, + output: map[string]interface{}{"ide0": "none,media=cdrom"}, + }, + {name: "Create Disks.Ide.Disk_X.CdRom.Iso", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + output: map[string]interface{}{"ide1": "Test:iso/test.iso,media=cdrom"}, + }, + {name: "Create Disks.Ide.Disk_X.CdRom.Passthrough", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + output: map[string]interface{}{"ide2": "cdrom,media=cdrom"}, + }, + {name: "Create Disks.Ide.Disk_X.CloudInit", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{CloudInit: &QemuCloudInitDisk{Format: format_Raw, Storage: "Test"}}}}}, + output: map[string]interface{}{"ide1": "Test:cloudinit,format=raw"}, + }, + // Create Disks.Ide.Disk_X.Disk + {name: "Create Disks.Ide.Disk_X.Disk All", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{ + AsyncIO: QemuDiskAsyncIO_Native, + Backup: true, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: float99, Concurrent: float10}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: float79, Concurrent: float45}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78, BurstDuration: 3, Concurrent: uint34}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89, BurstDuration: 4, Concurrent: uint23}, + }, + }, + Cache: QemuDiskCache_DirectSync, + Discard: true, + EmulateSSD: true, + Format: format_Raw, + Replicate: true, + Serial: "558485ef-478", + Size: 32, + Storage: "Test", + WorldWideName: "0x5000D31000C9876F", + }}}}}, + output: map[string]interface{}{"ide0": "Test:32,aio=native,cache=directsync,discard=on,format=raw,iops_rd=34,iops_rd_max=78,iops_rd_max_length=3,iops_wr=23,iops_wr_max=89,iops_wr_max_length=4,mbps_rd=10.30,mbps_rd_max=99.20,mbps_wr=45.23,mbps_wr_max=79.23,serial=558485ef-478,ssd=1,wwn=0x5000D31000C9876F"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.AsyncIO", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{AsyncIO: QemuDiskAsyncIO_Native}}}}}, + output: map[string]interface{}{"ide1": ",aio=native,backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Backup", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{Backup: true}}}}}, + output: map[string]interface{}{"ide2": ",replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{}}}}}}, + output: map[string]interface{}{"ide3": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth.Iops", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{}}}}}}}, + output: map[string]interface{}{"ide3": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth.Iops.ReadLimit", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"ide0": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth.Iops.ReadLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78}}}}}}}}, + output: map[string]interface{}{"ide1": ",backup=0,iops_rd_max=78,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth.Iops.ReadLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 3}}}}}}}}, + output: map[string]interface{}{"ide1": ",backup=0,iops_rd_max_length=3,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth.Iops.ReadLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint34}}}}}}}}, + output: map[string]interface{}{"ide2": ",backup=0,iops_rd=34,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth.Iops.WriteLimit", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"ide3": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth.Iops.WriteLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89}}}}}}}}, + output: map[string]interface{}{"ide0": ",backup=0,iops_wr_max=89,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth.Iops.WriteLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 4}}}}}}}}, + output: map[string]interface{}{"ide0": ",backup=0,iops_wr_max_length=4,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth.Iops.WriteLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint23}}}}}}}}, + output: map[string]interface{}{"ide1": ",backup=0,iops_wr=23,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth.MBps", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{}}}}}}}, + output: map[string]interface{}{"ide0": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth.MBps.ReadLimit", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{}}}}}}}}, + output: map[string]interface{}{"ide1": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth.MBps.ReadLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: float99}}}}}}}}, + output: map[string]interface{}{"ide2": ",backup=0,mbps_rd_max=99.20,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth.MBps.ReadLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: float10}}}}}}}}, + output: map[string]interface{}{"ide3": ",backup=0,mbps_rd=10.30,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth.MBps.WriteLimit", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{}}}}}}}}, + output: map[string]interface{}{"ide0": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth.MBps.WriteLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: float79}}}}}}}}, + output: map[string]interface{}{"ide1": ",backup=0,mbps_wr_max=79.23,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Bandwidth.MBps.WriteLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: float45}}}}}}}}, + output: map[string]interface{}{"ide2": ",backup=0,mbps_wr=45.23,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Cache", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{Cache: QemuDiskCache_DirectSync}}}}}, + output: map[string]interface{}{"ide2": ",backup=0,cache=directsync,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Discard", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{Discard: true}}}}}, + output: map[string]interface{}{"ide3": ",backup=0,discard=on,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.EmulateSSD", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{EmulateSSD: true}}}}}, + output: map[string]interface{}{"ide0": ",backup=0,replicate=0,ssd=1"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Format", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{Format: format_Raw}}}}}, + output: map[string]interface{}{"ide1": ",backup=0,format=raw,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Replicate", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{Replicate: true}}}}}, + output: map[string]interface{}{"ide1": ",backup=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Serial", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{Serial: "558485ef-478"}}}}}, + output: map[string]interface{}{"ide2": ",backup=0,replicate=0,serial=558485ef-478"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Size", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{Size: 32}}}}}, + output: map[string]interface{}{"ide3": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.Storage", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{Storage: "Test"}}}}}, + output: map[string]interface{}{"ide0": "Test:0,backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Disk.WorldWideName", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{WorldWideName: "0x5001234000F876AB"}}}}}, + output: map[string]interface{}{"ide0": ",backup=0,replicate=0,wwn=0x5001234000F876AB"}, + }, + // Create Disks.Ide.Disk_X.Passthrough + {name: "Create Disks.Ide.Disk_X.Passthrough All", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + AsyncIO: QemuDiskAsyncIO_Threads, + Backup: true, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: float99, Concurrent: float10}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: float79, Concurrent: float45}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78, BurstDuration: 3, Concurrent: uint34}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89, BurstDuration: 4, Concurrent: uint23}, + }, + }, + Cache: QemuDiskCache_Unsafe, + Discard: true, + EmulateSSD: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + Serial: "test-serial_757465-gdg", + WorldWideName: "0x500CBA2000D76543", + }}}}}, + output: map[string]interface{}{"ide0": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,aio=threads,cache=unsafe,discard=on,iops_rd=34,iops_rd_max=78,iops_rd_max_length=3,iops_wr=23,iops_wr_max=89,iops_wr_max_length=4,mbps_rd=10.30,mbps_rd_max=99.20,mbps_wr=45.23,mbps_wr_max=79.23,serial=test-serial_757465-gdg,ssd=1,wwn=0x500CBA2000D76543"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.AsyncIO", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{AsyncIO: QemuDiskAsyncIO_Threads}}}}}, + output: map[string]interface{}{"ide1": ",aio=threads,backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Backup", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Backup: true}}}}}, + output: map[string]interface{}{"ide2": ",replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{}}}}}}, + output: map[string]interface{}{"ide3": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth.Iops", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{}}}}}}}, + output: map[string]interface{}{"ide3": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth.Iops.ReadLimit", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"ide0": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth.Iops.ReadLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78}}}}}}}}, + output: map[string]interface{}{"ide1": ",backup=0,iops_rd_max=78,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth.Iops.ReadLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 3}}}}}}}}, + output: map[string]interface{}{"ide1": ",backup=0,iops_rd_max_length=3,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth.Iops.ReadLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint34}}}}}}}}, + output: map[string]interface{}{"ide2": ",backup=0,iops_rd=34,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth.Iops.WriteLimit", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"ide3": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth.Iops.WriteLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89}}}}}}}}, + output: map[string]interface{}{"ide0": ",backup=0,iops_wr_max=89,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth.Iops.WriteLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 4}}}}}}}}, + output: map[string]interface{}{"ide0": ",backup=0,iops_wr_max_length=4,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth.Iops.WriteLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint23}}}}}}}}, + output: map[string]interface{}{"ide1": ",backup=0,iops_wr=23,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth.MBps", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{}}}}}}}, + output: map[string]interface{}{"ide0": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth.MBps.ReadLimit", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{}}}}}}}}, + output: map[string]interface{}{"ide1": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth.MBps.ReadLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: float99}}}}}}}}, + output: map[string]interface{}{"ide2": ",backup=0,mbps_rd_max=99.20,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth.MBps.ReadLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: float10}}}}}}}}, + output: map[string]interface{}{"ide3": ",backup=0,mbps_rd=10.30,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth.MBps.WriteLimit", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{}}}}}}}}, + output: map[string]interface{}{"ide0": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth.MBps.WriteLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: float79}}}}}}}}, + output: map[string]interface{}{"ide1": ",backup=0,mbps_wr_max=79.23,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Bandwidth.MBps.WriteLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: float79}}}}}}}}, + output: map[string]interface{}{"ide2": ",backup=0,mbps_wr_max=79.23,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Cache", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Cache: QemuDiskCache_Unsafe}}}}}, + output: map[string]interface{}{"ide2": ",backup=0,cache=unsafe,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Discard", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Discard: true}}}}}, + output: map[string]interface{}{"ide3": ",backup=0,discard=on,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.EmulateSSD", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{EmulateSSD: true}}}}}, + output: map[string]interface{}{"ide0": ",backup=0,replicate=0,ssd=1"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.File", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8"}}}}}, + output: map[string]interface{}{"ide1": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,backup=0,replicate=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.replicate", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Replicate: true}}}}}, + output: map[string]interface{}{"ide2": ",backup=0"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.Serial", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Serial: "test-serial_757465-gdg"}}}}}, + output: map[string]interface{}{"ide3": ",backup=0,replicate=0,serial=test-serial_757465-gdg"}, + }, + {name: "Create Disks.Ide.Disk_X.Passthrough.WorldWideName", + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{WorldWideName: "0x500FED1000B65432"}}}}}, + output: map[string]interface{}{"ide3": ",backup=0,replicate=0,wwn=0x500FED1000B65432"}, + }, + // Create Disks.Sata + {name: "Create Disks.Sata.Disk_X.Disk_X.CdRom none", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{CdRom: &QemuCdRom{}}}}}, + output: map[string]interface{}{"sata0": "none,media=cdrom"}, + }, + {name: "Create Disks.Sata.Disk_X.CdRom.Iso", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + output: map[string]interface{}{"sata1": "Test:iso/test.iso,media=cdrom"}, + }, + {name: "Create Disks.Sata.Disk_X.CdRom.Passthrough", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + output: map[string]interface{}{"sata2": "cdrom,media=cdrom"}, + }, + {name: "Create Disks.Sata.Disk_X.CloudInit", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{CloudInit: &QemuCloudInitDisk{Format: format_Raw, Storage: "Test"}}}}}, + output: map[string]interface{}{"sata1": "Test:cloudinit,format=raw"}, + }, + // Create Disks.Sata.Disk_X.Disk + {name: "Create Disks.Sata.Disk_X.Disk ALL", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{ + AsyncIO: QemuDiskAsyncIO_Native, + Backup: true, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: float99, Concurrent: float10}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: float79, Concurrent: float45}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78, BurstDuration: 3, Concurrent: uint34}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89, BurstDuration: 4, Concurrent: uint23}, + }, + }, + Cache: QemuDiskCache_Unsafe, + Discard: true, + EmulateSSD: true, + Format: QemuDiskFormat_Qcow2, + Replicate: true, + Serial: "ab_C-12_3", + Size: 32, + Storage: "Test", + WorldWideName: "0x5009876000A321DC", + }}}}}, + output: map[string]interface{}{"sata0": "Test:32,aio=native,cache=unsafe,discard=on,format=qcow2,iops_rd=34,iops_rd_max=78,iops_rd_max_length=3,iops_wr=23,iops_wr_max=89,iops_wr_max_length=4,mbps_rd=10.30,mbps_rd_max=99.20,mbps_wr=45.23,mbps_wr_max=79.23,serial=ab_C-12_3,ssd=1,wwn=0x5009876000A321DC"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.AsyncIO", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{AsyncIO: QemuDiskAsyncIO_Native}}}}}, + output: map[string]interface{}{"sata0": ",aio=native,backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Backup", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{Backup: true}}}}}, + output: map[string]interface{}{"sata1": ",replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{}}}}}}, + output: map[string]interface{}{"sata2": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth.Iops", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{}}}}}}}, + output: map[string]interface{}{"sata4": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth.Iops.ReadLimit", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"sata5": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth.Iops.ReadLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78}}}}}}}}, + output: map[string]interface{}{"sata0": ",backup=0,iops_rd_max=78,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth.Iops.ReadLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 3}}}}}}}}, + output: map[string]interface{}{"sata0": ",backup=0,iops_rd_max_length=3,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth.Iops.ReadLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint34}}}}}}}}, + output: map[string]interface{}{"sata1": ",backup=0,iops_rd=34,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth.Iops.WriteLimit", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"sata2": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth.Iops.WriteLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89}}}}}}}}, + output: map[string]interface{}{"sata3": ",backup=0,iops_wr_max=89,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth.Iops.WriteLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 4}}}}}}}}, + output: map[string]interface{}{"sata3": ",backup=0,iops_wr_max_length=4,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth.Iops.WriteLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint23}}}}}}}}, + output: map[string]interface{}{"sata4": ",backup=0,iops_wr=23,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth.MBps", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{}}}}}}}, + output: map[string]interface{}{"sata3": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth.MBps.ReadLimit", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{}}}}}}}}, + output: map[string]interface{}{"sata4": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth.MBps.ReadLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: float99}}}}}}}}, + output: map[string]interface{}{"sata5": ",backup=0,mbps_rd_max=99.20,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth.MBps.ReadLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: float10}}}}}}}}, + output: map[string]interface{}{"sata0": ",backup=0,mbps_rd=10.30,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth.MBps.WriteLimit", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{}}}}}}}}, + output: map[string]interface{}{"sata1": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth.MBps.WriteLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: float79}}}}}}}}, + output: map[string]interface{}{"sata2": ",backup=0,mbps_wr_max=79.23,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Bandwidth.MBps.WriteLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: float45}}}}}}}}, + output: map[string]interface{}{"sata3": ",backup=0,mbps_wr=45.23,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Cache", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Disk: &QemuSataDisk{Cache: QemuDiskCache_DirectSync}}}}}, + output: map[string]interface{}{"sata5": ",backup=0,cache=directsync,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Discard", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{Discard: true}}}}}, + output: map[string]interface{}{"sata0": ",backup=0,discard=on,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.EmulateSSD", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{EmulateSSD: true}}}}}, + output: map[string]interface{}{"sata1": ",backup=0,replicate=0,ssd=1"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Format", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Disk: &QemuSataDisk{Format: format_Raw}}}}}, + output: map[string]interface{}{"sata2": ",backup=0,format=raw,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Replicate", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Disk: &QemuSataDisk{Replicate: true}}}}}, + output: map[string]interface{}{"sata2": ",backup=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Serial", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{Serial: "558485ef-478"}}}}}, + output: map[string]interface{}{"sata3": ",backup=0,replicate=0,serial=558485ef-478"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Size", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Disk: &QemuSataDisk{Size: 32}}}}}, + output: map[string]interface{}{"sata4": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.Storage", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Disk: &QemuSataDisk{Storage: "Test"}}}}}, + output: map[string]interface{}{"sata5": "Test:0,backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Disk.WorldWideName", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{WorldWideName: "0x500DCBA500E23456"}}}}}, + output: map[string]interface{}{"sata0": ",backup=0,replicate=0,wwn=0x500DCBA500E23456"}, + }, + // Create Disks.Sata.Disk_X.Passthrough + {name: "Create Disks.Sata.Disk_X.Passthrough All", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + AsyncIO: QemuDiskAsyncIO_Threads, + Backup: true, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: float99, Concurrent: float10}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: float79, Concurrent: float45}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78, BurstDuration: 3, Concurrent: uint34}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89, BurstDuration: 4, Concurrent: uint23}, + }, + }, + Cache: QemuDiskCache_Unsafe, + Discard: true, + EmulateSSD: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + Serial: "test-serial_757465-gdg", + WorldWideName: "0x5007892000C4321A", + }}}}}, + output: map[string]interface{}{"sata0": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,aio=threads,cache=unsafe,discard=on,iops_rd=34,iops_rd_max=78,iops_rd_max_length=3,iops_wr=23,iops_wr_max=89,iops_wr_max_length=4,mbps_rd=10.30,mbps_rd_max=99.20,mbps_wr=45.23,mbps_wr_max=79.23,serial=test-serial_757465-gdg,ssd=1,wwn=0x5007892000C4321A"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.AsyncIO", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Passthrough: &QemuSataPassthrough{AsyncIO: QemuDiskAsyncIO_Threads}}}}}, + output: map[string]interface{}{"sata1": ",aio=threads,backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Backup", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Backup: true}}}}}, + output: map[string]interface{}{"sata2": ",replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{}}}}}}, + output: map[string]interface{}{"sata3": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth.Iops", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{}}}}}}}, + output: map[string]interface{}{"sata5": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth.Iops.ReadLimit", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"sata0": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth.Iops.ReadLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78}}}}}}}}, + output: map[string]interface{}{"sata1": ",backup=0,iops_rd_max=78,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth.Iops.ReadLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 3}}}}}}}}, + output: map[string]interface{}{"sata1": ",backup=0,iops_rd_max_length=3,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth.Iops.ReadLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint34}}}}}}}}, + output: map[string]interface{}{"sata2": ",backup=0,iops_rd=34,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth.Iops.WriteLimit", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"sata3": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth.Iops.WriteLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89}}}}}}}}, + output: map[string]interface{}{"sata4": ",backup=0,iops_wr_max=89,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth.Iops.WriteLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 4}}}}}}}}, + output: map[string]interface{}{"sata4": ",backup=0,iops_wr_max_length=4,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth.Iops.WriteLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint23}}}}}}}}, + output: map[string]interface{}{"sata5": ",backup=0,iops_wr=23,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth.MBps", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{}}}}}}}, + output: map[string]interface{}{"sata4": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth.MBps.ReadLimit", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{}}}}}}}}, + output: map[string]interface{}{"sata5": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth.MBps.ReadLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: float99}}}}}}}}, + output: map[string]interface{}{"sata0": ",backup=0,mbps_rd_max=99.20,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth.MBps.ReadLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: float10}}}}}}}}, + output: map[string]interface{}{"sata1": ",backup=0,mbps_rd=10.30,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth.MBps.WriteLimit", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{}}}}}}}}, + output: map[string]interface{}{"sata2": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth.MBps.WriteLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: float79}}}}}}}}, + output: map[string]interface{}{"sata3": ",backup=0,mbps_wr_max=79.23,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Bandwidth.MBps.WriteLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: float45}}}}}}}}, + output: map[string]interface{}{"sata4": ",backup=0,mbps_wr=45.23,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Cache", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Cache: QemuDiskCache_Unsafe}}}}}, + output: map[string]interface{}{"sata0": ",backup=0,cache=unsafe,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Discard", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Discard: true}}}}}, + output: map[string]interface{}{"sata1": ",backup=0,discard=on,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.EmulateSSD", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Passthrough: &QemuSataPassthrough{EmulateSSD: true}}}}}, + output: map[string]interface{}{"sata2": ",backup=0,replicate=0,ssd=1"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.File", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Passthrough: &QemuSataPassthrough{File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8"}}}}}, + output: map[string]interface{}{"sata3": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,backup=0,replicate=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Replicate", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Replicate: true}}}}}, + output: map[string]interface{}{"sata4": ",backup=0"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.Serial", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Serial: "test-serial_757465-gdg"}}}}}, + output: map[string]interface{}{"sata5": ",backup=0,replicate=0,serial=test-serial_757465-gdg"}, + }, + {name: "Create Disks.Sata.Disk_X.Passthrough.WorldWideName", + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Passthrough: &QemuSataPassthrough{WorldWideName: "0x5001ABE000987654"}}}}}, + output: map[string]interface{}{"sata5": ",backup=0,replicate=0,wwn=0x5001ABE000987654"}, + }, + // Create Disks.Scsi + {name: "Create Disks.Scsi.CdRom none", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{CdRom: &QemuCdRom{}}}}}, + output: map[string]interface{}{"scsi0": "none,media=cdrom"}, + }, + {name: "Create Disks.Scsi.CdRom.Iso", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_1: &QemuScsiStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + output: map[string]interface{}{"scsi1": "Test:iso/test.iso,media=cdrom"}, + }, + {name: "Create Disks.Scsi.CdRom.Passthrough", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_2: &QemuScsiStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + output: map[string]interface{}{"scsi2": "cdrom,media=cdrom"}, + }, + {name: "Create Disks.Scsi.CloudInit", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_1: &QemuScsiStorage{CloudInit: &QemuCloudInitDisk{Format: format_Raw, Storage: "Test"}}}}}, + output: map[string]interface{}{"scsi1": "Test:cloudinit,format=raw"}, + }, + // Create Disks.Scsi.Disk_X.Disk + {name: "Create Disks.Scsi.Disk_X.Disk All", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{Disk: &QemuScsiDisk{ + AsyncIO: QemuDiskAsyncIO_Native, + Backup: true, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: float99, Concurrent: float10}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: float79, Concurrent: float45}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78, BurstDuration: 3, Concurrent: uint34}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89, BurstDuration: 4, Concurrent: uint23}, + }, + }, + Cache: QemuDiskCache_DirectSync, + Discard: true, + EmulateSSD: true, + Format: format_Raw, + IOThread: true, + ReadOnly: true, + Replicate: true, + Serial: "558485ef-478", + Size: 32, + Storage: "Test", + WorldWideName: "0x500D567800BAC321", + }}}}}, + output: map[string]interface{}{"scsi0": "Test:32,aio=native,cache=directsync,discard=on,format=raw,iops_rd=34,iops_rd_max=78,iops_rd_max_length=3,iops_wr=23,iops_wr_max=89,iops_wr_max_length=4,iothread=1,mbps_rd=10.30,mbps_rd_max=99.20,mbps_wr=45.23,mbps_wr_max=79.23,ro=1,serial=558485ef-478,ssd=1,wwn=0x500D567800BAC321"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.AsyncIO", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_1: &QemuScsiStorage{Disk: &QemuScsiDisk{AsyncIO: QemuDiskAsyncIO_Native}}}}}, + output: map[string]interface{}{"scsi1": ",aio=native,backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Backup", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_2: &QemuScsiStorage{Disk: &QemuScsiDisk{Backup: true}}}}}, + output: map[string]interface{}{"scsi2": ",replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_3: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{}}}}}}, + output: map[string]interface{}{"scsi3": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth.Iops", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_11: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"scsi11": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth.Iops.ReadLimit", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_12: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"scsi12": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth.Iops.ReadLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_13: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78}}}}}}}}, + output: map[string]interface{}{"scsi13": ",backup=0,iops_rd_max=78,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth.Iops.ReadLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_13: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 3}}}}}}}}, + output: map[string]interface{}{"scsi13": ",backup=0,iops_rd_max_length=3,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth.Iops.ReadLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_14: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint34}}}}}}}}, + output: map[string]interface{}{"scsi14": ",backup=0,iops_rd=34,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth.Iops.WriteLimit", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_15: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"scsi15": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth.Iops.WriteLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_16: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89}}}}}}}}, + output: map[string]interface{}{"scsi16": ",backup=0,iops_wr_max=89,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth.Iops.WriteLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_16: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 4}}}}}}}}, + output: map[string]interface{}{"scsi16": ",backup=0,iops_wr_max_length=4,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth.Iops.WriteLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_17: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint23}}}}}}}}, + output: map[string]interface{}{"scsi17": ",backup=0,iops_wr=23,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth.MBps", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_4: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{}}}}}}}, + output: map[string]interface{}{"scsi4": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth.MBps.ReadLimit", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_5: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{}}}}}}}}, + output: map[string]interface{}{"scsi5": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth.MBps.ReadLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_6: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: float99}}}}}}}}, + output: map[string]interface{}{"scsi6": ",backup=0,mbps_rd_max=99.20,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth.MBps.ReadLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_7: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: float10}}}}}}}}, + output: map[string]interface{}{"scsi7": ",backup=0,mbps_rd=10.30,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth.MBps.WriteLimit", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_8: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{}}}}}}}}, + output: map[string]interface{}{"scsi8": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth.MBps.WriteLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_9: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: float79}}}}}}}}, + output: map[string]interface{}{"scsi9": ",backup=0,mbps_wr_max=79.23,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Bandwidth.MBps.WriteLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_10: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: float45}}}}}}}}, + output: map[string]interface{}{"scsi10": ",backup=0,mbps_wr=45.23,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Cache", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_18: &QemuScsiStorage{Disk: &QemuScsiDisk{Cache: QemuDiskCache_DirectSync}}}}}, + output: map[string]interface{}{"scsi18": ",backup=0,cache=directsync,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Discard", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_19: &QemuScsiStorage{Disk: &QemuScsiDisk{Discard: true}}}}}, + output: map[string]interface{}{"scsi19": ",backup=0,discard=on,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.EmulateSSD", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_20: &QemuScsiStorage{Disk: &QemuScsiDisk{EmulateSSD: true}}}}}, + output: map[string]interface{}{"scsi20": ",backup=0,replicate=0,ssd=1"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Format", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_20: &QemuScsiStorage{Disk: &QemuScsiDisk{Format: format_Raw}}}}}, + output: map[string]interface{}{"scsi20": ",backup=0,format=raw,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.IOThread", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_21: &QemuScsiStorage{Disk: &QemuScsiDisk{IOThread: true}}}}}, + output: map[string]interface{}{"scsi21": ",backup=0,iothread=1,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.ReadOnly", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_22: &QemuScsiStorage{Disk: &QemuScsiDisk{ReadOnly: true}}}}}, + output: map[string]interface{}{"scsi22": ",backup=0,replicate=0,ro=1"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Replicate", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_23: &QemuScsiStorage{Disk: &QemuScsiDisk{Replicate: true}}}}}, + output: map[string]interface{}{"scsi23": ",backup=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Serial", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_24: &QemuScsiStorage{Disk: &QemuScsiDisk{Serial: "558485ef-478"}}}}}, + output: map[string]interface{}{"scsi24": ",backup=0,replicate=0,serial=558485ef-478"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Size", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_25: &QemuScsiStorage{Disk: &QemuScsiDisk{Size: 32}}}}}, + output: map[string]interface{}{"scsi25": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.Storage", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_26: &QemuScsiStorage{Disk: &QemuScsiDisk{Storage: "Test"}}}}}, + output: map[string]interface{}{"scsi26": "Test:0,backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Disk.WorldWideName", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_27: &QemuScsiStorage{Disk: &QemuScsiDisk{WorldWideName: "0x500EF32100D76589"}}}}}, + output: map[string]interface{}{"scsi27": ",backup=0,replicate=0,wwn=0x500EF32100D76589"}, + }, + // Create Disks.Scsi.Passthrough + {name: "Create Disks.Scsi.Disk_X.Passthrough All", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + AsyncIO: QemuDiskAsyncIO_Threads, + Backup: true, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: float99, Concurrent: float10}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: float79, Concurrent: float45}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78, BurstDuration: 3, Concurrent: uint34}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89, BurstDuration: 4, Concurrent: uint23}, + }, + }, + Cache: QemuDiskCache_Unsafe, + Discard: true, + EmulateSSD: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + IOThread: true, + ReadOnly: true, + Replicate: true, + Serial: "test-serial_757465-gdg", + WorldWideName: "0x500BCA3000F09876", + }}}}}, + output: map[string]interface{}{"scsi0": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,aio=threads,cache=unsafe,discard=on,iops_rd=34,iops_rd_max=78,iops_rd_max_length=3,iops_wr=23,iops_wr_max=89,iops_wr_max_length=4,iothread=1,mbps_rd=10.30,mbps_rd_max=99.20,mbps_wr=45.23,mbps_wr_max=79.23,ro=1,serial=test-serial_757465-gdg,ssd=1,wwn=0x500BCA3000F09876"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.AsyncIO", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_1: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{AsyncIO: QemuDiskAsyncIO_Threads}}}}}, + output: map[string]interface{}{"scsi1": ",aio=threads,backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Backup", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_2: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Backup: true}}}}}, + output: map[string]interface{}{"scsi2": ",replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_3: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{}}}}}}, + output: map[string]interface{}{"scsi3": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth.Iops", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_11: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{}}}}}}}, + output: map[string]interface{}{"scsi11": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth.Iops.ReadLimit", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_12: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"scsi12": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth.Iops.ReadLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_13: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78}}}}}}}}, + output: map[string]interface{}{"scsi13": ",backup=0,iops_rd_max=78,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth.Iops.ReadLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_13: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 3}}}}}}}}, + output: map[string]interface{}{"scsi13": ",backup=0,iops_rd_max_length=3,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth.Iops.ReadLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_14: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint34}}}}}}}}, + output: map[string]interface{}{"scsi14": ",backup=0,iops_rd=34,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth.Iops.WriteLimit", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_15: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"scsi15": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth.Iops.WriteLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_16: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89}}}}}}}}, + output: map[string]interface{}{"scsi16": ",backup=0,iops_wr_max=89,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth.Iops.WriteLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_16: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 4}}}}}}}}, + output: map[string]interface{}{"scsi16": ",backup=0,iops_wr_max_length=4,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth.Iops.WriteLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_17: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint23}}}}}}}}, + output: map[string]interface{}{"scsi17": ",backup=0,iops_wr=23,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth.MBps", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_4: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{}}}}}}}, + output: map[string]interface{}{"scsi4": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth.MBps.ReadLimit", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_5: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{}}}}}}}}, + output: map[string]interface{}{"scsi5": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth.MBps.ReadLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_6: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: float99}}}}}}}}, + output: map[string]interface{}{"scsi6": ",backup=0,mbps_rd_max=99.20,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth.MBps.ReadLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_7: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: float10}}}}}}}}, + output: map[string]interface{}{"scsi7": ",backup=0,mbps_rd=10.30,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth.MBps.WriteLimit", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_8: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{}}}}}}}}, + output: map[string]interface{}{"scsi8": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth.MBps.WriteLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_9: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: float79}}}}}}}}, + output: map[string]interface{}{"scsi9": ",backup=0,mbps_wr_max=79.23,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Bandwidth.MBps.WriteLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_10: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: float45}}}}}}}}, + output: map[string]interface{}{"scsi10": ",backup=0,mbps_wr=45.23,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Cache", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_18: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Cache: QemuDiskCache_Unsafe}}}}}, + output: map[string]interface{}{"scsi18": ",backup=0,cache=unsafe,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Discard", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_19: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Discard: true}}}}}, + output: map[string]interface{}{"scsi19": ",backup=0,discard=on,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.EmulateSSD", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_20: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{EmulateSSD: true}}}}}, + output: map[string]interface{}{"scsi20": ",backup=0,replicate=0,ssd=1"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.File", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_21: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8"}}}}}, + output: map[string]interface{}{"scsi21": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,backup=0,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.IOThread", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_22: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{IOThread: true}}}}}, + output: map[string]interface{}{"scsi22": ",backup=0,iothread=1,replicate=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.ReadOnly", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_23: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ReadOnly: true}}}}}, + output: map[string]interface{}{"scsi23": ",backup=0,replicate=0,ro=1"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Replicate", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_24: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Replicate: true}}}}}, + output: map[string]interface{}{"scsi24": ",backup=0"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.Serial", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_25: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Serial: "test-serial_757465-gdg"}}}}}, + output: map[string]interface{}{"scsi25": ",backup=0,replicate=0,serial=test-serial_757465-gdg"}, + }, + {name: "Create Disks.Scsi.Disk_X.Passthrough.WorldWideName", + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_25: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{WorldWideName: "0x5004DC0100E239C7"}}}}}, + output: map[string]interface{}{"scsi25": ",backup=0,replicate=0,wwn=0x5004DC0100E239C7"}, + }, + // Create Disks.VirtIO + {name: "Create Disks.VirtIO.Disk_X.CdRom none", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{CdRom: &QemuCdRom{}}}}}, + output: map[string]interface{}{"virtio0": "none,media=cdrom"}, + }, + {name: "Create Disks.VirtIO.Disk_X.CdRom.Iso", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + output: map[string]interface{}{"virtio1": "Test:iso/test.iso,media=cdrom"}, + }, + {name: "Create Disks.VirtIO.Disk_X.CdRom.Passthrough", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + output: map[string]interface{}{"virtio2": "cdrom,media=cdrom"}, + }, + {name: "Create Disks.VirtIO.Disk_X.CloudInit", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{CloudInit: &QemuCloudInitDisk{Format: format_Raw, Storage: "Test"}}}}}, + output: map[string]interface{}{"virtio1": "Test:cloudinit,format=raw"}, + }, + // Create Disks.VirtIO.Disk + {name: "Create Disks.VirtIO.Disk_X.Disk All", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + AsyncIO: QemuDiskAsyncIO_Native, + Backup: true, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: float99, Concurrent: float10}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: float79, Concurrent: float45}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78, BurstDuration: 3, Concurrent: uint34}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89, BurstDuration: 4, Concurrent: uint23}, + }, + }, + Cache: QemuDiskCache_DirectSync, + Discard: true, + Format: format_Raw, + IOThread: true, + ReadOnly: true, + Replicate: true, + Serial: "558485ef-478", + Size: 32, + Storage: "Test", + WorldWideName: "0x500A7B0800F345D2", + }}}}}, + output: map[string]interface{}{"virtio0": "Test:32,aio=native,cache=directsync,discard=on,format=raw,iops_rd=34,iops_rd_max=78,iops_rd_max_length=3,iops_wr=23,iops_wr_max=89,iops_wr_max_length=4,iothread=1,mbps_rd=10.30,mbps_rd_max=99.20,mbps_wr=45.23,mbps_wr_max=79.23,ro=1,serial=558485ef-478,wwn=0x500A7B0800F345D2"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.AsyncIO", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{AsyncIO: QemuDiskAsyncIO_Native}}}}}, + output: map[string]interface{}{"virtio1": ",aio=native,backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Backup", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Backup: true}}}}}, + output: map[string]interface{}{"virtio2": ",replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{}}}}}}, + output: map[string]interface{}{"virtio3": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.iops", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_11: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{}}}}}}}, + output: map[string]interface{}{"virtio11": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.iops.ReadLimit", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_12: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"virtio12": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.iops.ReadLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_13: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78}}}}}}}}, + output: map[string]interface{}{"virtio13": ",backup=0,iops_rd_max=78,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.iops.ReadLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_13: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 3}}}}}}}}, + output: map[string]interface{}{"virtio13": ",backup=0,iops_rd_max_length=3,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.iops.ReadLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_14: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint34}}}}}}}}, + output: map[string]interface{}{"virtio14": ",backup=0,iops_rd=34,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.iops.WriteLimit", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_15: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"virtio15": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.iops.WriteLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89}}}}}}}}, + output: map[string]interface{}{"virtio0": ",backup=0,iops_wr_max=89,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.iops.WriteLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 4}}}}}}}}, + output: map[string]interface{}{"virtio0": ",backup=0,iops_wr_max_length=4,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.iops.WriteLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint23}}}}}}}}, + output: map[string]interface{}{"virtio1": ",backup=0,iops_wr=23,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.MBps", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_4: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{}}}}}}}, + output: map[string]interface{}{"virtio4": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.MBps.ReadLimit", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"virtio5": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.MBps.ReadLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_6: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78}}}}}}}}, + output: map[string]interface{}{"virtio6": ",backup=0,iops_rd_max=78,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.MBps.ReadLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_6: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 1}}}}}}}}, + output: map[string]interface{}{"virtio6": ",backup=0,iops_rd_max_length=1,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.MBps.ReadLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_7: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint34}}}}}}}}, + output: map[string]interface{}{"virtio7": ",backup=0,iops_rd=34,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.MBps.WriteLimit", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_8: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"virtio8": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.MBps.WriteLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_9: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89}}}}}}}}, + output: map[string]interface{}{"virtio9": ",backup=0,iops_wr_max=89,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.MBps.WriteLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_9: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 2}}}}}}}}, + output: map[string]interface{}{"virtio9": ",backup=0,iops_wr_max_length=2,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Bandwidth.MBps.WriteLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_10: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint23}}}}}}}}, + output: map[string]interface{}{"virtio10": ",backup=0,iops_wr=23,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Cache", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Cache: QemuDiskCache_DirectSync}}}}}, + output: map[string]interface{}{"virtio2": ",backup=0,cache=directsync,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Discard", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Discard: true}}}}}, + output: map[string]interface{}{"virtio3": ",backup=0,discard=on,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Format", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_4: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: format_Raw}}}}}, + output: map[string]interface{}{"virtio4": ",backup=0,format=raw,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.IOThread", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_4: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{IOThread: true}}}}}, + output: map[string]interface{}{"virtio4": ",backup=0,iothread=1,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.ReadOnly", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ReadOnly: true}}}}}, + output: map[string]interface{}{"virtio5": ",backup=0,replicate=0,ro=1"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Replicate", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_6: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Replicate: true}}}}}, + output: map[string]interface{}{"virtio6": ",backup=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Serial", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_7: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Serial: "558485ef-478"}}}}}, + output: map[string]interface{}{"virtio7": ",backup=0,replicate=0,serial=558485ef-478"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Size", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_8: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Size: 32}}}}}, + output: map[string]interface{}{"virtio8": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.Storage", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_9: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Storage: "Test"}}}}}, + output: map[string]interface{}{"virtio9": "Test:0,backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Disk.WorldWideName", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_10: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{WorldWideName: "0x5005FED000B87632"}}}}}, + output: map[string]interface{}{"virtio10": ",backup=0,replicate=0,wwn=0x5005FED000B87632"}, + }, + // Create Disks.VirtIO.Disk_X.Passthrough + {name: "Create Disks.VirtIO.Disk_X.Passthrough All", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + AsyncIO: QemuDiskAsyncIO_Threads, + Backup: true, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: float99, Concurrent: float10}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: float79, Concurrent: float45}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78, BurstDuration: 3, Concurrent: uint34}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89, BurstDuration: 4, Concurrent: uint23}, + }, + }, + Cache: QemuDiskCache_Unsafe, + Discard: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + IOThread: true, + ReadOnly: true, + Replicate: true, + Serial: "test-serial_757465-gdg", + WorldWideName: "0x500C329500A1EFAB", + }}}}}, + output: map[string]interface{}{"virtio0": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,aio=threads,cache=unsafe,discard=on,iops_rd=34,iops_rd_max=78,iops_rd_max_length=3,iops_wr=23,iops_wr_max=89,iops_wr_max_length=4,iothread=1,mbps_rd=10.30,mbps_rd_max=99.20,mbps_wr=45.23,mbps_wr_max=79.23,ro=1,serial=test-serial_757465-gdg,wwn=0x500C329500A1EFAB"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.AsyncIO", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{AsyncIO: QemuDiskAsyncIO_Threads}}}}}, + output: map[string]interface{}{"virtio1": ",aio=threads,backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Backup", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Backup: true}}}}}, + output: map[string]interface{}{"virtio2": ",replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{}}}}}}, + output: map[string]interface{}{"virtio3": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth.Iops", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_11: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{}}}}}}}, + output: map[string]interface{}{"virtio11": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth.Iops.ReadLimit", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_12: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"virtio12": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth.Iops.ReadLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_13: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: uint78}}}}}}}}, + output: map[string]interface{}{"virtio13": ",backup=0,iops_rd_max=78,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth.Iops.ReadLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_13: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 3}}}}}}}}, + output: map[string]interface{}{"virtio13": ",backup=0,iops_rd_max_length=3,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth.Iops.ReadLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_14: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint34}}}}}}}}, + output: map[string]interface{}{"virtio14": ",backup=0,iops_rd=34,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth.Iops.WriteLimit", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_15: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{}}}}}}}}, + output: map[string]interface{}{"virtio15": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth.Iops.WriteLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: uint89}}}}}}}}, + output: map[string]interface{}{"virtio0": ",backup=0,iops_wr_max=89,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth.Iops.WriteLimit.BurstDuration", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 4}}}}}}}}, + output: map[string]interface{}{"virtio0": ",backup=0,iops_wr_max_length=4,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth.Iops.WriteLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: uint23}}}}}}}}, + output: map[string]interface{}{"virtio1": ",backup=0,iops_wr=23,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth.MBps", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_4: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{}}}}}}}, + output: map[string]interface{}{"virtio4": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth.MBps.ReadLimit", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{}}}}}}}}, + output: map[string]interface{}{"virtio5": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth.MBps.ReadLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_6: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: float99}}}}}}}}, + output: map[string]interface{}{"virtio6": ",backup=0,mbps_rd_max=99.20,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth.MBps.ReadLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_7: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: float10}}}}}}}}, + output: map[string]interface{}{"virtio7": ",backup=0,mbps_rd=10.30,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth.MBps.WriteLimit", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_8: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{}}}}}}}}, + output: map[string]interface{}{"virtio8": ",backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth.MBps.WriteLimit.Burst", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_9: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: float79}}}}}}}}, + output: map[string]interface{}{"virtio9": ",backup=0,mbps_wr_max=79.23,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Bandwidth.MBps.WriteLimit.Concurrent", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_10: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: float45}}}}}}}}, + output: map[string]interface{}{"virtio10": ",backup=0,mbps_wr=45.23,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Cache", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Cache: QemuDiskCache_Unsafe}}}}}, + output: map[string]interface{}{"virtio2": ",backup=0,cache=unsafe,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Discard", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Discard: true}}}}}, + output: map[string]interface{}{"virtio3": ",backup=0,discard=on,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.File", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_4: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8"}}}}}, + output: map[string]interface{}{"virtio4": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,backup=0,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.IOThread", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{IOThread: true}}}}}, + output: map[string]interface{}{"virtio5": ",backup=0,iothread=1,replicate=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.ReadOnly", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_6: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ReadOnly: true}}}}}, + output: map[string]interface{}{"virtio6": ",backup=0,replicate=0,ro=1"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Replicate", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_6: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Replicate: true}}}}}, + output: map[string]interface{}{"virtio6": ",backup=0"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.Serial", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_7: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Serial: "test-serial_757465-gdg"}}}}}, + output: map[string]interface{}{"virtio7": ",backup=0,replicate=0,serial=test-serial_757465-gdg"}, + }, + {name: "Create Disks.VirtIO.Disk_X.Passthrough.WorldWideName", + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_8: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{WorldWideName: "0x500D41A600C67853"}}}}}, + output: map[string]interface{}{"virtio8": ",backup=0,replicate=0,wwn=0x500D41A600C67853"}, + }, + // Create Iso + {name: "Create Iso", + config: &ConfigQemu{Iso: &IsoFile{Storage: "test", File: "file.iso"}}, + output: map[string]interface{}{"ide2": "test:iso/file.iso,media=cdrom"}, + }, + // Update + + // Update Disk.Ide + {name: "Update Disk.Ide.Disk_X DELETE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{}}}}, + output: map[string]interface{}{"delete": "ide0"}, + }, + // Update Disk.Ide.Disk_X.CdRom + {name: "Update Disk.Ide.Disk_X.CdRom CHANGE ISO TO None", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{CdRom: &QemuCdRom{}}}}}, + output: map[string]interface{}{"ide1": "none,media=cdrom"}, + }, + {name: "Update Disk.Ide.Disk_X.CdRom CHANGE ISO TO Passthrough", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + output: map[string]interface{}{"ide2": "cdrom,media=cdrom"}, + }, + {name: "Update Disk.Ide.Disk_X.CdRom CHANGE None TO ISO", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{CdRom: &QemuCdRom{}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + output: map[string]interface{}{"ide3": "Test:iso/test.iso,media=cdrom"}, + }, + {name: "Update Disk.Ide.Disk_X.CdRom CHANGE None TO Passthrough", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{CdRom: &QemuCdRom{}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + output: map[string]interface{}{"ide0": "cdrom,media=cdrom"}, + }, + {name: "Update Disk.Ide.Disk_X.CdRom CHANGE Passthrough TO ISO", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + output: map[string]interface{}{"ide1": "Test:iso/test.iso,media=cdrom"}, + }, + {name: "Update Disk.Ide.Disk_X.CdRom CHANGE Passthrough TO None", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{CdRom: &QemuCdRom{}}}}}, + output: map[string]interface{}{"ide2": "none,media=cdrom"}, + }, + {name: "Update Disk.Ide.Disk_X.CdRom DELETE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{}}}}, + output: map[string]interface{}{"delete": "ide3"}, + }, + {name: "Update Disk.Ide.Disk_X.CdRom SAME", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.Ide.Disk_X.CdRom.Iso.File CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test2.iso", Storage: "Test"}}}}}}, + output: map[string]interface{}{"ide1": "Test:iso/test2.iso,media=cdrom"}, + }, + {name: "Update Disk.Ide.Disk_X.CdRom.Iso.Storage CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "NewStorage"}}}}}}, + output: map[string]interface{}{"ide2": "NewStorage:iso/test.iso,media=cdrom"}, + }, + // Update Disk.Ide.Disk_X.CloudInit + {name: "Update Disk.Ide.Disk_X.CloudInit DELETE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{CloudInit: update_CloudInit}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{}}}}, + output: map[string]interface{}{"delete": "ide3"}, + }, + {name: "Update Disk.Ide.Disk_X.CloudInit SAME", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{CloudInit: update_CloudInit}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{CloudInit: update_CloudInit}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.Ide.Disk_X.CloudInit.Format CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{CloudInit: &QemuCloudInitDisk{Format: format_Raw, Storage: "Test"}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{CloudInit: &QemuCloudInitDisk{Format: QemuDiskFormat_Qcow2, Storage: "Test"}}}}}, + output: map[string]interface{}{"ide1": "Test:cloudinit,format=qcow2"}, + }, + {name: "Update Disk.Ide.Disk_X.CloudInit.Storage CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{CloudInit: &QemuCloudInitDisk{Format: format_Raw, Storage: "Test"}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{CloudInit: &QemuCloudInitDisk{Format: format_Raw, Storage: "NewStorage"}}}}}, + output: map[string]interface{}{"ide2": "NewStorage:cloudinit,format=raw"}, + }, + // Update Disk.Ide.Disk_X.Disk + {name: "Update Disk.Ide.Disk_X.Disk CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{ + AsyncIO: QemuDiskAsyncIO_Native, + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{"ide3": "test:0/vm-0-disk-23.raw,aio=native,backup=0,replicate=0"}, + }, + {name: "Update Disk.Ide.Disk_X.Disk CHANGE LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{ + AsyncIO: QemuDiskAsyncIO_Native, + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{"ide3": "test:100/base-100-disk-1.raw/0/vm-0-disk-23.raw,aio=native,backup=0,replicate=0"}, + }, + {name: "Update Disk.Ide.Disk_X.Disk DELETE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: ideBase}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{}}}}, + output: map[string]interface{}{"delete": "ide0"}, + }, + {name: "Update Disk.Ide.Disk_X.Disk MIGRATE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test1", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test2", + }}}}}, + output: map[string]interface{}{"ide1": "test2:0/vm-0-disk-23.raw,backup=0,replicate=0"}, + }, + {name: "Update Disk.Ide.Disk_X.Disk MIGRATE LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test1", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test2", + }}}}}, + output: map[string]interface{}{"ide1": "test2:0/vm-0-disk-23.raw,backup=0,replicate=0"}, + }, + {name: "Update Disk.Ide.Disk_X.Disk RESIZE DOWN", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Size: 9, + Storage: "test", + }}}}}, + output: map[string]interface{}{"ide2": "test:9,backup=0,format=raw,replicate=0"}, + }, + {name: "Update Disk.Ide.Disk_X.Disk RESIZE DOWN LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Size: 9, + Storage: "test", + }}}}}, + output: map[string]interface{}{"ide2": "test:9,backup=0,format=raw,replicate=0"}, + }, + {name: "Update Disk.Ide.Disk_X.Disk RESIZE UP", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Size: 11, + Storage: "test", + }}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.Ide.Disk_X.Disk RESIZE UP LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 110, + Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Size: 11, + Storage: "test", + }}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.Ide.Disk_X.Disk SAME", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: ideBase}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.Ide.Disk_X.Disk.Format CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Qcow2, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{"ide1": "test:0/vm-0-disk-23.qcow2,backup=0,replicate=0"}, + }, + {name: "Update Disk.Ide.Disk_X.Disk.Format CHANGE LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Qcow2, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{"ide1": "test:0/vm-0-disk-23.qcow2,backup=0,replicate=0"}, + }, + // Update Disk.Ide.Disk_X.Passthrough + {name: "Update Disk.Ide.Disk_X.Passthrough CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + File: "/dev/disk/sda", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + AsyncIO: QemuDiskAsyncIO_Native, + File: "/dev/disk/sda", + }}}}}, + output: map[string]interface{}{"ide0": "/dev/disk/sda,aio=native,backup=0,replicate=0"}, + }, + {name: "Update Disk.Ide.Disk_X.Passthrough SAME", + currentConfig: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + File: "/dev/disk/sda", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + File: "/dev/disk/sda", + }}}}}, + output: map[string]interface{}{}, + }, + // Update Disk.Sata + {name: "Update Disk.Sata.Disk_X DELETE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{}}}}, + output: map[string]interface{}{"delete": "sata0"}, + }, + // Update Disk.Sata.Disk_X.CdRom + {name: "Update Disk.Sata.Disk_X.CdRom CHANGE ISO TO None", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{CdRom: &QemuCdRom{}}}}}, + output: map[string]interface{}{"sata1": "none,media=cdrom"}, + }, + {name: "Update Disk.Sata.Disk_X.CdRom CHANGE ISO TO Passthrough", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + output: map[string]interface{}{"sata2": "cdrom,media=cdrom"}, + }, + {name: "Update Disk.Sata.Disk_X.CdRom CHANGE None TO ISO", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{CdRom: &QemuCdRom{}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + output: map[string]interface{}{"sata3": "Test:iso/test.iso,media=cdrom"}, + }, + {name: "Update Disk.Sata.Disk_X.CdRom CHANGE None TO Passthrough", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{CdRom: &QemuCdRom{}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + output: map[string]interface{}{"sata4": "cdrom,media=cdrom"}, + }, + {name: "Update Disk.Sata.Disk_X.CdRom CHANGE Passthrough TO ISO", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + output: map[string]interface{}{"sata5": "Test:iso/test.iso,media=cdrom"}, + }, + {name: "Update Disk.Sata.Disk_X.CdRom CHANGE Passthrough TO None", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{CdRom: &QemuCdRom{}}}}}, + output: map[string]interface{}{"sata0": "none,media=cdrom"}, + }, + {name: "Update Disk.Sata.Disk_X.CdRom DELETE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{}}}}, + output: map[string]interface{}{"delete": "sata1"}, + }, + {name: "Update Disk.Sata.Disk_X.CdRom SAME", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.Sata.Disk_X.CdRom.Iso.File CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test2.iso", Storage: "Test"}}}}}}, + output: map[string]interface{}{"sata3": "Test:iso/test2.iso,media=cdrom"}, + }, + {name: "Update Disk.Sata.Disk_X.CdRom.Iso.Storage CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "NewStorage"}}}}}}, + output: map[string]interface{}{"sata4": "NewStorage:iso/test.iso,media=cdrom"}, + }, + // Update Disk.Sata.Disk_X.CloudInit + {name: "Update Disk.Sata.Disk_X.CloudInit DELETE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{CloudInit: update_CloudInit}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{}}}}, + output: map[string]interface{}{"delete": "sata5"}, + }, + {name: "Update Disk.Sata.Disk_X.CloudInit SAME", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{CloudInit: update_CloudInit}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{CloudInit: update_CloudInit}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.Sata.Disk_X.CloudInit.Format CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{CloudInit: &QemuCloudInitDisk{Format: format_Raw, Storage: "Test"}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{CloudInit: &QemuCloudInitDisk{Format: QemuDiskFormat_Qcow2, Storage: "Test"}}}}}, + output: map[string]interface{}{"sata1": "Test:cloudinit,format=qcow2"}, + }, + {name: "Update Disk.Sata.Disk_X.CloudInit.Storage CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{CloudInit: &QemuCloudInitDisk{Format: format_Raw, Storage: "Test"}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{CloudInit: &QemuCloudInitDisk{Format: format_Raw, Storage: "NewStorage"}}}}}, + output: map[string]interface{}{"sata2": "NewStorage:cloudinit,format=raw"}, + }, + // Update Disk.Sata.Disk_X.Disk + {name: "Update Disk.Sata.Disk_X.Disk CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{ + AsyncIO: QemuDiskAsyncIO_Native, + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{"sata3": "test:0/vm-0-disk-23.raw,aio=native,backup=0,replicate=0"}, + }, + {name: "Update Disk.Sata.Disk_X.Disk CHANGE LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{ + AsyncIO: QemuDiskAsyncIO_Native, + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{"sata3": "test:100/base-100-disk-1.raw/0/vm-0-disk-23.raw,aio=native,backup=0,replicate=0"}, + }, + {name: "Update Disk.Sata.Disk_X.Disk DELETE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: sataBase}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{}}}}, + output: map[string]interface{}{"delete": "sata4"}, + }, + {name: "Update Disk.Sata.Disk_X.Disk MIGRATE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test1", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test2", + }}}}}, + output: map[string]interface{}{"sata5": "test2:0/vm-0-disk-23.raw,backup=0,replicate=0"}, + }, + {name: "Update Disk.Sata.Disk_X.Disk MIGRATE LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test1", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test2", + }}}}}, + output: map[string]interface{}{"sata5": "test2:0/vm-0-disk-23.raw,backup=0,replicate=0"}, + }, + {name: "Update Disk.Sata.Disk_X.Disk RESIZE DOWN", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Size: 9, + Storage: "test", + }}}}}, + output: map[string]interface{}{"sata0": "test:9,backup=0,format=raw,replicate=0"}, + }, + {name: "Update Disk.Sata.Disk_X.Disk RESIZE DOWN LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Size: 9, + Storage: "test", + }}}}}, + output: map[string]interface{}{"sata0": "test:9,backup=0,format=raw,replicate=0"}, + }, + {name: "Update Disk.Sata.Disk_X.Disk RESIZE UP", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Size: 11, + Storage: "test", + }}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.Sata.Disk_X.Disk RESIZE UP LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Size: 11, + Storage: "test", + }}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.Sata.Disk_X.Disk SAME", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: sataBase}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.Sata.Disk_X.Disk.Format CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Qcow2, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{"sata3": "test:0/vm-0-disk-23.qcow2,backup=0,replicate=0"}, + }, + {name: "Update Disk.Sata.Disk_X.Disk.Format CHANGE LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Qcow2, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{"sata3": "test:0/vm-0-disk-23.qcow2,backup=0,replicate=0"}, + }, + // Update Disk.Sata.Disk_X.Passthrough + {name: "Update Disk.Sata.Disk_X.Passthrough CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + File: "/dev/disk/sda", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + AsyncIO: QemuDiskAsyncIO_Native, + File: "/dev/disk/sda", + }}}}}, + output: map[string]interface{}{"sata0": "/dev/disk/sda,aio=native,backup=0,replicate=0"}, + }, + {name: "Update Disk.Sata.Disk_X.Passthrough SAME", + currentConfig: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + File: "/dev/disk/sda", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + File: "/dev/disk/sda", + }}}}}, + output: map[string]interface{}{}, + }, + // Update Disk.Scsi + {name: "Update Disk.Scsi.Disk_X DELETE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{}}}}, + output: map[string]interface{}{"delete": "scsi0"}, + }, + // Update Disk.Scsi.Disk_X.CdRom + {name: "Update Disk.Scsi.Disk_X.CdRom CHANGE ISO TO None", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_1: &QemuScsiStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_1: &QemuScsiStorage{CdRom: &QemuCdRom{}}}}}, + output: map[string]interface{}{"scsi1": "none,media=cdrom"}, + }, + {name: "Update Disk.Scsi.Disk_X.CdRom CHANGE ISO TO Passthrough", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_2: &QemuScsiStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_2: &QemuScsiStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + output: map[string]interface{}{"scsi2": "cdrom,media=cdrom"}, + }, + {name: "Update Disk.Scsi.Disk_X.CdRom CHANGE None TO ISO", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_3: &QemuScsiStorage{CdRom: &QemuCdRom{}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_3: &QemuScsiStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + output: map[string]interface{}{"scsi3": "Test:iso/test.iso,media=cdrom"}, + }, + {name: "Update Disk.Scsi.Disk_X.CdRom CHANGE None TO Passthrough", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_4: &QemuScsiStorage{CdRom: &QemuCdRom{}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_4: &QemuScsiStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + output: map[string]interface{}{"scsi4": "cdrom,media=cdrom"}, + }, + {name: "Update Disk.Scsi.Disk_X.CdRom CHANGE Passthrough TO ISO", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_5: &QemuScsiStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_5: &QemuScsiStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + output: map[string]interface{}{"scsi5": "Test:iso/test.iso,media=cdrom"}, + }, + {name: "Update Disk.Scsi.Disk_X.CdRom CHANGE Passthrough TO None", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_6: &QemuScsiStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_6: &QemuScsiStorage{CdRom: &QemuCdRom{}}}}}, + output: map[string]interface{}{"scsi6": "none,media=cdrom"}, + }, + {name: "Update Disk.Scsi.Disk_X.CdRom DELETE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_7: &QemuScsiStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_7: &QemuScsiStorage{}}}}, + output: map[string]interface{}{"delete": "scsi7"}, + }, + {name: "Update Disk.Scsi.Disk_X.CdRom SAME", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_8: &QemuScsiStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_8: &QemuScsiStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.Scsi.Disk_X.CdRom.Iso.File CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_9: &QemuScsiStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_9: &QemuScsiStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test2.iso", Storage: "Test"}}}}}}, + output: map[string]interface{}{"scsi9": "Test:iso/test2.iso,media=cdrom"}, + }, + {name: "Update Disk.Scsi.Disk_X.CdRom.Iso.Storage CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_10: &QemuScsiStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_10: &QemuScsiStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "NewStorage"}}}}}}, + output: map[string]interface{}{"scsi10": "NewStorage:iso/test.iso,media=cdrom"}, + }, + // Update Disk.Scsi.Disk_X.CloudInit + {name: "Update Disk.Scsi.Disk_X.CloudInit DELETE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_11: &QemuScsiStorage{CloudInit: update_CloudInit}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_11: &QemuScsiStorage{}}}}, + output: map[string]interface{}{"delete": "scsi11"}, + }, + {name: "Update Disk.Scsi.Disk_X.CloudInit SAME", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_12: &QemuScsiStorage{CloudInit: update_CloudInit}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_12: &QemuScsiStorage{CloudInit: update_CloudInit}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.Scsi.Disk_X.CloudInit.Format CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_13: &QemuScsiStorage{CloudInit: &QemuCloudInitDisk{Format: format_Raw, Storage: "Test"}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_13: &QemuScsiStorage{CloudInit: &QemuCloudInitDisk{Format: QemuDiskFormat_Qcow2, Storage: "Test"}}}}}, + output: map[string]interface{}{"scsi13": "Test:cloudinit,format=qcow2"}, + }, + {name: "Update Disk.Scsi.Disk_X.CloudInit.Storage CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_14: &QemuScsiStorage{CloudInit: &QemuCloudInitDisk{Format: format_Raw, Storage: "Test"}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_14: &QemuScsiStorage{CloudInit: &QemuCloudInitDisk{Format: format_Raw, Storage: "NewStorage"}}}}}, + output: map[string]interface{}{"scsi14": "NewStorage:cloudinit,format=raw"}, + }, + // Update Disk.Scsi.Disk_X.Disk + {name: "Update Disk.Scsi.Disk_X.Disk CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_15: &QemuScsiStorage{Disk: &QemuScsiDisk{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_15: &QemuScsiStorage{Disk: &QemuScsiDisk{ + AsyncIO: QemuDiskAsyncIO_Native, + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{"scsi15": "test:0/vm-0-disk-23.raw,aio=native,backup=0,replicate=0"}, + }, + {name: "Update Disk.Scsi.Disk_X.Disk CHANGE LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_15: &QemuScsiStorage{Disk: &QemuScsiDisk{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_15: &QemuScsiStorage{Disk: &QemuScsiDisk{ + AsyncIO: QemuDiskAsyncIO_Native, + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{"scsi15": "test:100/base-100-disk-1.raw/0/vm-0-disk-23.raw,aio=native,backup=0,replicate=0"}, + }, + {name: "Update Disk.Scsi.Disk_X.Disk DELETE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_16: scsiBase}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_16: &QemuScsiStorage{}}}}, + output: map[string]interface{}{"delete": "scsi16"}, + }, + {name: "Update Disk.Scsi.Disk_X.Disk MIGRATE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_17: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test1", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_17: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test2", + }}}}}, + output: map[string]interface{}{"scsi17": "test2:0/vm-0-disk-23.raw,backup=0,replicate=0"}, + }, + {name: "Update Disk.Scsi.Disk_X.Disk MIGRATE Linked Clone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_17: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test1", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_17: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test2", + }}}}}, + output: map[string]interface{}{"scsi17": "test2:0/vm-0-disk-23.raw,backup=0,replicate=0"}, + }, + {name: "Update Disk.Scsi.Disk_X.Disk RESIZE DOWN", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_18: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_18: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Size: 9, + Storage: "test", + }}}}}, + output: map[string]interface{}{"scsi18": "test:9,backup=0,format=raw,replicate=0"}, + }, + {name: "Update Disk.Scsi.Disk_X.Disk RESIZE DOWN LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_18: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_18: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Size: 9, + Storage: "test", + }}}}}, + output: map[string]interface{}{"scsi18": "test:9,backup=0,format=raw,replicate=0"}, + }, + {name: "Update Disk.Scsi.Disk_X.Disk RESIZE UP", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_19: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_19: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Size: 11, + Storage: "test", + }}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.Scsi.Disk_X.Disk RESIZE UP LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_19: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_19: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Size: 11, + Storage: "test", + }}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.Scsi.Disk_X.Disk SAME", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_20: scsiBase}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_20: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.Scsi.Disk_X.Disk.Format CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_21: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_21: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Qcow2, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{"scsi21": "test:0/vm-0-disk-23.qcow2,backup=0,replicate=0"}, + }, + {name: "Update Disk.Scsi.Disk_X.Disk.Format CHANGE LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_21: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_21: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Qcow2, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{"scsi21": "test:0/vm-0-disk-23.qcow2,backup=0,replicate=0"}, + }, + // Update Disk.Scsi.Disk_X.Passthrough + {name: "Update Disk.Scsi.Disk_X.Passthrough CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + File: "/dev/disk/sda", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + AsyncIO: QemuDiskAsyncIO_Native, + File: "/dev/disk/sda", + }}}}}, + output: map[string]interface{}{"scsi0": "/dev/disk/sda,aio=native,backup=0,replicate=0"}, + }, + {name: "Update Disk.Scsi.Disk_X.Passthrough SAME", + currentConfig: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_1: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + File: "/dev/disk/sda", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_1: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + File: "/dev/disk/sda", + }}}}}, + output: map[string]interface{}{}, + }, + // Update Disk.VirtIO + {name: "Update Disk.VirtIO.Disk_X DELETE", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{}}}}, + output: map[string]interface{}{"delete": "virtio0"}, + }, + // Update Disk.VirtIO.Disk_X.CdRom + {name: "Update Disk.VirtIO.Disk_X.CdRom CHANGE ISO TO None", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{CdRom: &QemuCdRom{}}}}}, + output: map[string]interface{}{"virtio1": "none,media=cdrom"}, + }, + {name: "Update Disk.VirtIO.Disk_X.CdRom CHANGE ISO TO Passthrough", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + output: map[string]interface{}{"virtio2": "cdrom,media=cdrom"}, + }, + {name: "Update Disk.VirtIO.Disk_X.CdRom CHANGE None TO ISO", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{CdRom: &QemuCdRom{}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + output: map[string]interface{}{"virtio3": "Test:iso/test.iso,media=cdrom"}, + }, + {name: "Update Disk.VirtIO.Disk_X.CdRom CHANGE None TO Passthrough", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_4: &QemuVirtIOStorage{CdRom: &QemuCdRom{}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_4: &QemuVirtIOStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + output: map[string]interface{}{"virtio4": "cdrom,media=cdrom"}, + }, + {name: "Update Disk.VirtIO.Disk_X.CdRom CHANGE Passthrough TO ISO", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + output: map[string]interface{}{"virtio5": "Test:iso/test.iso,media=cdrom"}, + }, + {name: "Update Disk.VirtIO.Disk_X.CdRom CHANGE Passthrough TO None", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_6: &QemuVirtIOStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_6: &QemuVirtIOStorage{CdRom: &QemuCdRom{}}}}}, + output: map[string]interface{}{"virtio6": "none,media=cdrom"}, + }, + {name: "Update Disk.VirtIO.Disk_X.CdRom DELETE", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_7: &QemuVirtIOStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_7: &QemuVirtIOStorage{}}}}, + output: map[string]interface{}{"delete": "virtio7"}, + }, + {name: "Update Disk.VirtIO.Disk_X.CdRom SAME", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_8: &QemuVirtIOStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_8: &QemuVirtIOStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.VirtIO.Disk_X.CdRom.Iso.File CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_9: &QemuVirtIOStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_9: &QemuVirtIOStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test2.iso", Storage: "Test"}}}}}}, + output: map[string]interface{}{"virtio9": "Test:iso/test2.iso,media=cdrom"}, + }, + {name: "Update Disk.VirtIO.Disk_X.CdRom.Iso.Storage CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_10: &QemuVirtIOStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "Test"}}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_10: &QemuVirtIOStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test.iso", Storage: "NewStorage"}}}}}}, + output: map[string]interface{}{"virtio10": "NewStorage:iso/test.iso,media=cdrom"}, + }, + // Update Disk.VirtIO.Disk_X.CloudInit + {name: "Update Disk.VirtIO.Disk_X.CloudInit DELETE", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_11: &QemuVirtIOStorage{CloudInit: update_CloudInit}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_11: &QemuVirtIOStorage{}}}}, + output: map[string]interface{}{"delete": "virtio11"}, + }, + {name: "Update Disk.VirtIO.Disk_X.CloudInit SAME", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_12: &QemuVirtIOStorage{CloudInit: update_CloudInit}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_12: &QemuVirtIOStorage{CloudInit: update_CloudInit}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.VirtIO.Disk_X.CloudInit.Format CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_13: &QemuVirtIOStorage{CloudInit: &QemuCloudInitDisk{Format: format_Raw, Storage: "Test"}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_13: &QemuVirtIOStorage{CloudInit: &QemuCloudInitDisk{Format: QemuDiskFormat_Qcow2, Storage: "Test"}}}}}, + output: map[string]interface{}{"virtio13": "Test:cloudinit,format=qcow2"}, + }, + {name: "Update Disk.VirtIO.Disk_X.CloudInit.Storage CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_14: &QemuVirtIOStorage{CloudInit: &QemuCloudInitDisk{Format: format_Raw, Storage: "Test"}}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_14: &QemuVirtIOStorage{CloudInit: &QemuCloudInitDisk{Format: format_Raw, Storage: "NewStorage"}}}}}, + output: map[string]interface{}{"virtio14": "NewStorage:cloudinit,format=raw"}, + }, + // Update Disk.VirtIO.Disk_X.Disk + {name: "Update Disk.VirtIO.Disk_X.Disk CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_15: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_15: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + AsyncIO: QemuDiskAsyncIO_Native, + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{"virtio15": "test:0/vm-0-disk-23.raw,aio=native,backup=0,replicate=0"}, + }, + {name: "Update Disk.VirtIO.Disk_X.Disk CHANGE LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_15: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_15: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + AsyncIO: QemuDiskAsyncIO_Native, + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{"virtio15": "test:100/base-100-disk-1.raw/0/vm-0-disk-23.raw,aio=native,backup=0,replicate=0"}, + }, + {name: "Update Disk.VirtIO.Disk_X.Disk DELETE", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: virtioBase}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{}}}}, + output: map[string]interface{}{"delete": "virtio0"}, + }, + {name: "Update Disk.VirtIO.Disk_X.Disk MIGRATE", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test1", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test2", + }}}}}, + output: map[string]interface{}{"virtio1": "test2:0/vm-0-disk-23.raw,backup=0,replicate=0"}, + }, + {name: "Update Disk.VirtIO.Disk_X.Disk MIGRATE LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test1", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test2", + }}}}}, + output: map[string]interface{}{"virtio1": "test2:0/vm-0-disk-23.raw,backup=0,replicate=0"}, + }, + {name: "Update Disk.VirtIO.Disk_X.Disk RESIZE DOWN", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Size: 9, + Storage: "test", + }}}}}, + output: map[string]interface{}{"virtio2": "test:9,backup=0,format=raw,replicate=0"}, + }, + {name: "Update Disk.VirtIO.Disk_X.Disk RESIZE DOWN LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Size: 9, + Storage: "test", + }}}}}, + output: map[string]interface{}{"virtio2": "test:9,backup=0,format=raw,replicate=0"}, + }, + {name: "Update Disk.VirtIO.Disk_X.Disk RESIZE UP", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Size: 11, + Storage: "test", + }}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.VirtIO.Disk_X.Disk RESIZE UP LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Size: 11, + Storage: "test", + }}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.VirtIO.Disk_X.Disk SAME", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_4: virtioBase}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_4: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{}, + }, + {name: "Update Disk.VirtIO.Disk_X.Disk.Format CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Qcow2, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{"virtio5": "test:0/vm-0-disk-23.qcow2,backup=0,replicate=0"}, + }, + {name: "Update Disk.VirtIO.Disk_X.Disk.Format CHANGE LinkedClone", + currentConfig: ConfigQemu{ + LinkedVmId: 100, + Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Id: 23, + LinkedDiskId: &uint1, + Size: 10, + Storage: "test", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Qcow2, + Size: 10, + Storage: "test", + }}}}}, + output: map[string]interface{}{"virtio5": "test:0/vm-0-disk-23.qcow2,backup=0,replicate=0"}, + }, + // Update Disk.VirtIO.Disk_X.Passthrough + {name: "Update Disk.VirtIO.Disk_X.Passthrough CHANGE", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + File: "/dev/disk/sda", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + AsyncIO: QemuDiskAsyncIO_Native, + File: "/dev/disk/sda", + }}}}}, + output: map[string]interface{}{"virtio0": "/dev/disk/sda,aio=native,backup=0,replicate=0"}, + }, + {name: "Update Disk.VirtIO.Disk_X.Passthrough SAME", + currentConfig: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + File: "/dev/disk/sda", + }}}}}, + config: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + File: "/dev/disk/sda", + }}}}}, + output: map[string]interface{}{}, + }, + // Update Iso + {name: "Update Iso nil", + currentConfig: ConfigQemu{Iso: &IsoFile{Storage: "test", File: "file.iso"}}, + config: &ConfigQemu{Iso: nil}, + output: map[string]interface{}{}, + }, + {name: "Update Iso SAME", + currentConfig: ConfigQemu{Iso: &IsoFile{Storage: "test", File: "file.iso"}}, + config: &ConfigQemu{Iso: &IsoFile{Storage: "test", File: "file.iso"}}, + output: map[string]interface{}{"ide2": "test:iso/file.iso,media=cdrom"}, + }, + {name: "Update Iso.File", + currentConfig: ConfigQemu{Iso: &IsoFile{Storage: "test", File: "file.iso"}}, + config: &ConfigQemu{Iso: &IsoFile{Storage: "test", File: "file2.iso"}}, + output: map[string]interface{}{"ide2": "test:iso/file2.iso,media=cdrom"}, + }, + {name: "Update Iso.Storage", + currentConfig: ConfigQemu{Iso: &IsoFile{Storage: "test", File: "file.iso"}}, + config: &ConfigQemu{Iso: &IsoFile{Storage: "NewStorage", File: "file.iso"}}, + output: map[string]interface{}{"ide2": "NewStorage:iso/file.iso,media=cdrom"}, + }, + } + for _, test := range tests { + t.Run(test.name, func(*testing.T) { + reboot, tmpParams, _ := test.config.mapToApiValues(test.currentConfig) + require.Equal(t, test.output, tmpParams, test.name) + require.Equal(t, test.reboot, reboot, test.name) + }) + } +} + +func Test_ConfigQemu_mapToStruct(t *testing.T) { + uint1 := uint(1) + uint2 := uint(2) + uint31 := uint(31) + uint47 := uint(47) + uint53 := uint(53) + tests := []struct { + name string + input map[string]interface{} + output *ConfigQemu + err error + }{ + // TODO add test cases for all other items of ConfigQemu{} + // Disks Ide CdRom + {name: "Disks Ide CdRom none", + input: map[string]interface{}{"ide1": "none,media=cdrom"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{CdRom: &QemuCdRom{}}}}}, + }, + {name: "Disks Ide CdRom passthrough", + input: map[string]interface{}{"ide2": "cdrom,media=cdrom"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + }, + {name: "Disks Ide CdRom iso", + input: map[string]interface{}{"ide3": "local:iso/debian-11.0.0-amd64-netinst.iso,media=cdrom,size=377M"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{CdRom: &QemuCdRom{Iso: &IsoFile{ + File: "debian-11.0.0-amd64-netinst.iso", + Storage: "local", + Size: "377M", + }}}}}}, + }, + // Disks Ide CloudInit + {name: "Disks Ide CloudInit", + input: map[string]interface{}{"ide0": "Test:100/vm-100-cloudinit.raw,media=cdrom"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{CloudInit: &QemuCloudInitDisk{ + Format: QemuDiskFormat_Raw, + Storage: "Test", + }}}}}, + }, + // Disks Ide Disk + {name: "Disks Ide Disk", + input: map[string]interface{}{"ide0": "test2:100/vm-100-disk-53.qcow2"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk ALL", + input: map[string]interface{}{"ide1": "test2:100/vm-100-disk-53.qcow2,aio=io_uring,backup=0,cache=writethrough,discard=on,iops_rd=12,iops_rd_max=13,iops_rd_max_length=4,iops_wr=15,iops_wr_max=14,iops_wr_max_length=5,mbps_rd=1.46,mbps_rd_max=3.57,mbps_wr=2.68,mbps_wr_max=4.55,replicate=0,serial=disk-9763,size=1032G,ssd=1,wwn=0x500F753600A987E1"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Backup: false, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.57, Concurrent: 1.46}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.55, Concurrent: 2.68}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 13, BurstDuration: 4, Concurrent: 12}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 14, BurstDuration: 5, Concurrent: 15}, + }, + }, + Cache: QemuDiskCache_WriteThrough, + Discard: true, + EmulateSSD: true, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: false, + Serial: "disk-9763", + Size: 1032, + Storage: "test2", + WorldWideName: "0x500F753600A987E1", + }}}}}, + }, + {name: "Disks Ide Disk ALL LinkedClone", + input: map[string]interface{}{"ide1": "test2:110/base-110-disk-1.qcow2/100/vm-100-disk-53.qcow2,aio=io_uring,backup=0,cache=writethrough,discard=on,iops_rd=12,iops_rd_max=13,iops_rd_max_length=4,iops_wr=15,iops_wr_max=14,iops_wr_max_length=5,mbps_rd=1.46,mbps_rd_max=3.57,mbps_wr=2.68,mbps_wr_max=4.55,replicate=0,serial=disk-9763,size=1032G,ssd=1,wwn=0x500679CE00B1DAF4"}, + output: &ConfigQemu{ + LinkedVmId: 110, + Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Backup: false, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.57, Concurrent: 1.46}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.55, Concurrent: 2.68}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 13, BurstDuration: 4, Concurrent: 12}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 14, BurstDuration: 5, Concurrent: 15}, + }, + }, + Cache: QemuDiskCache_WriteThrough, + Discard: true, + EmulateSSD: true, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + LinkedDiskId: &uint1, + Replicate: false, + Serial: "disk-9763", + Size: 1032, + Storage: "test2", + WorldWideName: "0x500679CE00B1DAF4", + }}}}}, + }, + {name: "Disks Ide Disk aio", + input: map[string]interface{}{"ide2": "test2:100/vm-100-disk-53.qcow2,aio=io_uring"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk backup", + input: map[string]interface{}{"ide3": "test2:100/vm-100-disk-53.qcow2,backup=0"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: false, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk cache", + input: map[string]interface{}{"ide0": "test2:100/vm-100-disk-53.qcow2,cache=writethrough"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Cache: QemuDiskCache_WriteThrough, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk discard", + input: map[string]interface{}{"ide1": "test2:100/vm-100-disk-53.qcow2,discard=on"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Discard: true, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk iops_rd", + input: map[string]interface{}{"ide2": "test2:100/vm-100-disk-53.qcow2,iops_rd=12"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 12}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk iops_rd_max", + input: map[string]interface{}{"ide3": "test2:100/vm-100-disk-53.qcow2,iops_rd_max=13"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 13}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk iops_rd_max_length", + input: map[string]interface{}{"ide3": "test2:100/vm-100-disk-53.qcow2,iops_rd_max_length=2"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 2}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk iops_wr", + input: map[string]interface{}{"ide0": "test2:100/vm-100-disk-53.qcow2,iops_wr=15"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 15}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk iops_wr_max", + input: map[string]interface{}{"ide1": "test2:100/vm-100-disk-53.qcow2,iops_wr_max=14"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 14}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk iops_wr_max_length", + input: map[string]interface{}{"ide1": "test2:100/vm-100-disk-53.qcow2,iops_wr_max_length=3"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 3}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk mbps_rd", + input: map[string]interface{}{"ide2": "test2:100/vm-100-disk-53.qcow2,mbps_rd=1.46"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 1.46}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk mbps_rd_max", + input: map[string]interface{}{"ide3": "test2:100/vm-100-disk-53.qcow2,mbps_rd_max=3.57"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.57}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk mbps_wr", + input: map[string]interface{}{"ide0": "test2:100/vm-100-disk-53.qcow2,mbps_wr=2.68"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 2.68}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk mbps_wr_max", + input: map[string]interface{}{"ide1": "test2:100/vm-100-disk-53.qcow2,mbps_wr_max=4.55"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.55}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk replicate", + input: map[string]interface{}{"ide2": "test2:100/vm-100-disk-53.qcow2,replicate=0"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: false, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk serial", + input: map[string]interface{}{"ide3": "test2:100/vm-100-disk-53.qcow2,serial=disk-9763"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Serial: "disk-9763", + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk size G", + input: map[string]interface{}{"ide0": "test2:100/vm-100-disk-53.qcow2,size=1032G"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Size: 1032, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk size T", + input: map[string]interface{}{"ide0": "test2:100/vm-100-disk-53.qcow2,size=2T"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Size: 2048, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk ssd", + input: map[string]interface{}{"ide1": "test2:100/vm-100-disk-53.qcow2,ssd=1"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + EmulateSSD: true, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Ide Disk wwn", + input: map[string]interface{}{"ide1": "test2:100/vm-100-disk-53.qcow2,wwn=0x500DB82100C6FA59"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint53, + Replicate: true, + Storage: "test2", + WorldWideName: "0x500DB82100C6FA59", + }}}}}, + }, + // Disks Ide Passthrough + {name: "Disks Ide Passthrough", + input: map[string]interface{}{"ide0": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Ide Passthrough All", + input: map[string]interface{}{"ide1": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,aio=threads,backup=0,cache=unsafe,discard=on,iops_rd=10,iops_rd_max=12,iops_rd_max_length=4,iops_wr=11,iops_wr_max=13,iops_wr_max_length=5,mbps_rd=1.51,mbps_rd_max=3.51,mbps_wr=2.51,mbps_wr_max=4.51,replicate=0,serial=disk-9763,size=1G,ssd=1,wwn=0x500CBE4300D978A6"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + AsyncIO: QemuDiskAsyncIO_Threads, + Backup: false, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51, Concurrent: 1.51}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51, Concurrent: 2.51}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12, BurstDuration: 4, Concurrent: 10}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13, BurstDuration: 5, Concurrent: 11}, + }, + }, + Cache: QemuDiskCache_Unsafe, + Discard: true, + EmulateSSD: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: false, + Serial: "disk-9763", + Size: 1, + WorldWideName: "0x500CBE4300D978A6", + }}}}}, + }, + {name: "Disks Ide Passthrough aio", + input: map[string]interface{}{"ide2": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,aio=threads"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + AsyncIO: QemuDiskAsyncIO_Threads, + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Ide Passthrough backup", + input: map[string]interface{}{"ide3": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,backup=0"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: false, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Ide Passthrough cache", + input: map[string]interface{}{"ide0": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,cache=unsafe"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + Cache: QemuDiskCache_Unsafe, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Ide Passthrough discard", + input: map[string]interface{}{"ide1": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,discard=on"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + Discard: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Ide Passthrough iops_rd", + input: map[string]interface{}{"ide2": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_rd=10"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 10}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Ide Passthrough iops_rd_max", + input: map[string]interface{}{"ide3": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_rd_max=12"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Ide Passthrough iops_rd_max_length", + input: map[string]interface{}{"ide3": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_rd_max_length=2"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 2}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Ide Passthrough iops_wr", + input: map[string]interface{}{"ide0": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_wr=11"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 11}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Ide Passthrough iops_wr_max", + input: map[string]interface{}{"ide1": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_wr_max=13"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Ide Passthrough iops_wr_max_length", + input: map[string]interface{}{"ide1": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_wr_max_length=3"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 3}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Ide Passthrough mbps_rd", + input: map[string]interface{}{"ide2": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,mbps_rd=1.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 1.51}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Ide Passthrough mbps_rd_max", + input: map[string]interface{}{"ide3": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,mbps_rd_max=3.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Ide Passthrough mbps_wr", + input: map[string]interface{}{"ide0": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,mbps_wr=2.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 2.51}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Ide Passthrough mbps_wr_max", + input: map[string]interface{}{"ide1": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,mbps_wr_max=4.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Ide Passthrough replicate", + input: map[string]interface{}{"ide2": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,replicate=0"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: false, + }}}}}, + }, + {name: "Disks Ide Passthrough serial", + input: map[string]interface{}{"ide3": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,serial=disk-9763"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + Serial: "disk-9763", + }}}}}, + }, + {name: "Disks Ide Passthrough size", + input: map[string]interface{}{"ide0": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,size=1G"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + Size: 1, + }}}}}, + }, + {name: "Disks Ide Passthrough ssd", + input: map[string]interface{}{"ide1": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,ssd=1"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + EmulateSSD: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Ide Passthrough wwn", + input: map[string]interface{}{"ide1": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,wwn=0x5005AC1200F643B8"}, + output: &ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + WorldWideName: "0x5005AC1200F643B8", + }}}}}, + }, + // Disks Sata CdRom + {name: "Disks Sata CdRom none", + input: map[string]interface{}{"sata5": "none,media=cdrom"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{CdRom: &QemuCdRom{}}}}}, + }, + {name: "Disks Sata CdRom passthrough", + input: map[string]interface{}{"sata4": "cdrom,media=cdrom"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + }, + {name: "Disks Sata CdRom iso", + input: map[string]interface{}{"sata3": "local:iso/debian-11.0.0-amd64-netinst.iso,media=cdrom,size=377M"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{CdRom: &QemuCdRom{Iso: &IsoFile{ + File: "debian-11.0.0-amd64-netinst.iso", + Storage: "local", + Size: "377M", + }}}}}}, + }, + // Disks Sata CloudInit + {name: "Disks Sata CloudInit", + input: map[string]interface{}{"sata0": "Test:100/vm-100-cloudinit.raw,media=cdrom"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{CloudInit: &QemuCloudInitDisk{ + Format: QemuDiskFormat_Raw, + Storage: "Test", + }}}}}, + }, + // Disks Sata Disk + {name: "Disks Sata Disk", + input: map[string]interface{}{"sata0": "test2:100/vm-100-disk-47.qcow2"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk ALL", + input: map[string]interface{}{"sata1": "test2:100/vm-100-disk-47.qcow2,aio=native,backup=0,cache=none,discard=on,iops_rd=10,iops_rd_max=12,iops_rd_max_length=4,iops_wr=11,iops_wr_max=13,iops_wr_max_length=5,mbps_rd=1.51,mbps_rd_max=3.51,mbps_wr=2.51,mbps_wr_max=4.51,replicate=0,serial=disk-9763,size=32G,ssd=1,wwn=0x500DFA8900E3C641"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{ + AsyncIO: QemuDiskAsyncIO_Native, + Backup: false, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51, Concurrent: 1.51}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51, Concurrent: 2.51}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12, BurstDuration: 4, Concurrent: 10}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13, BurstDuration: 5, Concurrent: 11}, + }, + }, + Cache: QemuDiskCache_None, + Discard: true, + EmulateSSD: true, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: false, + Serial: "disk-9763", + Size: 32, + Storage: "test2", + WorldWideName: "0x500DFA8900E3C641", + }}}}}, + }, + {name: "Disks Sata Disk ALL LinkedClone", + input: map[string]interface{}{"sata1": "test2:110/base-110-disk-1.qcow2/100/vm-100-disk-47.qcow2,aio=native,backup=0,cache=none,discard=on,iops_rd=10,iops_rd_max=12,iops_rd_max_length=4,iops_wr=11,iops_wr_max=13,iops_wr_max_length=5,mbps_rd=1.51,mbps_rd_max=3.51,mbps_wr=2.51,mbps_wr_max=4.51,replicate=0,serial=disk-9763,size=32G,ssd=1,wwn=0x5003B97600A8F2D4"}, + output: &ConfigQemu{ + LinkedVmId: 110, + Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{ + AsyncIO: QemuDiskAsyncIO_Native, + Backup: false, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51, Concurrent: 1.51}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51, Concurrent: 2.51}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12, BurstDuration: 4, Concurrent: 10}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13, BurstDuration: 5, Concurrent: 11}, + }, + }, + Cache: QemuDiskCache_None, + Discard: true, + EmulateSSD: true, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + LinkedDiskId: &uint1, + Replicate: false, + Serial: "disk-9763", + Size: 32, + Storage: "test2", + WorldWideName: "0x5003B97600A8F2D4", + }}}}}, + }, + {name: "Disks Sata Disk aio", + input: map[string]interface{}{"sata2": "test2:100/vm-100-disk-47.qcow2,aio=native"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Disk: &QemuSataDisk{ + AsyncIO: QemuDiskAsyncIO_Native, + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk backup", + input: map[string]interface{}{"sata3": "test2:100/vm-100-disk-47.qcow2,backup=0"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: false, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk cache", + input: map[string]interface{}{"sata4": "test2:100/vm-100-disk-47.qcow2,cache=none"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Cache: QemuDiskCache_None, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk discard", + input: map[string]interface{}{"sata5": "test2:100/vm-100-disk-47.qcow2,discard=on"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Discard: true, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk iops_rd", + input: map[string]interface{}{"sata0": "test2:100/vm-100-disk-47.qcow2,iops_rd=10"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 10}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk iops_rd_max", + input: map[string]interface{}{"sata1": "test2:100/vm-100-disk-47.qcow2,iops_rd_max=12"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk iops_rd_max_length", + input: map[string]interface{}{"sata1": "test2:100/vm-100-disk-47.qcow2,iops_rd_max_length=2"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 2}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk iops_wr", + input: map[string]interface{}{"sata2": "test2:100/vm-100-disk-47.qcow2,iops_wr=11"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 11}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk iops_wr_max", + input: map[string]interface{}{"sata3": "test2:100/vm-100-disk-47.qcow2,iops_wr_max=13"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk iops_wr_max_length", + input: map[string]interface{}{"sata3": "test2:100/vm-100-disk-47.qcow2,iops_wr_max_length=3"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 3}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk mbps_rd", + input: map[string]interface{}{"sata4": "test2:100/vm-100-disk-47.qcow2,mbps_rd=1.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 1.51}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk mbps_rd_max", + input: map[string]interface{}{"sata5": "test2:100/vm-100-disk-47.qcow2,mbps_rd_max=3.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk mbps_wr", + input: map[string]interface{}{"sata0": "test2:100/vm-100-disk-47.qcow2,mbps_wr=2.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 2.51}, + }, + }, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk mbps_wr_max", + input: map[string]interface{}{"sata1": "test2:100/vm-100-disk-47.qcow2,mbps_wr_max=4.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk replicate", + input: map[string]interface{}{"sata2": "test2:100/vm-100-disk-47.qcow2,replicate=0"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: false, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk serial", + input: map[string]interface{}{"sata3": "test2:100/vm-100-disk-47.qcow2,serial=disk-9763"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Serial: "disk-9763", + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk size G", + input: map[string]interface{}{"sata4": "test2:100/vm-100-disk-47.qcow2,size=32G"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Size: 32, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk size T", + input: map[string]interface{}{"sata4": "test2:100/vm-100-disk-47.qcow2,size=3T"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Size: 3072, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk ssd", + input: map[string]interface{}{"sata5": "test2:100/vm-100-disk-47.qcow2,ssd=1"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + EmulateSSD: true, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks Sata Disk wwn", + input: map[string]interface{}{"sata5": "test2:100/vm-100-disk-47.qcow2,wwn=0x5001E48A00D567C9"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Disk: &QemuSataDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint47, + Replicate: true, + Storage: "test2", + WorldWideName: "0x5001E48A00D567C9", + }}}}}, + }, + // Disks Sata Passthrough + {name: "Disks Sata Passthrough", + input: map[string]interface{}{"sata1": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Sata Passthrough All", + input: map[string]interface{}{"sata1": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,aio=io_uring,backup=0,cache=directsync,discard=on,iops_rd=10,iops_rd_max=12,iops_rd_max_length=5,iops_wr=11,iops_wr_max=13,iops_wr_max_length=4,mbps_rd=1.51,mbps_rd_max=3.51,mbps_wr=2.51,mbps_wr_max=4.51,replicate=0,serial=disk-9763,size=1G,ssd=1,wwn=500E9FBC00F2A6D3"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Backup: false, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51, Concurrent: 1.51}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51, Concurrent: 2.51}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12, BurstDuration: 5, Concurrent: 10}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13, BurstDuration: 4, Concurrent: 11}, + }, + }, + Cache: QemuDiskCache_DirectSync, + Discard: true, + EmulateSSD: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: false, + Serial: "disk-9763", + Size: 1, + WorldWideName: "500E9FBC00F2A6D3", + }}}}}, + }, + {name: "Disks Sata Passthrough aio", + input: map[string]interface{}{"sata2": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,aio=io_uring"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Sata Passthrough backup", + input: map[string]interface{}{"sata3": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,backup=0"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: false, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Sata Passthrough cache", + input: map[string]interface{}{"sata4": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,cache=directsync"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + Cache: QemuDiskCache_DirectSync, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Sata Passthrough discard", + input: map[string]interface{}{"sata5": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,discard=on"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + Discard: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Sata Passthrough iops_rd", + input: map[string]interface{}{"sata0": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_rd=10"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 10}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Sata Passthrough iops_rd_max", + input: map[string]interface{}{"sata1": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_rd_max=12"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Sata Passthrough iops_rd_max_length", + input: map[string]interface{}{"sata1": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_rd_max_length=2"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 2}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Sata Passthrough iops_wr", + input: map[string]interface{}{"sata2": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_wr=11"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 11}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Sata Passthrough iops_wr_max", + input: map[string]interface{}{"sata3": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_wr_max=13"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Sata Passthrough iops_wr_max_length", + input: map[string]interface{}{"sata3": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_wr_max_length=3"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 3}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Sata Passthrough mbps_rd", + input: map[string]interface{}{"sata4": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,mbps_rd=1.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 1.51}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Sata Passthrough mbps_rd_max", + input: map[string]interface{}{"sata5": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,mbps_rd_max=3.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Sata Passthrough mbps_wr", + input: map[string]interface{}{"sata0": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,mbps_wr=2.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 2.51}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Sata Passthrough mbps_wr_max", + input: map[string]interface{}{"sata1": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,mbps_wr_max=4.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Sata Passthrough replicate", + input: map[string]interface{}{"sata2": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,replicate=0"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: false, + }}}}}, + }, + {name: "Disks Sata Passthrough serial", + input: map[string]interface{}{"sata3": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,serial=disk-9763"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + Serial: "disk-9763", + }}}}}, + }, + {name: "Disks Sata Passthrough size", + input: map[string]interface{}{"sata4": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,size=1G"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + Size: 1, + }}}}}, + }, + {name: "Disks Sata Passthrough ssd", + input: map[string]interface{}{"sata5": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,ssd=1"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + EmulateSSD: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Sata Passthrough wwn", + input: map[string]interface{}{"sata5": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,wwn=0x5004D2EF00C8B57A"}, + output: &ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + WorldWideName: "0x5004D2EF00C8B57A", + }}}}}, + }, + // Disks Scsi CdRom + {name: "Disks Scsi CdRom none", + input: map[string]interface{}{"scsi30": "none,media=cdrom"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_30: &QemuScsiStorage{CdRom: &QemuCdRom{}}}}}, + }, + {name: "Disks Scsi CdRom passthrough", + input: map[string]interface{}{"scsi29": "cdrom,media=cdrom"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_29: &QemuScsiStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + }, + {name: "Disks Scsi CdRom iso", + input: map[string]interface{}{"scsi28": "local:iso/debian-11.0.0-amd64-netinst.iso,media=cdrom,size=377M"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_28: &QemuScsiStorage{CdRom: &QemuCdRom{Iso: &IsoFile{ + File: "debian-11.0.0-amd64-netinst.iso", + Storage: "local", + Size: "377M", + }}}}}}, + }, + // Disks Scsi CloudInit + {name: "Disks Scsi CloudInit", + input: map[string]interface{}{"scsi0": "Test:100/vm-100-cloudinit.raw,media=cdrom"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{CloudInit: &QemuCloudInitDisk{ + Format: QemuDiskFormat_Raw, + Storage: "Test", + }}}}}, + }, + // Disks Scsi Disk + {name: "Disks Scsi Disk", + input: map[string]interface{}{"scsi0": "test:100/vm-100-disk-2.qcow2"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk ALL", + input: map[string]interface{}{"scsi1": "test:100/vm-100-disk-2.qcow2,aio=threads,backup=0,cache=writeback,discard=on,iops_rd=10,iops_rd_max=12,iops_rd_max_length=4,iops_wr=11,iops_wr_max=13,iops_wr_max_length=5,iothread=1,mbps_rd=1.51,mbps_rd_max=3.51,mbps_wr=2.51,mbps_wr_max=4.51,replicate=0,ro=1,serial=disk-9763,size=32G,ssd=1,wwn=0x500AF18700E9CD25"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_1: &QemuScsiStorage{Disk: &QemuScsiDisk{ + AsyncIO: QemuDiskAsyncIO_Threads, + Backup: false, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51, Concurrent: 1.51}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51, Concurrent: 2.51}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12, BurstDuration: 4, Concurrent: 10}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13, BurstDuration: 5, Concurrent: 11}, + }, + }, + Cache: QemuDiskCache_WriteBack, + Discard: true, + EmulateSSD: true, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + IOThread: true, + ReadOnly: true, + Replicate: false, + Serial: "disk-9763", + Size: 32, + Storage: "test", + WorldWideName: "0x500AF18700E9CD25", + }}}}}, + }, + {name: "Disks Scsi Disk ALL LinkedClone", + input: map[string]interface{}{"scsi1": "test:110/base-110-disk-1.qcow2/100/vm-100-disk-2.qcow2,aio=threads,backup=0,cache=writeback,discard=on,iops_rd=10,iops_rd_max=12,iops_rd_max_length=4,iops_wr=11,iops_wr_max=13,iops_wr_max_length=5,iothread=1,mbps_rd=1.51,mbps_rd_max=3.51,mbps_wr=2.51,mbps_wr_max=4.51,replicate=0,ro=1,serial=disk-9763,size=32G,ssd=1,wwn=0x500879DC00F3BE6A"}, + output: &ConfigQemu{ + LinkedVmId: 110, + Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_1: &QemuScsiStorage{Disk: &QemuScsiDisk{ + AsyncIO: QemuDiskAsyncIO_Threads, + Backup: false, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51, Concurrent: 1.51}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51, Concurrent: 2.51}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12, BurstDuration: 4, Concurrent: 10}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13, BurstDuration: 5, Concurrent: 11}, + }, + }, + Cache: QemuDiskCache_WriteBack, + Discard: true, + EmulateSSD: true, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + IOThread: true, + LinkedDiskId: &uint1, + ReadOnly: true, + Replicate: false, + Serial: "disk-9763", + Size: 32, + Storage: "test", + WorldWideName: "0x500879DC00F3BE6A", + }}}}}, + }, + {name: "Disks Scsi Disk aio", + input: map[string]interface{}{"scsi2": "test:100/vm-100-disk-2.qcow2,aio=threads"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_2: &QemuScsiStorage{Disk: &QemuScsiDisk{ + AsyncIO: QemuDiskAsyncIO_Threads, + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk backup", + input: map[string]interface{}{"scsi3": "test:100/vm-100-disk-2.qcow2,backup=0"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_3: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: false, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk cache", + input: map[string]interface{}{"scsi4": "test:100/vm-100-disk-2.qcow2,cache=writeback"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_4: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Cache: QemuDiskCache_WriteBack, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk discard", + input: map[string]interface{}{"scsi5": "test:100/vm-100-disk-2.qcow2,discard=on"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_5: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Discard: true, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk iops_rd", + input: map[string]interface{}{"scsi6": "test:100/vm-100-disk-2.qcow2,iops_rd=10"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_6: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 10}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk iops_rd_max", + input: map[string]interface{}{"scsi7": "test:100/vm-100-disk-2.qcow2,iops_rd_max=12"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_7: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk iops_rd_max_length", + input: map[string]interface{}{"scsi7": "test:100/vm-100-disk-2.qcow2,iops_rd_max_length=2"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_7: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 2}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk iops_wr", + input: map[string]interface{}{"scsi8": "test:100/vm-100-disk-2.qcow2,iops_wr=11"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_8: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 11}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk iops_wr_max", + input: map[string]interface{}{"scsi9": "test:100/vm-100-disk-2.qcow2,iops_wr_max=13"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_9: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk iops_wr_max_length", + input: map[string]interface{}{"scsi9": "test:100/vm-100-disk-2.qcow2,iops_wr_max_length=3"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_9: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 3}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk iothread", + input: map[string]interface{}{"scsi10": "test:100/vm-100-disk-2.qcow2,iothread=1"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_10: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + IOThread: true, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk mbps_rd", + input: map[string]interface{}{"scsi11": "test:100/vm-100-disk-2.qcow2,mbps_rd=1.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_11: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 1.51}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk mbps_rd_max", + input: map[string]interface{}{"scsi12": "test:100/vm-100-disk-2.qcow2,mbps_rd_max=3.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_12: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk mbps_wr", + input: map[string]interface{}{"scsi13": "test:100/vm-100-disk-2.qcow2,mbps_wr=2.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_13: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 2.51}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk mbps_wr_max", + input: map[string]interface{}{"scsi14": "test:100/vm-100-disk-2.qcow2,mbps_wr_max=4.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_14: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk replicate", + input: map[string]interface{}{"scsi15": "test:100/vm-100-disk-2.qcow2,replicate=0"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_15: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: false, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk ro", + input: map[string]interface{}{"scsi16": "test:100/vm-100-disk-2.qcow2,ro=1"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_16: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + ReadOnly: true, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk serial", + input: map[string]interface{}{"scsi17": "test:100/vm-100-disk-2.qcow2,serial=disk-9763"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_17: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Serial: "disk-9763", + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk size G", + input: map[string]interface{}{"scsi18": "test:100/vm-100-disk-2.qcow2,size=32G"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_18: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Size: 32, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk size T", + input: map[string]interface{}{"scsi18": "test:100/vm-100-disk-2.qcow2,size=4T"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_18: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Size: 4096, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk ssd", + input: map[string]interface{}{"scsi19": "test:100/vm-100-disk-2.qcow2,ssd=1"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_19: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + EmulateSSD: true, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + }}}}}, + }, + {name: "Disks Scsi Disk wwn", + input: map[string]interface{}{"scsi19": "test:100/vm-100-disk-2.qcow2,wwn=0x500E265400A1F3D7"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_19: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint2, + Replicate: true, + Storage: "test", + WorldWideName: "0x500E265400A1F3D7", + }}}}}, + }, + // Disks Scsi Passthrough + {name: "Disks Scsi Passthrough", + input: map[string]interface{}{"scsi0": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough All", + input: map[string]interface{}{"scsi1": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,aio=threads,backup=0,cache=none,discard=on,iops_rd=10,iops_rd_max=12,iops_rd_max_length=4,iops_wr=11,iops_wr_max=13,iops_wr_max_length=5,iothread=1,mbps_rd=1.51,mbps_rd_max=3.51,mbps_wr=2.51,mbps_wr_max=4.51,replicate=0,ro=1,serial=disk-9763,size=1G,ssd=1,wwn=500CB15600D8FE32"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_1: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + AsyncIO: QemuDiskAsyncIO_Threads, + Backup: false, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51, Concurrent: 1.51}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51, Concurrent: 2.51}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12, BurstDuration: 4, Concurrent: 10}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13, BurstDuration: 5, Concurrent: 11}, + }, + }, + Cache: QemuDiskCache_None, + Discard: true, + EmulateSSD: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + IOThread: true, + ReadOnly: true, + Replicate: false, + Serial: "disk-9763", + Size: 1, + WorldWideName: "500CB15600D8FE32", + }}}}}, + }, + {name: "Disks Scsi Passthrough aio", + input: map[string]interface{}{"scsi2": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,aio=threads"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_2: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + AsyncIO: QemuDiskAsyncIO_Threads, + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough backup", + input: map[string]interface{}{"scsi3": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,backup=0"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_3: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: false, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough cache", + input: map[string]interface{}{"scsi4": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,cache=none"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_4: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + Cache: QemuDiskCache_None, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough discard", + input: map[string]interface{}{"scsi5": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,discard=on"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_5: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + Discard: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough iops_rd", + input: map[string]interface{}{"scsi6": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_rd=10"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_6: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 10}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough iops_rd_max", + input: map[string]interface{}{"scsi7": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_rd_max=12"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_7: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough iops_rd_max_length", + input: map[string]interface{}{"scsi7": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_rd_max_length=2"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_7: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 2}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough iops_wr", + input: map[string]interface{}{"scsi8": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_wr=11"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_8: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 11}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough iops_wr_max", + input: map[string]interface{}{"scsi9": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_wr_max=13"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_9: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough iops_wr_max_length", + input: map[string]interface{}{"scsi9": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_wr_max_length=3"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_9: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 3}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough iothread", + input: map[string]interface{}{"scsi10": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iothread=1"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_10: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + IOThread: true, + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough mbps_rd", + input: map[string]interface{}{"scsi11": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,mbps_rd=1.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_11: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 1.51}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough mbps_rd_max", + input: map[string]interface{}{"scsi12": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,mbps_rd_max=3.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_12: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough mbps_wr", + input: map[string]interface{}{"scsi13": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,mbps_wr=2.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_13: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 2.51}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough mbps_wr_max", + input: map[string]interface{}{"scsi14": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,mbps_wr_max=4.51"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_14: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough replicate", + input: map[string]interface{}{"scsi15": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,replicate=0"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_15: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: false, + }}}}}, + }, + {name: "Disks Scsi Passthrough ro", + input: map[string]interface{}{"scsi16": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,ro=1"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_16: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + ReadOnly: true, + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough serial", + input: map[string]interface{}{"scsi17": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,serial=disk-9763"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_17: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + Serial: "disk-9763", + }}}}}, + }, + {name: "Disks Scsi Passthrough size", + input: map[string]interface{}{"scsi18": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,size=1G"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_18: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + Size: 1, + }}}}}, + }, + {name: "Disks Scsi Passthrough ssd", + input: map[string]interface{}{"scsi19": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,ssd=1"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_19: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + EmulateSSD: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks Scsi Passthrough wwn", + input: map[string]interface{}{"scsi19": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,wwn=0x5009A4FC00B7C613"}, + output: &ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_19: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + WorldWideName: "0x5009A4FC00B7C613", + }}}}}, + }, + // VirtIO CdRom + {name: "Disks VirtIO CdRom none", + input: map[string]interface{}{"virtio11": "none,media=cdrom"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_11: &QemuVirtIOStorage{CdRom: &QemuCdRom{}}}}}, + }, + {name: "Disks VirtIO CdRom passthrough", + input: map[string]interface{}{"virtio10": "cdrom,media=cdrom"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_10: &QemuVirtIOStorage{CdRom: &QemuCdRom{Passthrough: true}}}}}, + }, + {name: "Disks VirtIO CdRom iso", + input: map[string]interface{}{"virtio9": "local:iso/debian-11.0.0-amd64-netinst.iso,media=cdrom,size=377M"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_9: &QemuVirtIOStorage{CdRom: &QemuCdRom{Iso: &IsoFile{ + File: "debian-11.0.0-amd64-netinst.iso", + Storage: "local", + Size: "377M", + }}}}}}, + }, + // Disks VirtIO CloudInit + {name: "Disks VirtIO CloudInit", + input: map[string]interface{}{"virtio0": "Test:100/vm-100-cloudinit.raw,media=cdrom"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{CloudInit: &QemuCloudInitDisk{ + Format: QemuDiskFormat_Raw, + Storage: "Test", + }}}}}, + }, + // Disks VirtIO Disk + {name: "Disks VirtIO Disk", + input: map[string]interface{}{"virtio0": "test2:100/vm-100-disk-31.qcow2"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk ALL", + input: map[string]interface{}{"virtio1": "test2:100/vm-100-disk-31.qcow2,aio=io_uring,backup=0,cache=directsync,discard=on,iops_rd=10,iops_rd_max=12,iops_rd_max_length=2,iops_wr=11,iops_wr_max=13,iops_wr_max_length=3,iothread=1,mbps_rd=1.51,mbps_rd_max=3.51,mbps_wr=2.51,mbps_wr_max=4.51,replicate=0,ro=1,serial=disk-9763,size=32G,wwn=0x50015B3900F8EAD2"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Backup: false, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51, Concurrent: 1.51}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51, Concurrent: 2.51}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12, BurstDuration: 2, Concurrent: 10}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13, BurstDuration: 3, Concurrent: 11}, + }, + }, + Cache: QemuDiskCache_DirectSync, + Discard: true, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + IOThread: true, + ReadOnly: true, + Replicate: false, + Serial: "disk-9763", + Size: 32, + Storage: "test2", + WorldWideName: "0x50015B3900F8EAD2", + }}}}}, + }, + {name: "Disks VirtIO Disk ALL LinkedClone", + input: map[string]interface{}{"virtio1": "test2:110/base-110-disk-1.qcow2/100/vm-100-disk-31.qcow2,aio=io_uring,backup=0,cache=directsync,discard=on,iops_rd=10,iops_rd_max=12,iops_rd_max_length=2,iops_wr=11,iops_wr_max=13,iops_wr_max_length=3,iothread=1,mbps_rd=1.51,mbps_rd_max=3.51,mbps_wr=2.51,mbps_wr_max=4.51,replicate=0,ro=1,serial=disk-9763,size=32G,wwn=0x500FA2D000C69587"}, + output: &ConfigQemu{ + LinkedVmId: 110, + Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Backup: false, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51, Concurrent: 1.51}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51, Concurrent: 2.51}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12, BurstDuration: 2, Concurrent: 10}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13, BurstDuration: 3, Concurrent: 11}, + }, + }, + Cache: QemuDiskCache_DirectSync, + Discard: true, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + IOThread: true, + LinkedDiskId: &uint1, + ReadOnly: true, + Replicate: false, + Serial: "disk-9763", + Size: 32, + Storage: "test2", + WorldWideName: "0x500FA2D000C69587", + }}}}}, + }, + {name: "Disks VirtIO Disk aio", + input: map[string]interface{}{"virtio2": "test2:100/vm-100-disk-31.qcow2,aio=io_uring"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk backup", + input: map[string]interface{}{"virtio3": "test2:100/vm-100-disk-31.qcow2,backup=0"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: false, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk cache", + input: map[string]interface{}{"virtio4": "test2:100/vm-100-disk-31.qcow2,cache=directsync"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_4: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Cache: QemuDiskCache_DirectSync, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk discard", + input: map[string]interface{}{"virtio5": "test2:100/vm-100-disk-31.qcow2,discard=on"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Discard: true, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk iops_rd", + input: map[string]interface{}{"virtio6": "test2:100/vm-100-disk-31.qcow2,iops_rd=10"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_6: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 10}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk iops_rd_max", + input: map[string]interface{}{"virtio7": "test2:100/vm-100-disk-31.qcow2,iops_rd_max=12"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_7: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk iops_rd_max_length", + input: map[string]interface{}{"virtio7": "test2:100/vm-100-disk-31.qcow2,iops_rd_max_length=2"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_7: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 2}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk iops_wr", + input: map[string]interface{}{"virtio8": "test2:100/vm-100-disk-31.qcow2,iops_wr=11"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_8: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 11}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk iops_wr_max", + input: map[string]interface{}{"virtio9": "test2:100/vm-100-disk-31.qcow2,iops_wr_max=13"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_9: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk iops_wr_max_length", + input: map[string]interface{}{"virtio9": "test2:100/vm-100-disk-31.qcow2,iops_wr_max_length=3"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_9: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 3}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk iothread", + input: map[string]interface{}{"virtio10": "test2:100/vm-100-disk-31.qcow2,iothread=1"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_10: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + IOThread: true, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk mbps_rd", + input: map[string]interface{}{"virtio11": "test2:100/vm-100-disk-31.qcow2,mbps_rd=1.51"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_11: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 1.51}, + }, + }, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk mbps_rd_max", + input: map[string]interface{}{"virtio12": "test2:100/vm-100-disk-31.qcow2,mbps_rd_max=3.51"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_12: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk mbps_wr", + input: map[string]interface{}{"virtio13": "test2:100/vm-100-disk-31.qcow2,mbps_wr=2.51"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_13: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 2.51}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk mbps_wr_max", + input: map[string]interface{}{"virtio14": "test2:100/vm-100-disk-31.qcow2,mbps_wr_max=4.51"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_14: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51}}}, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk replicate", + input: map[string]interface{}{"virtio15": "test2:100/vm-100-disk-31.qcow2,replicate=0"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_15: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: false, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk ro", + input: map[string]interface{}{"virtio0": "test2:100/vm-100-disk-31.qcow2,ro=1"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + ReadOnly: true, + Replicate: true, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk serial", + input: map[string]interface{}{"virtio1": "test2:100/vm-100-disk-31.qcow2,serial=disk-9763"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Serial: "disk-9763", + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk size G", + input: map[string]interface{}{"virtio2": "test2:100/vm-100-disk-31.qcow2,size=32G"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Size: 32, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk size T", + input: map[string]interface{}{"virtio2": "test2:100/vm-100-disk-31.qcow2,size=5T"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Size: 5120, + Storage: "test2", + }}}}}, + }, + {name: "Disks VirtIO Disk wwn", + input: map[string]interface{}{"virtio2": "test2:100/vm-100-disk-31.qcow2,wwn=0x500D3FAB00B4E672"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Backup: true, + Format: QemuDiskFormat_Qcow2, + Id: uint31, + Replicate: true, + Storage: "test2", + WorldWideName: "0x500D3FAB00B4E672", + }}}}}, + }, + // Disks VirtIO Passthrough + {name: "Disks VirtIO Passthrough", + input: map[string]interface{}{"virtio0": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough ALL", + input: map[string]interface{}{"virtio1": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,aio=native,backup=0,cache=unsafe,discard=on,iops_rd=10,iops_rd_max=12,iops_rd_max_length=4,iops_wr=11,iops_wr_max=13,iops_wr_max_length=5,iothread=1,mbps_rd=1.51,mbps_rd_max=3.51,mbps_wr=2.51,mbps_wr_max=4.51,replicate=0,ro=1,serial=disk-9763,size=1G,wwn=0x500B6ED600F1C945"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + AsyncIO: QemuDiskAsyncIO_Native, + Backup: false, + Bandwidth: QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51, Concurrent: 1.51}, + WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51, Concurrent: 2.51}, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12, BurstDuration: 4, Concurrent: 10}, + WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13, BurstDuration: 5, Concurrent: 11}, + }, + }, + Cache: QemuDiskCache_Unsafe, + Discard: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + IOThread: true, + ReadOnly: true, + Replicate: false, + Serial: "disk-9763", + Size: 1, + WorldWideName: "0x500B6ED600F1C945", + }}}}}, + }, + {name: "Disks VirtIO Passthrough aio", + input: map[string]interface{}{"virtio2": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,aio=native"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + AsyncIO: QemuDiskAsyncIO_Native, + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough backup", + input: map[string]interface{}{"virtio3": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,backup=0"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: false, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough cache", + input: map[string]interface{}{"virtio4": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,cache=unsafe"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_4: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + Cache: QemuDiskCache_Unsafe, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough discard", + input: map[string]interface{}{"virtio5": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,discard=on"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + Discard: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough iops_rd", + input: map[string]interface{}{"virtio6": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_rd=10"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_6: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 10}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough iops_rd_max", + input: map[string]interface{}{"virtio7": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_rd_max=12"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_7: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 12}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough iops_rd_max_length", + input: map[string]interface{}{"virtio7": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_rd_max_length=2"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_7: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 2}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough iops_wr", + input: map[string]interface{}{"virtio8": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_wr=11"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_8: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 11}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough iops_wr_max", + input: map[string]interface{}{"virtio9": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_wr_max=13"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_9: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 13}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough iops_wr_max_length", + input: map[string]interface{}{"virtio9": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iops_wr_max_length=3"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_9: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{BurstDuration: 3}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough iothread", + input: map[string]interface{}{"virtio10": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,iothread=1"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_10: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + IOThread: true, + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough mbps_rd", + input: map[string]interface{}{"virtio11": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,mbps_rd=1.51"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_11: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 1.51}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough mbps_rd_max", + input: map[string]interface{}{"virtio12": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,mbps_rd_max=3.51"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_12: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 3.51}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough mbps_wr", + input: map[string]interface{}{"virtio13": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,mbps_wr=2.51"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_13: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 2.51}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough mbps_wr_max", + input: map[string]interface{}{"virtio14": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,mbps_wr_max=4.51"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_14: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 4.51}}}, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough replicate", + input: map[string]interface{}{"virtio15": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,replicate=0"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_15: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: false, + }}}}}, + }, + {name: "Disks VirtIO Passthrough ro", + input: map[string]interface{}{"virtio0": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,ro=1"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + ReadOnly: true, + Replicate: true, + }}}}}, + }, + {name: "Disks VirtIO Passthrough serial", + input: map[string]interface{}{"virtio1": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,serial=disk-9763"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + Serial: "disk-9763", + }}}}}, + }, + {name: "Disks VirtIO Passthrough size", + input: map[string]interface{}{"virtio2": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,size=1G"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + Size: 1, + }}}}}, + }, + {name: "Disks VirtIO Passthrough wwn", + input: map[string]interface{}{"virtio2": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8,wwn=0x5008FA6500D9C8B3"}, + output: &ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + Backup: true, + File: "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi8", + Replicate: true, + WorldWideName: "0x5008FA6500D9C8B3", + }}}}}, + }, + // Iso + {name: "Iso", + input: map[string]interface{}{"ide2": "local:iso/debian-11.0.0-amd64-netinst.iso,media=cdrom,size=377M"}, + output: &ConfigQemu{ + Iso: &IsoFile{ + File: "debian-11.0.0-amd64-netinst.iso", + Storage: "local", + Size: "377M", + }, + Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{CdRom: &QemuCdRom{ + Iso: &IsoFile{ + File: "debian-11.0.0-amd64-netinst.iso", + Storage: "local", + Size: "377M", + }, + }}}}, + }, + }, + // EFI + {name: "EFI Disk", + input: map[string]interface{}{"efidisk0": "local-lvm:vm-1000-disk-0,efitype=2m,size=4M"}, + output: &ConfigQemu{EFIDisk: map[string]interface{}{ + "efitype": "2m", + "size": "4M", + "storage": "local-lvm", + "file": "vm-1000-disk-0", + "volume": "local-lvm:vm-1000-disk-0", + }}, + }, + } + for _, test := range tests { + t.Run(test.name, func(*testing.T) { + output, err := ConfigQemu{}.mapToStruct(test.input) + if err != nil { + require.Equal(t, test.err, err, test.name) + } else { + require.Equal(t, test.output, output, test.name) + } + }) + } +} +func Test_ConfigQemu_Validate(t *testing.T) { + BandwidthValid0 := QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{ + Burst: 0, + Concurrent: 0, + }, + WriteLimit: QemuDiskBandwidthMBpsLimit{ + Burst: 0, + Concurrent: 0, + }, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{ + Burst: 0, + Concurrent: 0, + }, + WriteLimit: QemuDiskBandwidthIopsLimit{ + Burst: 0, + Concurrent: 0, + }, + }, + } + BandwidthValid1 := QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{ + Burst: 1, + Concurrent: 1, + }, + WriteLimit: QemuDiskBandwidthMBpsLimit{ + Burst: 1, + Concurrent: 1, + }, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{ + Burst: 10, + Concurrent: 10, + }, + WriteLimit: QemuDiskBandwidthIopsLimit{ + Burst: 10, + Concurrent: 10, + }, + }, + } + BandwidthValid2 := QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{ + Burst: 1, + Concurrent: 0, + }, + WriteLimit: QemuDiskBandwidthMBpsLimit{ + Burst: 1, + Concurrent: 0, + }, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{ + Burst: 10, + Concurrent: 0, + }, + WriteLimit: QemuDiskBandwidthIopsLimit{ + Burst: 10, + Concurrent: 0, + }, + }, + } + BandwidthValid3 := QemuDiskBandwidth{ + MBps: QemuDiskBandwidthMBps{ + ReadLimit: QemuDiskBandwidthMBpsLimit{ + Burst: 0, + Concurrent: 1, + }, + WriteLimit: QemuDiskBandwidthMBpsLimit{ + Burst: 0, + Concurrent: 1, + }, + }, + Iops: QemuDiskBandwidthIops{ + ReadLimit: QemuDiskBandwidthIopsLimit{ + Burst: 0, + Concurrent: 10, + }, + WriteLimit: QemuDiskBandwidthIopsLimit{ + Burst: 0, + Concurrent: 10, + }, + }, + } + validCloudInit := QemuCloudInitDisk{Format: QemuDiskFormat_Raw, Storage: "Test"} + testData := []struct { + name string + input ConfigQemu + err error + }{ + // Valid + // Valid Disks + {name: "Valid Disks Empty 0", + input: ConfigQemu{Disks: &QemuStorages{}}, + }, + {name: "Valid Disks Empty 1", + input: ConfigQemu{Disks: &QemuStorages{ + Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{}}, + Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{}}, + Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{}}, + VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{}}, + }}, + }, + // Valid Disks CdRom + {name: "Valid Disks CdRom", + input: ConfigQemu{Disks: &QemuStorages{ + Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{CdRom: &QemuCdRom{}}}, + Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test", Storage: "test"}}}}, + Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{CdRom: &QemuCdRom{Passthrough: true}}}, + VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test", Storage: "test"}}}}, + }}, + }, + // Valid Disks CloudInit + {name: "Valid Disks CloudInit Ide", + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{CloudInit: &validCloudInit}}}}, + }, + {name: "Valid Disks CloudInit Sata", + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{CloudInit: &validCloudInit}}}}, + }, + {name: "Valid Disks CloudInit Scsi", + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{CloudInit: &validCloudInit}}}}, + }, + {name: "Valid Disks CloudInit VirtIO", + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{CloudInit: &validCloudInit}}}}, + }, + // Valid Disks Disk + {name: "Valid Disks Disk", + input: ConfigQemu{Disks: &QemuStorages{ + Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Bandwidth: BandwidthValid0, + Cache: QemuDiskCache_DirectSync, + Format: QemuDiskFormat_Raw, + Size: 32, + Storage: "test", + WorldWideName: "0x500A1B2C3D4E5F60", + }}}, + Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{ + AsyncIO: QemuDiskAsyncIO_Native, + Bandwidth: BandwidthValid1, + Cache: QemuDiskCache_None, + Format: QemuDiskFormat_Cow, + Size: 1, + Storage: "test", + WorldWideName: "0x500F123456789ABC", + }}}, + Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{Disk: &QemuScsiDisk{ + AsyncIO: QemuDiskAsyncIO_Threads, + Bandwidth: BandwidthValid2, + Cache: QemuDiskCache_WriteBack, + Format: QemuDiskFormat_Qcow2, + Size: 10, + Storage: "test", + WorldWideName: "0x5009876543210DEF", + }}}, + VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + AsyncIO: "", + Bandwidth: BandwidthValid3, + Cache: "", + Format: QemuDiskFormat_Vmdk, + Size: 1024, + Storage: "test", + WorldWideName: "0x500C0D0E0F101112", + }}}, + }}, + }, + // Valid Disks Passthrough + {name: "Valid Disks Passthrough", + input: ConfigQemu{Disks: &QemuStorages{ + Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{ + AsyncIO: QemuDiskAsyncIO_IOuring, + Bandwidth: BandwidthValid3, + Cache: QemuDiskCache_DirectSync, + File: "test", + WorldWideName: "0x5001A2B3C4D5E6F7", + }}}, + Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Passthrough: &QemuSataPassthrough{ + AsyncIO: QemuDiskAsyncIO_Native, + Bandwidth: BandwidthValid2, + Cache: "", + File: "test", + WorldWideName: "0x500B0A0908070605", + }}}, + Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{ + AsyncIO: QemuDiskAsyncIO_Threads, + Bandwidth: BandwidthValid1, + Cache: QemuDiskCache_WriteBack, + File: "test", + WorldWideName: "0x500F1E2D3C4B5A69", + }}}, + VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{ + AsyncIO: "", + Bandwidth: BandwidthValid0, + Cache: QemuDiskCache_WriteThrough, + File: "test", + WorldWideName: "0x5004A3B2C1D0E0F1", + }}}, + }}, + }, + // Invalid + // Invalid Disks Mutually exclusive Ide + {name: "Invalid Disks MutuallyExclusive Ide 0", + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{ + CdRom: &QemuCdRom{}, + CloudInit: &QemuCloudInitDisk{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Ide 1", + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{ + CdRom: &QemuCdRom{}, + Disk: &QemuIdeDisk{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Ide 2", + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{ + CdRom: &QemuCdRom{}, + Passthrough: &QemuIdePassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Ide 3", + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{ + CloudInit: &QemuCloudInitDisk{}, + Disk: &QemuIdeDisk{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Ide 4", + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{ + CloudInit: &QemuCloudInitDisk{}, + Passthrough: &QemuIdePassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Ide 5", + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{ + Disk: &QemuIdeDisk{}, + Passthrough: &QemuIdePassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Ide 6", + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{ + CdRom: &QemuCdRom{}, + CloudInit: &QemuCloudInitDisk{}, + Disk: &QemuIdeDisk{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Ide 7", + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{ + CloudInit: &QemuCloudInitDisk{}, + Disk: &QemuIdeDisk{}, + Passthrough: &QemuIdePassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Ide 8", + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{ + CdRom: &QemuCdRom{}, + Disk: &QemuIdeDisk{}, + Passthrough: &QemuIdePassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Ide 9", + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{ + CdRom: &QemuCdRom{}, + CloudInit: &QemuCloudInitDisk{}, + Passthrough: &QemuIdePassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Ide 10", + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{ + CdRom: &QemuCdRom{}, + CloudInit: &QemuCloudInitDisk{}, + Disk: &QemuIdeDisk{}, + Passthrough: &QemuIdePassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + // Invalid Disks Mutually exclusive Sata + {name: "Invalid Disks MutuallyExclusive Sata 0", + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{ + CdRom: &QemuCdRom{}, + CloudInit: &QemuCloudInitDisk{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Sata 1", + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{ + CdRom: &QemuCdRom{}, + Disk: &QemuSataDisk{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Sata 2", + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{ + CdRom: &QemuCdRom{}, + Passthrough: &QemuSataPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Sata 3", + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{ + CloudInit: &QemuCloudInitDisk{}, + Disk: &QemuSataDisk{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Sata 4", + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{ + CloudInit: &QemuCloudInitDisk{}, + Passthrough: &QemuSataPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Sata 5", + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{ + Disk: &QemuSataDisk{}, + Passthrough: &QemuSataPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Sata 6", + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{ + CdRom: &QemuCdRom{}, + CloudInit: &QemuCloudInitDisk{}, + Disk: &QemuSataDisk{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Sata 7", + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{ + CloudInit: &QemuCloudInitDisk{}, + Disk: &QemuSataDisk{}, + Passthrough: &QemuSataPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Sata 8", + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{ + CdRom: &QemuCdRom{}, + Disk: &QemuSataDisk{}, + Passthrough: &QemuSataPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Sata 9", + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{ + CdRom: &QemuCdRom{}, + CloudInit: &QemuCloudInitDisk{}, + Passthrough: &QemuSataPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Sata 10", + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{ + CdRom: &QemuCdRom{}, + CloudInit: &QemuCloudInitDisk{}, + Disk: &QemuSataDisk{}, + Passthrough: &QemuSataPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + // Invalid Disks Mutually exclusive Scsi + {name: "Invalid Disks MutuallyExclusive Scsi 0", + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{ + CdRom: &QemuCdRom{}, + CloudInit: &QemuCloudInitDisk{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Scsi 1", + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_1: &QemuScsiStorage{ + CdRom: &QemuCdRom{}, + Disk: &QemuScsiDisk{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Scsi 2", + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_2: &QemuScsiStorage{ + CdRom: &QemuCdRom{}, + Passthrough: &QemuScsiPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Scsi 3", + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_3: &QemuScsiStorage{ + CloudInit: &QemuCloudInitDisk{}, + Disk: &QemuScsiDisk{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Scsi 4", + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_4: &QemuScsiStorage{ + CloudInit: &QemuCloudInitDisk{}, + Passthrough: &QemuScsiPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Scsi 5", + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_5: &QemuScsiStorage{ + Disk: &QemuScsiDisk{}, + Passthrough: &QemuScsiPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Scsi 6", + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_6: &QemuScsiStorage{ + CdRom: &QemuCdRom{}, + CloudInit: &QemuCloudInitDisk{}, + Disk: &QemuScsiDisk{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Scsi 7", + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_7: &QemuScsiStorage{ + CloudInit: &QemuCloudInitDisk{}, + Disk: &QemuScsiDisk{}, + Passthrough: &QemuScsiPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Scsi 8", + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_8: &QemuScsiStorage{ + CdRom: &QemuCdRom{}, + Disk: &QemuScsiDisk{}, + Passthrough: &QemuScsiPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Scsi 9", + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_9: &QemuScsiStorage{ + CdRom: &QemuCdRom{}, + CloudInit: &QemuCloudInitDisk{}, + Passthrough: &QemuScsiPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive Scsi 10", + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_10: &QemuScsiStorage{ + CdRom: &QemuCdRom{}, + CloudInit: &QemuCloudInitDisk{}, + Disk: &QemuScsiDisk{}, + Passthrough: &QemuScsiPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + // Invalid Disks Mutually exclusive VirtIO + {name: "Invalid Disks MutuallyExclusive VirtIO 0", + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{ + CdRom: &QemuCdRom{}, + CloudInit: &QemuCloudInitDisk{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive VirtIO 1", + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{ + CdRom: &QemuCdRom{}, + Disk: &QemuVirtIODisk{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive VirtIO 2", + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{ + CdRom: &QemuCdRom{}, + Passthrough: &QemuVirtIOPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive VirtIO 3", + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{ + CloudInit: &QemuCloudInitDisk{}, + Disk: &QemuVirtIODisk{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive VirtIO 4", + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_4: &QemuVirtIOStorage{ + CloudInit: &QemuCloudInitDisk{}, + Passthrough: &QemuVirtIOPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive VirtIO 5", + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{ + Disk: &QemuVirtIODisk{}, + Passthrough: &QemuVirtIOPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive VirtIO 6", + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_6: &QemuVirtIOStorage{ + CdRom: &QemuCdRom{}, + CloudInit: &QemuCloudInitDisk{}, + Disk: &QemuVirtIODisk{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive VirtIO 7", + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_7: &QemuVirtIOStorage{ + CloudInit: &QemuCloudInitDisk{}, + Disk: &QemuVirtIODisk{}, + Passthrough: &QemuVirtIOPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive VirtIO 8", + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_8: &QemuVirtIOStorage{ + CdRom: &QemuCdRom{}, + Disk: &QemuVirtIODisk{}, + Passthrough: &QemuVirtIOPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive VirtIO 9", + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_9: &QemuVirtIOStorage{ + CdRom: &QemuCdRom{}, + CloudInit: &QemuCloudInitDisk{}, + Passthrough: &QemuVirtIOPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + {name: "Invalid Disks MutuallyExclusive VirtIO 10", + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_10: &QemuVirtIOStorage{ + CdRom: &QemuCdRom{}, + CloudInit: &QemuCloudInitDisk{}, + Disk: &QemuVirtIODisk{}, + Passthrough: &QemuVirtIOPassthrough{}, + }}}}, + err: errors.New(Error_QemuDisk_MutuallyExclusive), + }, + // Invalid Disks CdRom Ide + {name: "Invalid Disks CdRom Ide errors.New(Error_IsoFile_File) 0", + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{CdRom: &QemuCdRom{Iso: &IsoFile{}}}}}}, + err: errors.New(Error_IsoFile_File), + }, + {name: "Invalid Disks CdRom Ide errors.New(Error_IsoFile_File) 1", + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{CdRom: &QemuCdRom{Iso: &IsoFile{Storage: "test"}}}}}}, + err: errors.New(Error_IsoFile_File), + }, + {name: "Invalid Disks CdRom Ide errors.New(Error_IsoFile_Storage)", + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test"}}}}}}, + err: errors.New(Error_IsoFile_Storage), + }, + {name: "Invalid Disks CdRom Ide errors.New(Error_QemuCdRom_MutuallyExclusive)", + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test", Storage: "test"}, Passthrough: true}}}}}, + err: errors.New(Error_QemuCdRom_MutuallyExclusive), + }, + // Invalid Disks CdRom Sata + {name: "Invalid Disks CdRom Sata errors.New(Error_IsoFile_File) 0", + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{CdRom: &QemuCdRom{Iso: &IsoFile{}}}}}}, + err: errors.New(Error_IsoFile_File), + }, + {name: "Invalid Disks CdRom Sata errors.New(Error_IsoFile_File) 1", + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{CdRom: &QemuCdRom{Iso: &IsoFile{Storage: "test"}}}}}}, + err: errors.New(Error_IsoFile_File), + }, + {name: "Invalid Disks CdRom Sata errors.New(Error_IsoFile_Storage)", + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test"}}}}}}, + err: errors.New(Error_IsoFile_Storage), + }, + {name: "Invalid Disks CdRom Sata errors.New(Error_QemuCdRom_MutuallyExclusive)", + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test", Storage: "test"}, Passthrough: true}}}}}, + err: errors.New(Error_QemuCdRom_MutuallyExclusive), + }, + // Invalid Disks CdRom Scsi + {name: "Invalid Disks CdRom Scsi errors.New(Error_IsoFile_File) 0", + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{CdRom: &QemuCdRom{Iso: &IsoFile{}}}}}}, + err: errors.New(Error_IsoFile_File), + }, + {name: "Invalid Disks CdRom Scsi errors.New(Error_IsoFile_File) 1", + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_2: &QemuScsiStorage{CdRom: &QemuCdRom{Iso: &IsoFile{Storage: "test"}}}}}}, + err: errors.New(Error_IsoFile_File), + }, + {name: "Invalid Disks CdRom Scsi errors.New(Error_IsoFile_Storage)", + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_1: &QemuScsiStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test"}}}}}}, + err: errors.New(Error_IsoFile_Storage), + }, + {name: "Invalid Disks CdRom Scsi errors.New(Error_QemuCdRom_MutuallyExclusive)", + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_3: &QemuScsiStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test", Storage: "test"}, Passthrough: true}}}}}, + err: errors.New(Error_QemuCdRom_MutuallyExclusive), + }, + // Invalid Disks CdRom VirtIO + {name: "Invalid Disks CdRom VirtIO errors.New(Error_IsoFile_File) 0", + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{CdRom: &QemuCdRom{Iso: &IsoFile{}}}}}}, + err: errors.New(Error_IsoFile_File), + }, + {name: "Invalid Disks CdRom VirtIO errors.New(Error_IsoFile_File) 1", + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{CdRom: &QemuCdRom{Iso: &IsoFile{Storage: "test"}}}}}}, + err: errors.New(Error_IsoFile_File), + }, + {name: "Invalid Disks CdRom VirtIO errors.New(Error_IsoFile_Storage)", + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test"}}}}}}, + err: errors.New(Error_IsoFile_Storage), + }, + {name: "Invalid Disks CdRom VirtIO errors.New(Error_QemuCdRom_MutuallyExclusive)", + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{CdRom: &QemuCdRom{Iso: &IsoFile{File: "test", Storage: "test"}, Passthrough: true}}}}}, + err: errors.New(Error_QemuCdRom_MutuallyExclusive), + }, + // Invalid Disks CloudInit Duplicate + {name: "Invalid Disks CloudInit Duplicate errors.New(Error_QemuCloudInitDisk_OnlyOne)", + input: ConfigQemu{Disks: &QemuStorages{ + Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{CloudInit: &validCloudInit}}, + Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{CloudInit: &validCloudInit}}, + Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{CloudInit: &validCloudInit}}, + VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{CloudInit: &validCloudInit}}, + }}, + err: errors.New(Error_QemuCloudInitDisk_OnlyOne), + }, + {name: "Invalid Disks CloudInit Duplicate Ide errors.New(Error_QemuCloudInitDisk_OnlyOne)", + input: ConfigQemu{Disks: &QemuStorages{ + Ide: &QemuIdeDisks{ + Disk_0: &QemuIdeStorage{CloudInit: &validCloudInit}, + Disk_1: &QemuIdeStorage{CloudInit: &validCloudInit}, + }, + }}, + err: errors.New(Error_QemuCloudInitDisk_OnlyOne), + }, + {name: "Invalid Disks CloudInit Duplicate Sata errors.New(Error_QemuCloudInitDisk_OnlyOne)", + input: ConfigQemu{Disks: &QemuStorages{ + Sata: &QemuSataDisks{ + Disk_0: &QemuSataStorage{CloudInit: &validCloudInit}, + Disk_1: &QemuSataStorage{CloudInit: &validCloudInit}, + }, + }}, + err: errors.New(Error_QemuCloudInitDisk_OnlyOne), + }, + {name: "Invalid Disks CloudInit Duplicate Scsi errors.New(Error_QemuCloudInitDisk_OnlyOne)", + input: ConfigQemu{Disks: &QemuStorages{ + Scsi: &QemuScsiDisks{ + Disk_0: &QemuScsiStorage{CloudInit: &validCloudInit}, + Disk_1: &QemuScsiStorage{CloudInit: &validCloudInit}, + }, + }}, + err: errors.New(Error_QemuCloudInitDisk_OnlyOne), + }, + {name: "Invalid Disks CloudInit Duplicate VirtIO errors.New(Error_QemuCloudInitDisk_OnlyOne)", + input: ConfigQemu{Disks: &QemuStorages{ + VirtIO: &QemuVirtIODisks{ + Disk_0: &QemuVirtIOStorage{CloudInit: &validCloudInit}, + Disk_1: &QemuVirtIOStorage{CloudInit: &validCloudInit}, + }, + }}, + err: errors.New(Error_QemuCloudInitDisk_OnlyOne), + }, + // Invalid Disks CloudInit Ide + {name: `Invalid Disks CloudInit Ide QemuDiskFormat("").Error() 0`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{CloudInit: &QemuCloudInitDisk{}}}}}, + err: QemuDiskFormat("").Error(), + }, + {name: `Invalid Disks CloudInit Ide QemuDiskFormat("").Error() 1`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{CloudInit: &QemuCloudInitDisk{Storage: "test"}}}}}, + err: QemuDiskFormat("").Error(), + }, + {name: `Invalid Disks CloudInit Ide errors.New(Error_QemuCloudInitDisk_Storage)`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{CloudInit: &QemuCloudInitDisk{Format: QemuDiskFormat_Raw}}}}}, + err: errors.New(Error_QemuCloudInitDisk_Storage), + }, + // Invalid Disks CloudInit Sata + {name: `Invalid Disks CloudInit Sata QemuDiskFormat("").Error() 0`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{CloudInit: &QemuCloudInitDisk{}}}}}, + err: QemuDiskFormat("").Error(), + }, + {name: `Invalid Disks CloudInit Sata QemuDiskFormat("").Error() 1`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{CloudInit: &QemuCloudInitDisk{Storage: "test"}}}}}, + err: QemuDiskFormat("").Error(), + }, + {name: `Invalid Disks CloudInit Sata errors.New(Error_QemuCloudInitDisk_Storage)`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{CloudInit: &QemuCloudInitDisk{Format: QemuDiskFormat_Raw}}}}}, + err: errors.New(Error_QemuCloudInitDisk_Storage), + }, + // Invalid Disks CloudInit Scsi + {name: `Invalid Disks CloudInit Scsi QemuDiskFormat("").Error() 0`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{CloudInit: &QemuCloudInitDisk{}}}}}, + err: QemuDiskFormat("").Error(), + }, + {name: `Invalid Disks CloudInit Scsi QemuDiskFormat("").Error() 1`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_1: &QemuScsiStorage{CloudInit: &QemuCloudInitDisk{Storage: "test"}}}}}, + err: QemuDiskFormat("").Error(), + }, + {name: `Invalid Disks CloudInit Scsi errors.New(Error_QemuCloudInitDisk_Storage)`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_2: &QemuScsiStorage{CloudInit: &QemuCloudInitDisk{Format: QemuDiskFormat_Raw}}}}}, + err: errors.New(Error_QemuCloudInitDisk_Storage), + }, + // Invalid Disks CloudInit VirtIO + {name: `Invalid Disks CloudInit VirtIO QemuDiskFormat("").Error() 0`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{CloudInit: &QemuCloudInitDisk{}}}}}, + err: QemuDiskFormat("").Error(), + }, + {name: `Invalid Disks CloudInit VirtIO QemuDiskFormat("").Error() 1`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{CloudInit: &QemuCloudInitDisk{Storage: "test"}}}}}, + err: QemuDiskFormat("").Error(), + }, + {name: `Invalid Disks CloudInit VirtIO errors.New(Error_QemuCloudInitDisk_Storage)`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{CloudInit: &QemuCloudInitDisk{Format: QemuDiskFormat_Raw}}}}}, + err: errors.New(Error_QemuCloudInitDisk_Storage), + }, + // Invalid Disks Disk Ide + {name: `Invalid Disks Disk Ide QemuDiskFormat("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{}}}}}, + err: QemuDiskFormat("").Error(), + }, + {name: `Invalid Disks Disk Ide QemuDiskAsyncIO("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{AsyncIO: "invalid"}}}}}, + err: QemuDiskAsyncIO("").Error(), + }, + {name: `Invalid Disks Disk Ide errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) 0`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst), + }, + {name: `Invalid Disks Disk Ide errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) 0`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent), + }, + {name: `Invalid Disks Disk Ide errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) 1`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst), + }, + {name: `Invalid Disks Disk Ide errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) 1`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent), + }, + {name: `Invalid Disks Disk Ide errors.New(Error_QemuDiskBandwidthIopsLimitBurst) 0`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 9}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst), + }, + {name: `Invalid Disks Disk Ide errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) 0`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 8}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent), + }, + {name: `Invalid Disks Disk Ide errors.New(Error_QemuDiskBandwidthIopsLimitBurst) 1`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 7}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst), + }, + {name: `Invalid Disks Disk Ide errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) 1`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 6}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent), + }, + {name: `Invalid Disks Disk Ide QemuDiskCache("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{Cache: "invalid"}}}}}, + err: QemuDiskCache("").Error(), + }, + {name: `Invalid Disks Disk Ide QemuDiskFormat("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{Format: ""}}}}}, + err: QemuDiskFormat("").Error(), + }, + {name: `Invalid Disks Disk Ide errors.New(Error_QemuDiskSerial_IllegalCharacter)`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Serial: "!@^$^&$^&"}, + }}}}, + err: errors.New(Error_QemuDiskSerial_IllegalCharacter), + }, + {name: `Invalid Disks Disk Ide errors.New(Error_QemuDiskSerial_IllegalLength)`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Serial: QemuDiskSerial(test_data_qemu.QemuDiskSerial_Max_Illegal()), + }}}}}, + err: errors.New(Error_QemuDiskSerial_IllegalLength), + }, + {name: `Invalid Disks Disk Ide errors.New(Error_QemuDisk_Size)`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Size: 0, + }}}}}, + err: errors.New(Error_QemuDisk_Size), + }, + {name: `Invalid Disks Disk Ide errors.New(Error_QemuDisk_Storage)`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Size: 32, + Storage: "", + }}}}}, + err: errors.New(Error_QemuDisk_Storage), + }, + {name: `Invalid Disks Disk Ide errors.New(Error_QemuWorldWideName_Invalid)`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Disk: &QemuIdeDisk{ + Format: QemuDiskFormat_Raw, + Size: 32, + Storage: "Test", + WorldWideName: "0xG123456789ABCDE", + }}}}}, + err: errors.New(Error_QemuWorldWideName_Invalid), + }, + // Invalid Disks Disk Sata + {name: `Invalid Disks Disk Sata QemuDiskFormat("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{}}}}}, + err: QemuDiskFormat("").Error(), + }, + {name: `Invalid Disks Disk Sata QemuDiskAsyncIO("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{AsyncIO: "invalid"}}}}}, + err: QemuDiskAsyncIO("").Error(), + }, + {name: `Invalid Disks Disk Sata errors.New(Error_QemuDiskBandwidthIopsLimitBurst) 0`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 9}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst), + }, + {name: `Invalid Disks Disk Sata errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) 0`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 8}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent), + }, + {name: `Invalid Disks Disk Sata errors.New(Error_QemuDiskBandwidthIopsLimitBurst) 1`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 7}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst), + }, + {name: `Invalid Disks Disk Sata errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) 1`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 6}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent), + }, + {name: `Invalid Disks Disk Sata errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) 0`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst), + }, + {name: `Invalid Disks Disk Sata errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) 0`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent), + }, + {name: `Invalid Disks Disk Sata errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) 1`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst), + }, + {name: `Invalid Disks Disk Sata errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) 1`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Disk: &QemuSataDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent), + }, + {name: `Invalid Disks Disk Sata QemuDiskCache("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Disk: &QemuSataDisk{Cache: "invalid"}}}}}, + err: QemuDiskCache("").Error(), + }, + {name: `Invalid Disks Disk Sata QemuDiskFormat("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Disk: &QemuSataDisk{Format: ""}}}}}, + err: QemuDiskFormat("").Error(), + }, + {name: `Invalid Disks Disk Sata errors.New(Error_QemuDiskSerial_IllegalCharacter)`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Serial: "!@^$^&$^&"}, + }}}}, + err: errors.New(Error_QemuDiskSerial_IllegalCharacter), + }, + {name: `Invalid Disks Disk Sata errors.New(Error_QemuDiskSerial_IllegalLength)`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Serial: QemuDiskSerial(test_data_qemu.QemuDiskSerial_Max_Illegal()), + }}}}}, + err: errors.New(Error_QemuDiskSerial_IllegalLength), + }, + {name: `Invalid Disks Disk Sata errors.New(Error_QemuDisk_Size)`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Size: 0, + }}}}}, + err: errors.New(Error_QemuDisk_Size), + }, + {name: `Invalid Disks Disk Sata errors.New(Error_QemuDisk_Storage)`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Size: 32, + Storage: "", + }}}}}, + err: errors.New(Error_QemuDisk_Storage), + }, + {name: `Invalid Disks Disk Sata errors.New(Error_QemuWorldWideName_Invalid)`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Disk: &QemuSataDisk{ + Format: QemuDiskFormat_Raw, + Size: 32, + Storage: "Test", + WorldWideName: "500A1B2C3D4E5F60", + }}}}}, + err: errors.New(Error_QemuWorldWideName_Invalid), + }, + // Invalid Disks Disk Scsi + {name: `Invalid Disks Disk Scsi QemuDiskFormat("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{Disk: &QemuScsiDisk{}}}}}, + err: QemuDiskFormat("").Error(), + }, + {name: `Invalid Disks Disk Scsi QemuDiskAsyncIO("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_1: &QemuScsiStorage{Disk: &QemuScsiDisk{AsyncIO: "invalid"}}}}}, + err: QemuDiskAsyncIO("").Error(), + }, + {name: `Invalid Disks Disk Scsi errors.New(Error_QemuDiskBandwidthIopsLimitBurst) 0`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_6: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 9}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst), + }, + {name: `Invalid Disks Disk Scsi errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) 0`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_7: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 8}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent), + }, + {name: `Invalid Disks Disk Scsi errors.New(Error_QemuDiskBandwidthIopsLimitBurst) 1`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_8: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 7}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst), + }, + {name: `Invalid Disks Disk Scsi errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) 1`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_9: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 6}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent), + }, + {name: `Invalid Disks Disk Scsi errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) 0`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_2: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst), + }, + {name: `Invalid Disks Disk Scsi errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) 0`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_3: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent), + }, + {name: `Invalid Disks Disk Scsi errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) 1`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_4: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst), + }, + {name: `Invalid Disks Disk Scsi errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) 1`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_5: &QemuScsiStorage{Disk: &QemuScsiDisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent), + }, + {name: `Invalid Disks Disk Scsi QemuDiskCache("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_10: &QemuScsiStorage{Disk: &QemuScsiDisk{Cache: "invalid"}}}}}, + err: QemuDiskCache("").Error(), + }, + {name: `Invalid Disks Disk Scsi QemuDiskFormat("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_11: &QemuScsiStorage{Disk: &QemuScsiDisk{Format: ""}}}}}, + err: QemuDiskFormat("").Error(), + }, + {name: `Invalid Disks Disk Scsi errors.New(Error_QemuDiskSerial_IllegalCharacter)`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_12: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Serial: "!@^$^&$^&"}, + }}}}, + err: errors.New(Error_QemuDiskSerial_IllegalCharacter), + }, + {name: `Invalid Disks Disk Scsi errors.New(Error_QemuDiskSerial_IllegalLength)`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_13: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Serial: QemuDiskSerial(test_data_qemu.QemuDiskSerial_Max_Illegal()), + }}}}}, + err: errors.New(Error_QemuDiskSerial_IllegalLength), + }, + {name: `Invalid Disks Disk Scsi errors.New(Error_QemuDisk_Size)`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_14: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Size: 0, + }}}}}, + err: errors.New(Error_QemuDisk_Size), + }, + {name: `Invalid Disks Disk Scsi errors.New(Error_QemuDisk_Storage)`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_15: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Size: 32, + Storage: "", + }}}}}, + err: errors.New(Error_QemuDisk_Storage), + }, + {name: `Invalid Disks Disk Scsi errors.New(Error_QemuWorldWideName_Invalid)`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_16: &QemuScsiStorage{Disk: &QemuScsiDisk{ + Format: QemuDiskFormat_Raw, + Size: 32, + Storage: "Test", + WorldWideName: "0x5009876543210DEFG", + }}}}}, + err: errors.New(Error_QemuWorldWideName_Invalid), + }, + // Invalid Disks Disk VirtIO + {name: `Invalid Disks Disk VirtIO QemuDiskFormat("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{}}}}}, + err: QemuDiskFormat("").Error(), + }, + {name: `Invalid Disks Disk VirtIO QemuDiskAsyncIO("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{AsyncIO: "invalid"}}}}}, + err: QemuDiskAsyncIO("").Error(), + }, + {name: `Invalid Disks Disk VirtIO errors.New(Error_QemuDiskBandwidthIopsLimitBurst) 0`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_6: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 9}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst), + }, + {name: `Invalid Disks Disk VirtIO errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) 0`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_7: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 8}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent), + }, + {name: `Invalid Disks Disk VirtIO errors.New(Error_QemuDiskBandwidthIopsLimitBurst) 1`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_8: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 7}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst), + }, + {name: `Invalid Disks Disk VirtIO errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) 1`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_9: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 6}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent), + }, + {name: `Invalid Disks Disk VirtIO errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) 0`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst), + }, + {name: `Invalid Disks Disk VirtIO errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) 0`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent), + }, + {name: `Invalid Disks Disk VirtIO errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) 1`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_4: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst), + }, + {name: `Invalid Disks Disk VirtIO errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) 1`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent), + }, + {name: `Invalid Disks Disk VirtIO QemuDiskCache("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_10: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Cache: "invalid"}}}}}, + err: QemuDiskCache("").Error(), + }, + {name: `Invalid Disks Disk VirtIO QemuDiskFormat("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_11: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{Format: ""}}}}}, + err: QemuDiskFormat("").Error(), + }, + {name: `Invalid Disks Disk VirtIO errors.New(Error_QemuDiskSerial_IllegalCharacter)`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_12: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Serial: "!@^$^&$^&"}, + }}}}, + err: errors.New(Error_QemuDiskSerial_IllegalCharacter), + }, + {name: `Invalid Disks Disk VirtIO errors.New(Error_QemuDiskSerial_IllegalLength)`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_13: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Serial: QemuDiskSerial(test_data_qemu.QemuDiskSerial_Max_Illegal()), + }}}}}, + err: errors.New(Error_QemuDiskSerial_IllegalLength), + }, + {name: `Invalid Disks Disk VirtIO errors.New(Error_QemuDisk_Size)`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_14: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Size: 0, + }}}}}, + err: errors.New(Error_QemuDisk_Size), + }, + {name: `Invalid Disks Disk VirtIO errors.New(Error_QemuDisk_Storage)`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_15: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Size: 32, + Storage: "", + }}}}}, + err: errors.New(Error_QemuDisk_Storage), + }, + {name: `Invalid Disks Disk VirtIO errors.New(Error_QemuWorldWideName_Invalid)`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Disk: &QemuVirtIODisk{ + Format: QemuDiskFormat_Raw, + Size: 32, + Storage: "Test", + WorldWideName: "500C0D0E0F10111", + }}}}}, + err: errors.New(Error_QemuWorldWideName_Invalid), + }, + // Invalid Disks Passthrough Ide + {name: `Invalid Disks Passthrough Ide QemuDiskAsyncIO("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{AsyncIO: "invalid"}}}}}, + err: QemuDiskAsyncIO("").Error(), + }, + {name: `Invalid Disks Passthrough Ide errors.New(Error_QemuDiskBandwidthIopsLimitBurst) 0`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 9}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst), + }, + {name: `Invalid Disks Passthrough Ide errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) 0`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 8}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent), + }, + {name: `Invalid Disks Passthrough Ide errors.New(Error_QemuDiskBandwidthIopsLimitBurst) 1`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 7}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst), + }, + {name: `Invalid Disks Passthrough Ide errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) 1`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 6}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent), + }, + {name: `Invalid Disks Passthrough Ide errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) 0`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst), + }, + {name: `Invalid Disks Passthrough Ide errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) 0`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent), + }, + {name: `Invalid Disks Passthrough Ide errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) 1`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst), + }, + {name: `Invalid Disks Passthrough Ide errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) 1`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent), + }, + {name: `Invalid Disks Passthrough Ide QemuDiskCache("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{Cache: "invalid"}}}}}, + err: QemuDiskCache("").Error(), + }, + {name: `Invalid Disks Passthrough Ide errors.New(Error_QemuDisk_File)`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_2: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{File: ""}}}}}, + err: errors.New(Error_QemuDisk_File), + }, + {name: `Invalid Disks Passthrough Ide errors.New(Error_QemuDiskSerial_IllegalCharacter)`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_3: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{File: "/dev/disk/by-id/scsi1", Serial: "!@^$^&$^&"}}}}}, + err: errors.New(Error_QemuDiskSerial_IllegalCharacter), + }, + {name: `Invalid Disks Passthrough Ide errors.New(Error_QemuDiskSerial_IllegalLength)`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_0: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{File: "/dev/disk/by-id/scsi1", Serial: QemuDiskSerial(test_data_qemu.QemuDiskSerial_Max_Illegal())}}}}}, + err: errors.New(Error_QemuDiskSerial_IllegalLength), + }, + {name: `Invalid Disks Passthrough Ide errors.New(Error_QemuWorldWideName_Invalid)`, + input: ConfigQemu{Disks: &QemuStorages{Ide: &QemuIdeDisks{Disk_1: &QemuIdeStorage{Passthrough: &QemuIdePassthrough{File: "/dev/disk/by-id/scsi1", WorldWideName: "5001A2B3C4D5E6F7"}}}}}, + err: errors.New(Error_QemuWorldWideName_Invalid), + }, + // Invalid Disks Passthrough Sata + {name: `Invalid Disks Passthrough Sata QemuDiskAsyncIO("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Passthrough: &QemuSataPassthrough{AsyncIO: "invalid"}}}}}, + err: QemuDiskAsyncIO("").Error(), + }, + {name: `Invalid Disks Passthrough Sata errors.New(Error_QemuDiskBandwidthIopsLimitBurst) 0`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 9}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst), + }, + {name: `Invalid Disks Passthrough Sata errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) 0`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 8}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent), + }, + {name: `Invalid Disks Passthrough Sata errors.New(Error_QemuDiskBandwidthIopsLimitBurst) 1`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 7}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst), + }, + {name: `Invalid Disks Passthrough Sata errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) 1`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 6}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent), + }, + {name: `Invalid Disks Passthrough Sata errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) 0`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst), + }, + {name: `Invalid Disks Passthrough Sata errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) 0`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_2: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent), + }, + {name: `Invalid Disks Passthrough Sata errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) 1`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst), + }, + {name: `Invalid Disks Passthrough Sata errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) 1`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent), + }, + {name: `Invalid Disks Passthrough Sata QemuDiskCache("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_3: &QemuSataStorage{Passthrough: &QemuSataPassthrough{Cache: "invalid"}}}}}, + err: QemuDiskCache("").Error(), + }, + {name: `Invalid Disks Passthrough Sata errors.New(Error_QemuDisk_File)`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_4: &QemuSataStorage{Passthrough: &QemuSataPassthrough{File: ""}}}}}, + err: errors.New(Error_QemuDisk_File), + }, + {name: `Invalid Disks Passthrough Sata errors.New(Error_QemuDiskSerial_IllegalCharacter)`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_5: &QemuSataStorage{Passthrough: &QemuSataPassthrough{File: "/dev/disk/by-id/scsi1", Serial: "!@^$^&$^&"}}}}}, + err: errors.New(Error_QemuDiskSerial_IllegalCharacter), + }, + {name: `Invalid Disks Passthrough Sata errors.New(Error_QemuDiskSerial_IllegalLength)`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_0: &QemuSataStorage{Passthrough: &QemuSataPassthrough{File: "/dev/disk/by-id/scsi1", Serial: QemuDiskSerial(test_data_qemu.QemuDiskSerial_Max_Illegal())}}}}}, + err: errors.New(Error_QemuDiskSerial_IllegalLength), + }, + {name: `Invalid Disks Passthrough Sata errors.New(Error_QemuWorldWideName_Invalid)`, + input: ConfigQemu{Disks: &QemuStorages{Sata: &QemuSataDisks{Disk_1: &QemuSataStorage{Passthrough: &QemuSataPassthrough{File: "/dev/disk/by-id/scsi1", WorldWideName: "0x500B0A09080706050"}}}}}, + err: errors.New(Error_QemuWorldWideName_Invalid), + }, + // Invalid Disks Passthrough Scsi + {name: `Invalid Disks Passthrough Scsi QemuDiskAsyncIO("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_0: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{AsyncIO: "invalid"}}}}}, + err: QemuDiskAsyncIO("").Error(), + }, + {name: `Invalid Disks Passthrough Scsi errors.New(Error_QemuDiskBandwidthIopsLimitBurst) 0`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_5: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 9}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst), + }, + {name: `Invalid Disks Passthrough Scsi errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) 0`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_6: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 8}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent), + }, + {name: `Invalid Disks Passthrough Scsi errors.New(Error_QemuDiskBandwidthIopsLimitBurst) 1`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_7: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 7}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst), + }, + {name: `Invalid Disks Passthrough Scsi errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) 1`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_8: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 6}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent), + }, + {name: `Invalid Disks Passthrough Scsi errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) 0`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_1: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst), + }, + {name: `Invalid Disks Passthrough Scsi errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) 0`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_2: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent), + }, + {name: `Invalid Disks Passthrough Scsi errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) 1`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_3: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst), + }, + {name: `Invalid Disks Passthrough Scsi errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) 1`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_4: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent), + }, + {name: `Invalid Disks Passthrough Scsi QemuDiskCache("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_9: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{Cache: "invalid"}}}}}, + err: QemuDiskCache("").Error(), + }, + {name: `Invalid Disks Passthrough Scsi errors.New(Error_QemuDisk_File)`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_10: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{File: ""}}}}}, + err: errors.New(Error_QemuDisk_File), + }, + {name: `Invalid Disks Passthrough Scsi errors.New(Error_QemuDiskSerial_IllegalCharacter)`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_11: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{File: "/dev/disk/by-id/scsi1", Serial: "!@^$^&$^&"}}}}}, + err: errors.New(Error_QemuDiskSerial_IllegalCharacter), + }, + {name: `Invalid Disks Passthrough Scsi errors.New(Error_QemuDiskSerial_IllegalLength)`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_12: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{File: "/dev/disk/by-id/scsi1", Serial: QemuDiskSerial(test_data_qemu.QemuDiskSerial_Max_Illegal())}}}}}, + err: errors.New(Error_QemuDiskSerial_IllegalLength), + }, + {name: `Invalid Disks Passthrough Scsi errors.New(Error_QemuWorldWideName_Invalid)`, + input: ConfigQemu{Disks: &QemuStorages{Scsi: &QemuScsiDisks{Disk_13: &QemuScsiStorage{Passthrough: &QemuScsiPassthrough{File: "/dev/disk/by-id/scsi1", WorldWideName: "500F1E2D3C4B5A69!"}}}}}, + err: errors.New(Error_QemuWorldWideName_Invalid), + }, + // Invalid Disks Passthrough VirtIO + {name: `Invalid Disks Passthrough VirtIO QemuDiskAsyncIO("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_0: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{AsyncIO: "invalid"}}}}}, + err: QemuDiskAsyncIO("").Error(), + }, + {name: `Invalid Disks Passthrough VirtIO errors.New(Error_QemuDiskBandwidthIopsLimitBurst) 0`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_5: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Burst: 9}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst), + }, + {name: `Invalid Disks Passthrough VirtIO errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) 0`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_6: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{ReadLimit: QemuDiskBandwidthIopsLimit{Concurrent: 8}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent), + }, + {name: `Invalid Disks Passthrough VirtIO errors.New(Error_QemuDiskBandwidthIopsLimitBurst) 1`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_7: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Burst: 7}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitBurst), + }, + {name: `Invalid Disks Passthrough VirtIO errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent) 1`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_8: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{Iops: QemuDiskBandwidthIops{WriteLimit: QemuDiskBandwidthIopsLimit{Concurrent: 6}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthIopsLimitConcurrent), + }, + {name: `Invalid Disks Passthrough VirtIO errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) 0`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_1: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst), + }, + {name: `Invalid Disks Passthrough VirtIO errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) 0`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_2: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{ReadLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent), + }, + {name: `Invalid Disks Passthrough VirtIO errors.New(Error_QemuDiskBandwidthMBpsLimitBurst) 1`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_3: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Burst: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitBurst), + }, + {name: `Invalid Disks Passthrough VirtIO errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent) 1`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_4: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Bandwidth: QemuDiskBandwidth{MBps: QemuDiskBandwidthMBps{WriteLimit: QemuDiskBandwidthMBpsLimit{Concurrent: 0.99}}}}}}}}, + err: errors.New(Error_QemuDiskBandwidthMBpsLimitConcurrent), + }, + {name: `Invalid Disks Passthrough VirtIO QemuDiskCache("").Error()`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_9: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{Cache: "invalid"}}}}}, + err: QemuDiskCache("").Error(), + }, + {name: `Invalid Disks Passthrough VirtIO errors.New(Error_QemuDisk_File)`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_10: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{File: ""}}}}}, + err: errors.New(Error_QemuDisk_File), + }, + {name: `Invalid Disks Passthrough VirtIO errors.New(Error_QemuDiskSerial_IllegalCharacter)`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_11: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{File: "/dev/disk/by-id/scsi1", Serial: "!@^$^&$^&"}}}}}, + err: errors.New(Error_QemuDiskSerial_IllegalCharacter), + }, + {name: `Invalid Disks Passthrough VirtIO errors.New(Error_QemuDiskSerial_IllegalLength)`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_12: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{File: "/dev/disk/by-id/scsi1", Serial: QemuDiskSerial(test_data_qemu.QemuDiskSerial_Max_Illegal())}}}}}, + err: errors.New(Error_QemuDiskSerial_IllegalLength), + }, + {name: `Invalid Disks Passthrough VirtIO errors.New(Error_QemuWorldWideName_Invalid)`, + input: ConfigQemu{Disks: &QemuStorages{VirtIO: &QemuVirtIODisks{Disk_13: &QemuVirtIOStorage{Passthrough: &QemuVirtIOPassthrough{File: "/dev/disk/by-id/scsi1", WorldWideName: "0x5004A3B2C1D0E0F1#"}}}}}, + err: errors.New(Error_QemuWorldWideName_Invalid), + }, + } + for _, test := range testData { + t.Run(test.name, func(*testing.T) { + if test.err != nil { + require.Equal(t, test.input.Validate(), test.err, test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + // Test the encoding logic to encode the ssh keys func Test_sshKeyUrlEncode(t *testing.T) { input := test_sshKeyUrlEncode_Input() diff --git a/proxmox/config_sdn_dns.go b/proxmox/config_sdn_dns.go new file mode 100644 index 00000000..de97d188 --- /dev/null +++ b/proxmox/config_sdn_dns.go @@ -0,0 +1,91 @@ +package proxmox + +import ( + "encoding/json" + "fmt" +) + +// ConfigSDNDNS describes the SDN DNS configurable element +type ConfigSDNDNS struct { + DNS string `json:"dns"` + Key string `json:"key"` + Type string `json:"type"` + URL string `json:"url"` + TTL int `json:"ttl,omitempty"` + // The SDN Plugin schema contains ReverseV6Mask attribute while the + // PowerDNS plugin schema contains the ReverseMaskV6 attribute + // This is probably a bug that crept into the Proxmox implementation.a + // Checked in libpve-network-perl=0.7.3 + ReverseMaskV6 int `json:"reversemaskv6,omitempty"` + ReverseV6Mask int `json:"reversev6mask,omitempty"` + // Digest allows for a form of optimistic locking + Digest string `json:"digest,omitempty"` +} + +func NewConfigSDNDNSFromJson(input []byte) (config *ConfigSDNDNS, err error) { + config = &ConfigSDNDNS{} + err = json.Unmarshal([]byte(input), config) + return +} + +func (config *ConfigSDNDNS) CreateWithValidate(id string, client *Client) (err error) { + err = config.Validate(id, true, client) + if err != nil { + return + } + return config.Create(id, client) +} + +func (config *ConfigSDNDNS) Create(id string, client *Client) (err error) { + config.DNS = id + params := config.mapToApiValues() + return client.CreateSDNDNS(params) +} + +func (config *ConfigSDNDNS) UpdateWithValidate(id string, client *Client) (err error) { + err = config.Validate(id, false, client) + if err != nil { + return + } + return config.Update(id, client) +} + +func (config *ConfigSDNDNS) Update(id string, client *Client) (err error) { + config.DNS = id + params := config.mapToApiValues() + err = client.UpdateSDNDNS(id, params) + if err != nil { + params, _ := json.Marshal(¶ms) + return fmt.Errorf("error updating SDN DNS: %v, (params: %v)", err, string(params)) + } + return +} + +func (c *ConfigSDNDNS) Validate(id string, create bool, client *Client) (err error) { + exists, err := client.CheckSDNDNSExistance(id) + if err != nil { + return + } + if exists && create { + return ErrorItemExists(id, "dns") + } + if !exists && !create { + return ErrorItemNotExists(id, "dns") + } + + err = ValidateStringInArray([]string{"powerdns"}, c.Type, "type") + if err != nil { + return + } + err = ValidateIntGreater(0, c.TTL, "ttl") + if err != nil { + return + } + return +} + +func (config *ConfigSDNDNS) mapToApiValues() (params map[string]interface{}) { + d, _ := json.Marshal(config) + json.Unmarshal(d, ¶ms) + return +} diff --git a/proxmox/config_sdn_subnet.go b/proxmox/config_sdn_subnet.go new file mode 100644 index 00000000..0f9a2a81 --- /dev/null +++ b/proxmox/config_sdn_subnet.go @@ -0,0 +1,123 @@ +package proxmox + +import ( + "encoding/json" + "fmt" + "net" +) + +type ConfigSDNSubnet struct { + // For creation purposes - Subnet is a CIDR + // Once a subnet has been created, the Subnet is an identifier with the format + // "--" + Subnet string `json:"subnet"` + + DNSZonePrefix string `json:"dnszoneprefix,omitempty"` + Gateway string `json:"gateway,omitempty"` + SNAT bool `json:"snat,omitempty"` + + // Delete is a string of attributes to be deleted from the object + Delete string `json:"delete,omitempty"` + // Type must always hold the string "subnet" + Type string `json:"type"` + // Digest allows for a form of optimistic locking + Digest string `json:"digest,omitempty"` +} + +// NewConfigSDNSubnetFromJSON takes in a byte array from a json encoded SDN Subnet +// configuration and stores it in config. +// It returns the newly created config with the passed in configuration stored +// and an error if one occurs unmarshalling the input data. +func NewConfigSDNSubnetFromJson(input []byte) (config *ConfigSDNSubnet, err error) { + config = &ConfigSDNSubnet{} + err = json.Unmarshal([]byte(input), config) + return +} + +func (config *ConfigSDNSubnet) CreateWithValidate(vnet, id string, client *Client) (err error) { + err = config.Validate(vnet, id, true, client) + if err != nil { + return + } + return config.Create(vnet, id, client) +} + +func (config *ConfigSDNSubnet) Create(vnet, id string, client *Client) (err error) { + config.Subnet = id + config.Type = "subnet" + params := config.mapToApiValues() + return client.CreateSDNSubnet(vnet, params) +} + +func (config *ConfigSDNSubnet) UpdateWithValidate(vnet, id string, client *Client) (err error) { + err = config.Validate(vnet, id, false, client) + if err != nil { + return + } + return config.Update(vnet, id, client) +} + +func (config *ConfigSDNSubnet) Update(vnet, id string, client *Client) (err error) { + config.Subnet = id + config.Type = "" // For some reason, this shouldn't be sent on update. Only on create. + params := config.mapToApiValues() + err = client.UpdateSDNSubnet(vnet, id, params) + if err != nil { + params, _ := json.Marshal(¶ms) + return fmt.Errorf("error updating SDN Subnet: %v, (params: %v)", err, string(params)) + } + return +} + +func (c *ConfigSDNSubnet) Validate(vnet, id string, create bool, client *Client) (err error) { + vnetExists, err := client.CheckSDNVNetExistance(vnet) + if err != nil { + return + } + if !vnetExists { + return fmt.Errorf("subnet must be created in an existing vnet. vnet (%s) wasn't found", vnet) + } + exists, err := client.CheckSDNSubnetExistance(vnet, id) + if err != nil { + return + } + if exists && create { + return ErrorItemExists(id, "subnet") + } + if !exists && !create { + return ErrorItemNotExists(id, "subnet") + } + + // if this is an update, the Subnet is an identifier of the form -- + // and therefore shouldn't be validated or changed + if create { + // Make sure that the CIDR is actually a valid CIDR + _, _, err = net.ParseCIDR(c.Subnet) + if err != nil { + return + } + } + + if c.Gateway != "" { + ip := net.ParseIP(c.Gateway) + if ip == nil { + return fmt.Errorf("error gateway (%s) is not a valid IP", c.Gateway) + } + } + + return +} + +func (config *ConfigSDNSubnet) mapToApiValues() (params map[string]interface{}) { + + d, _ := json.Marshal(config) + json.Unmarshal(d, ¶ms) + + if v, has := params["snat"]; has { + params["snat"] = Btoi(v.(bool)) + } + // Remove the subnet and vnet (path parameters) from the map + delete(params, "subnet") + delete(params, "vnet") + return +} diff --git a/proxmox/config_sdn_vnet.go b/proxmox/config_sdn_vnet.go new file mode 100644 index 00000000..f7793226 --- /dev/null +++ b/proxmox/config_sdn_vnet.go @@ -0,0 +1,100 @@ +package proxmox + +import ( + "encoding/json" + "fmt" + "regexp" +) + +type ConfigSDNVNet struct { + VNet string `json:"vnet"` + Zone string `json:"zone"` + Alias string `json:"alias,omitempty"` + Delete string `json:"delete,omitempty"` + Tag int `json:"tag,omitempty"` + VLANAware bool `json:"vlanaware,omitempty"` + // Digest allows for a form of optimistic locking + Digest string `json:"digest,omitempty"` +} + +func NewConfigSDNVNetFromJson(input []byte) (config *ConfigSDNVNet, err error) { + config = &ConfigSDNVNet{} + err = json.Unmarshal([]byte(input), config) + return +} + +func (config *ConfigSDNVNet) CreateWithValidate(id string, client *Client) (err error) { + err = config.Validate(id, true, client) + if err != nil { + return + } + return config.Create(id, client) +} + +func (config *ConfigSDNVNet) Create(id string, client *Client) (err error) { + config.VNet = id + params := config.mapToApiValues() + return client.CreateSDNVNet(params) +} + +func (config *ConfigSDNVNet) UpdateWithValidate(id string, client *Client) (err error) { + err = config.Validate(id, false, client) + if err != nil { + return + } + return config.Update(id, client) +} + +func (config *ConfigSDNVNet) Update(id string, client *Client) (err error) { + config.VNet = id + params := config.mapToApiValues() + err = client.UpdateSDNVNet(id, params) + if err != nil { + params, _ := json.Marshal(¶ms) + return fmt.Errorf("error updating SDN VNet: %v, (params: %v)", err, string(params)) + } + return +} + +func (c *ConfigSDNVNet) Validate(id string, create bool, client *Client) (err error) { + exists, err := client.CheckSDNVNetExistance(id) + if err != nil { + return + } + if exists && create { + return ErrorItemExists(id, "vnet") + } + if !exists && !create { + return ErrorItemNotExists(id, "vnet") + } + zoneExists, err := client.CheckSDNZoneExistance(c.Zone) + if err != nil { + return + } + if !zoneExists { + return fmt.Errorf("vnet must be associated to an existing zone. zone %s could not be found", c.Zone) + } + if c.Alias != "" { + regex, _ := regexp.Compile(`^(?i:[\(\)-_.\w\d\s]{0,256})$`) + if !regex.Match([]byte(c.Alias)) { + return fmt.Errorf(`alias must match the validation regular expression: ^(?i:[\(\)-_.\w\d\s]{0,256})$`) + } + } + err = ValidateIntGreater(0, c.Tag, "tag") + if err != nil { + return + } + + return +} + +func (config *ConfigSDNVNet) mapToApiValues() (params map[string]interface{}) { + d, _ := json.Marshal(config) + json.Unmarshal(d, ¶ms) + + if v, has := params["vlanaware"]; has { + params["vlanaware"] = Btoi(v.(bool)) + } + + return +} diff --git a/proxmox/config_sdn_zone.go b/proxmox/config_sdn_zone.go new file mode 100644 index 00000000..6dd9dc3e --- /dev/null +++ b/proxmox/config_sdn_zone.go @@ -0,0 +1,163 @@ +package proxmox + +import ( + "encoding/json" + "fmt" +) + +// ConfigSDNZone describes the Zone configurable element +type ConfigSDNZone struct { + Type string `json:"type"` + Zone string `json:"zone"` + AdvertiseSubnets bool `json:"advertise-subnets,omitempty"` + Bridge string `json:"bridge,omitempty"` + BridgeDisableMacLearning bool `json:"bridge-disable-mac-learning,omitempty"` + Controller string `json:"controller,omitempty"` + DisableARPNDSuppression bool `json:"disable-arp-nd-suppression,omitempty"` + DNS string `json:"dns,omitempty"` + DNSZone string `json:"dnszone,omitempty"` + DPID int `json:"dp-id,omitempty"` + ExitNodes string `json:"exitnodes,omitempty"` + ExitNodesLocalRouting bool `json:"exitnodes-local-routing,omitempty"` + ExitNodesPrimary string `json:"exitnodes-primary,omitempty"` + IPAM string `json:"ipam,omitempty"` + MAC string `json:"mac,omitempty"` + MTU int `json:"mtu,omitempty"` + Nodes string `json:"nodes,omitempty"` + Peers string `json:"peers,omitempty"` + ReverseDNS string `json:"reversedns,omitempty"` + RTImport string `json:"rt-import,omitempty"` + Tag int `json:"tag,omitempty"` + VlanProtocol string `json:"vlan-protocol,omitempty"` + VrfVxlan int `json:"vrf-vxlan,omitempty"` + // Pass a string of attributes to be deleted from the remote object + Delete string `json:"delete,omitempty"` + // Digest allows for a form of optimistic locking + Digest string `json:"digest,omitempty"` +} + +// NewConfigNetworkFromJSON takes in a byte array from a json encoded SDN Zone +// configuration and stores it in config. +// It returns the newly created config with the passed in configuration stored +// and an error if one occurs unmarshalling the input data. +func NewConfigSDNZoneFromJson(input []byte) (config *ConfigSDNZone, err error) { + config = &ConfigSDNZone{} + err = json.Unmarshal([]byte(input), config) + return +} + +func (config *ConfigSDNZone) CreateWithValidate(id string, client *Client) (err error) { + err = config.Validate(id, true, client) + if err != nil { + return + } + return config.Create(id, client) +} + +func (config *ConfigSDNZone) Create(id string, client *Client) (err error) { + config.Zone = id + params := config.mapToApiValues() + return client.CreateSDNZone(params) +} + +func (config *ConfigSDNZone) UpdateWithValidate(id string, client *Client) (err error) { + err = config.Validate(id, false, client) + if err != nil { + return + } + return config.Update(id, client) +} + +func (config *ConfigSDNZone) Update(id string, client *Client) (err error) { + config.Zone = id + params := config.mapToApiValues() + err = client.UpdateSDNZone(id, params) + if err != nil { + params, _ := json.Marshal(¶ms) + return fmt.Errorf("error updating SDN Zone: %v, (params: %v)", err, string(params)) + } + return +} + +func (c *ConfigSDNZone) Validate(id string, create bool, client *Client) (err error) { + exists, err := client.CheckSDNZoneExistance(id) + if err != nil { + return + } + if exists && create { + return ErrorItemExists(id, "zone") + } + if !exists && !create { + return ErrorItemNotExists(id, "zone") + } + + err = ValidateStringInArray([]string{"evpn", "qinq", "simple", "vlan", "vxlan"}, c.Type, "type") + if err != nil { + return + } + switch c.Type { + case "simple": + case "vlan": + if create { + if c.Bridge == "" { + return ErrorKeyEmpty("bridge") + } + } + case "qinq": + if create { + if c.Bridge == "" { + return ErrorKeyEmpty("bridge") + } + if c.Tag <= 0 { + return ErrorKeyEmpty("tag") + } + if c.VlanProtocol == "" { + return ErrorKeyEmpty("vlan-protocol") + } + } + case "vxlan": + if create { + if c.Peers == "" { + return ErrorKeyEmpty("peers") + } + } + case "evpn": + if create { + if c.VrfVxlan < 0 { + return ErrorKeyEmpty("vrf-vxlan") + } + if c.Controller == "" { + return ErrorKeyEmpty("controller") + } + } + } + if c.VlanProtocol != "" { + err = ValidateStringInArray([]string{"802.1q", "802.1ad"}, c.VlanProtocol, "vlan-protocol") + if err != nil { + return + } + } + return +} + +func (config *ConfigSDNZone) mapToApiValues() (params map[string]interface{}) { + + d, _ := json.Marshal(config) + json.Unmarshal(d, ¶ms) + + boolsToFix := []string{ + "advertise-subnets", + "bridge-disable-mac-learning", + "disable-arp-nd-suppression", + "exitnodes-local-routing", + } + for _, key := range boolsToFix { + if v, has := params[key]; has { + params[key] = Btoi(v.(bool)) + } + } + // Remove the zone and type (path parameters) from the map + delete(params, "zone") + delete(params, "type") + return +} diff --git a/proxmox/config_user.go b/proxmox/config_user.go index 1c49cbfc..13833760 100644 --- a/proxmox/config_user.go +++ b/proxmox/config_user.go @@ -172,6 +172,86 @@ func (config ConfigUser) UpdateUserPassword(client *Client) (err error) { }, "/access/password") } +type ApiToken struct { + TokenId string `json:"tokenid"` + Comment string `json:"comment,omitempty"` + Expire int64 `json:"expire"` + Privsep bool `json:"privsep"` +} +type ApiTokenCreateResult struct { + Info map[string]interface{} `json:"info"` + Value string `json:"value"` +} +type ApiTokenCreateResultWrapper struct { + Data ApiTokenCreateResult `json:"data"` +} + +// Maps the API values from proxmox to a struct +func (tokens ApiToken) mapToStruct(params map[string]interface{}) *ApiToken { + if _, isSet := params["tokenid"]; isSet { + tokens.TokenId = params["tokenid"].(string) + } + if _, isSet := params["comment"]; isSet { + tokens.Comment = params["comment"].(string) + } + if _, isSet := params["expire"]; isSet { + tokens.Expire = int64(params["expire"].(float64)) + } + if _, isSet := params["privsep"]; isSet { + tokens.Privsep = false + if params["privsep"] == 1 { + tokens.Privsep = true + } + } + return &tokens +} + +func (ApiToken) mapToArray(params []interface{}) *[]ApiToken { + tokens := make([]ApiToken, len(params)) + for i, e := range params { + tokens[i] = *ApiToken{}.mapToStruct(e.(map[string]interface{})) + } + return &tokens +} + +func (config ConfigUser) CreateApiToken(client *Client, token ApiToken) (value string, err error) { + status, err := client.CreateItemReturnStatus(map[string]interface{}{ + "comment": token.Comment, + "expire": token.Expire, + "privsep": token.Privsep, + }, "/access/users/"+config.User.ToString()+"/token/"+token.TokenId) + if err != nil { + return + } + var resultWrapper *ApiTokenCreateResultWrapper + err = json.Unmarshal([]byte(status), &resultWrapper) + value = resultWrapper.Data.Value + return +} + +func (config ConfigUser) UpdateApiToken(client *Client, token ApiToken) (err error) { + err = client.Put(map[string]interface{}{ + "comment": token.Comment, + "expire": token.Expire, + "privsep": token.Privsep, + }, "/access/users/"+config.User.ToString()+"/token/"+token.TokenId) + return +} + +func (config ConfigUser) ListApiTokens(client *Client) (tokens *[]ApiToken, err error) { + status, err := client.GetItemListInterfaceArray("/access/users/" + config.User.ToString() + "/token") + if err != nil { + return + } + tokens = ApiToken{}.mapToArray(status) + return +} + +func (config ConfigUser) DeleteApiToken(client *Client, token ApiToken) (err error) { + err = client.Delete("/access/users/" + config.User.ToString() + "/token/" + token.TokenId) + return +} + // Validates all items and sub items in the ConfigUser struct func (config ConfigUser) Validate() (err error) { err = config.User.Validate() diff --git a/proxmox/session.go b/proxmox/session.go index 057d6c06..b9b3a3dd 100644 --- a/proxmox/session.go +++ b/proxmox/session.go @@ -17,6 +17,8 @@ import ( var Debug = new(bool) +const DebugLargeBodyThreshold = 5 * 1024 * 1024 + type Response struct { Resp *http.Response Body []byte @@ -217,7 +219,11 @@ func (s *Session) Do(req *http.Request) (*http.Response, error) { } if *Debug { - d, _ := httputil.DumpRequestOut(req, true) + includeBody := req.ContentLength < DebugLargeBodyThreshold + d, _ := httputil.DumpRequestOut(req, includeBody) + if !includeBody { + d = append(d, fmt.Sprintf("\n\n", req.ContentLength)...) + } log.Printf(">>>>>>>>>> REQUEST:\n%v", string(d)) } @@ -238,7 +244,11 @@ func (s *Session) Do(req *http.Request) (*http.Response, error) { resp.Body = io.NopCloser(bytes.NewReader(respBody)) if *Debug { - dr, _ := httputil.DumpResponse(resp, true) + includeBody := resp.ContentLength < DebugLargeBodyThreshold + dr, _ := httputil.DumpResponse(resp, includeBody) + if !includeBody { + dr = append(dr, fmt.Sprintf("\n\n", resp.ContentLength)...) + } log.Printf("<<<<<<<<<< RESULT:\n%v", string(dr)) } diff --git a/proxmox/snapshot.go b/proxmox/snapshot.go index 5ea77bf5..35243947 100644 --- a/proxmox/snapshot.go +++ b/proxmox/snapshot.go @@ -3,16 +3,19 @@ package proxmox import ( "encoding/json" "fmt" + "regexp" "strconv" + "unicode" ) type ConfigSnapshot struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - VmState bool `json:"ram,omitempty"` + Name SnapshotName `json:"name,omitempty"` + Description string `json:"description,omitempty"` + VmState bool `json:"ram,omitempty"` } -func (config *ConfigSnapshot) mapToApiValues() map[string]interface{} { +// TODO write tests for this +func (config ConfigSnapshot) mapToApiValues() map[string]interface{} { return map[string]interface{}{ "snapname": config.Name, "description": config.Description, @@ -20,13 +23,16 @@ func (config *ConfigSnapshot) mapToApiValues() map[string]interface{} { } } -func (config *ConfigSnapshot) CreateSnapshot(c *Client, guestId uint) (err error) { - params := config.mapToApiValues() - vmr := NewVmRef(int(guestId)) - _, err = c.GetVmInfo(vmr) +func (config ConfigSnapshot) CreateSnapshot(c *Client, vmr *VmRef) (err error) { + err = c.CheckVmRef(vmr) if err != nil { return } + err = config.Validate() + if err != nil { + return + } + params := config.mapToApiValues() _, err = c.PostWithTask(params, "/nodes/"+vmr.node+"/"+vmr.vmType+"/"+strconv.Itoa(vmr.vmId)+"/snapshot/") if err != nil { params, _ := json.Marshal(¶ms) @@ -35,29 +41,43 @@ func (config *ConfigSnapshot) CreateSnapshot(c *Client, guestId uint) (err error return } -func ListSnapshots(c *Client, vmr *VmRef) (taskResponse []interface{}, err error) { - err = c.CheckVmRef(vmr) +func (config ConfigSnapshot) Validate() error { + return config.Name.Validate() +} + +type rawSnapshots []interface{} + +func ListSnapshots(c *Client, vmr *VmRef) (rawSnapshots, error) { + err := c.CheckVmRef(vmr) if err != nil { - return + return nil, err } return c.GetItemConfigInterfaceArray("/nodes/"+vmr.node+"/"+vmr.vmType+"/"+strconv.Itoa(vmr.vmId)+"/snapshot/", "Guest", "SNAPSHOTS") } // Can only be used to update the description of an already existing snapshot -func UpdateSnapshotDescription(c *Client, vmr *VmRef, snapshot, description string) (err error) { +func UpdateSnapshotDescription(c *Client, vmr *VmRef, snapshot SnapshotName, description string) (err error) { err = c.CheckVmRef(vmr) if err != nil { return } - return c.Put(map[string]interface{}{"description": description}, "/nodes/"+vmr.node+"/"+vmr.vmType+"/"+strconv.Itoa(vmr.vmId)+"/snapshot/"+snapshot+"/config") + err = snapshot.Validate() + if err != nil { + return + } + return c.Put(map[string]interface{}{"description": description}, "/nodes/"+vmr.node+"/"+vmr.vmType+"/"+strconv.Itoa(vmr.vmId)+"/snapshot/"+string(snapshot)+"/config") } -func DeleteSnapshot(c *Client, vmr *VmRef, snapshot string) (exitStatus string, err error) { +func DeleteSnapshot(c *Client, vmr *VmRef, snapshot SnapshotName) (exitStatus string, err error) { err = c.CheckVmRef(vmr) if err != nil { return } - return c.DeleteWithTask("/nodes/" + vmr.node + "/" + vmr.vmType + "/" + strconv.Itoa(vmr.vmId) + "/snapshot/" + snapshot) + err = snapshot.Validate() + if err != nil { + return + } + return c.DeleteWithTask("/nodes/" + vmr.node + "/" + vmr.vmType + "/" + strconv.Itoa(vmr.vmId) + "/snapshot/" + string(snapshot)) } func RollbackSnapshot(c *Client, vmr *VmRef, snapshot string) (exitStatus string, err error) { @@ -70,27 +90,27 @@ func RollbackSnapshot(c *Client, vmr *VmRef, snapshot string) (exitStatus string // Used for formatting the output when retrieving snapshots type Snapshot struct { - Name string `json:"name"` - SnapTime uint `json:"time,omitempty"` - Description string `json:"description,omitempty"` - VmState bool `json:"ram,omitempty"` - Children []*Snapshot `json:"children,omitempty"` - Parent string `json:"parent,omitempty"` + Name SnapshotName `json:"name"` + SnapTime uint `json:"time,omitempty"` + Description string `json:"description,omitempty"` + VmState bool `json:"ram,omitempty"` + Children []*Snapshot `json:"children,omitempty"` + Parent SnapshotName `json:"parent,omitempty"` } // Formats the taskResponse as a list of snapshots -func FormatSnapshotsList(taskResponse []interface{}) (list []*Snapshot) { - list = make([]*Snapshot, len(taskResponse)) - for i, e := range taskResponse { +func (raw rawSnapshots) FormatSnapshotsList() (list []*Snapshot) { + list = make([]*Snapshot, len(raw)) + for i, e := range raw { list[i] = &Snapshot{} if _, isSet := e.(map[string]interface{})["description"]; isSet { list[i].Description = e.(map[string]interface{})["description"].(string) } if _, isSet := e.(map[string]interface{})["name"]; isSet { - list[i].Name = e.(map[string]interface{})["name"].(string) + list[i].Name = SnapshotName(e.(map[string]interface{})["name"].(string)) } if _, isSet := e.(map[string]interface{})["parent"]; isSet { - list[i].Parent = e.(map[string]interface{})["parent"].(string) + list[i].Parent = SnapshotName(e.(map[string]interface{})["parent"].(string)) } if _, isSet := e.(map[string]interface{})["snaptime"]; isSet { list[i].SnapTime = uint(e.(map[string]interface{})["snaptime"].(float64)) @@ -103,8 +123,8 @@ func FormatSnapshotsList(taskResponse []interface{}) (list []*Snapshot) { } // Formats a list of snapshots as a tree of snapshots -func FormatSnapshotsTree(taskResponse []interface{}) (tree []*Snapshot) { - list := FormatSnapshotsList(taskResponse) +func (raw rawSnapshots) FormatSnapshotsTree() (tree []*Snapshot) { + list := raw.FormatSnapshotsList() for _, e := range list { for _, ee := range list { if e.Parent == ee.Name { @@ -121,3 +141,33 @@ func FormatSnapshotsTree(taskResponse []interface{}) (tree []*Snapshot) { } return } + +// Minimum length of 3 characters +// Maximum length of 40 characters +// First character must be a letter +// Must only contain the following characters: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_ +type SnapshotName string + +const ( + SnapshotName_Error_IllegalCharacters string = "SnapshotName must only contain the following characters: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" + SnapshotName_Error_MaxLength string = "SnapshotName must be at most 40 characters long" + SnapshotName_Error_MinLength string = "SnapshotName must be at least 3 characters long" + SnapshotName_Error_StartNoLetter string = "SnapshotName must start with a letter" +) + +func (name SnapshotName) Validate() error { + regex, _ := regexp.Compile(`^([a-zA-Z])([a-z]|[A-Z]|[0-9]|_|-){2,39}$`) + if !regex.Match([]byte(name)) { + if len(name) < 3 { + return fmt.Errorf(SnapshotName_Error_MinLength) + } + if len(name) > 40 { + return fmt.Errorf(SnapshotName_Error_MaxLength) + } + if !unicode.IsLetter(rune(name[0])) { + return fmt.Errorf(SnapshotName_Error_StartNoLetter) + } + return fmt.Errorf(SnapshotName_Error_IllegalCharacters) + } + return nil +} diff --git a/proxmox/snapshot_test.go b/proxmox/snapshot_test.go index 2dce5a3e..24c042a9 100644 --- a/proxmox/snapshot_test.go +++ b/proxmox/snapshot_test.go @@ -2,33 +2,64 @@ package proxmox import ( "encoding/json" + "errors" "testing" + "github.com/Telmate/proxmox-api-go/test/data/test_data_snapshot" "github.com/stretchr/testify/require" ) +func Test_ConfigSnapshot_Validate(t *testing.T) { + tests := []struct { + name string + input ConfigSnapshot + err bool + }{ + // Valid + {name: "Valid ConfigSnapshot", + input: ConfigSnapshot{Name: SnapshotName(test_data_snapshot.SnapshotName_Max_Legal())}, + }, + // Invalid + {name: "Invalid ConfigSnapshot", + input: ConfigSnapshot{Name: SnapshotName(test_data_snapshot.SnapshotName_Max_Illegal())}, + err: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(*testing.T) { + if test.err { + require.Error(t, test.input.Validate(), test.name) + } else { + require.NoError(t, test.input.Validate(), test.name) + } + }) + } +} + +// TODO rename this test // Test the formatting logic to build the tree of snapshots func Test_FormatSnapshotsTree(t *testing.T) { input := test_FormatSnapshots_Input() output := test_FormatSnapshotsTree_Output() for i, e := range input { - result, _ := json.Marshal(FormatSnapshotsTree(e)) + result, _ := json.Marshal(e.FormatSnapshotsTree()) require.JSONEq(t, output[i], string(result)) } } +// TODO rename this test // Test the formatting logic to build the list of snapshots func Test_FormatSnapshotsList(t *testing.T) { input := test_FormatSnapshots_Input() output := test_FormatSnapshotsList_Output() for i, e := range input { - result, _ := json.Marshal(FormatSnapshotsList(e)) + result, _ := json.Marshal(e.FormatSnapshotsList()) require.JSONEq(t, output[i], string(result)) } } -func test_FormatSnapshots_Input() [][]interface{} { - return [][]interface{}{{map[string]interface{}{ +func test_FormatSnapshots_Input() []rawSnapshots { + return []rawSnapshots{{map[string]interface{}{ "name": "aa", "snaptime": float64(1666361849), "description": "", @@ -204,3 +235,38 @@ func test_FormatSnapshotsList_Output() []string { "name":"bba","time":1666362071,"parent":"bb"},{ "name":"bbb","time":1666362062,"parent":"bb"}]`} } + +func Test_SnapshotName_Validate(t *testing.T) { + tests := []struct { + name string + input []string + err error + }{ + // Valid + {name: "Valid", input: test_data_snapshot.SnapshotName_Legal()}, + // Invalid + {name: "Invalid SnapshotName_Error_MinLength", + input: []string{"", test_data_snapshot.SnapshotName_Min_Illegal()}, + err: errors.New(SnapshotName_Error_MinLength), + }, + {name: "Invalid SnapshotName_Error_MaxLength", + input: []string{test_data_snapshot.SnapshotName_Max_Illegal()}, + err: errors.New(SnapshotName_Error_MaxLength), + }, + {name: "Invalid SnapshotName_Error_StartNoLetter", + input: test_data_snapshot.SnapshotName_Start_Illegal(), + err: errors.New(SnapshotName_Error_StartNoLetter), + }, + {name: "Invalid SnapshotName_Error_StartNoLetter", + input: test_data_snapshot.SnapshotName_Character_Illegal(), + err: errors.New(SnapshotName_Error_IllegalCharacters), + }, + } + for _, test := range tests { + for _, snapshot := range test.input { + t.Run(test.name+" :"+snapshot, func(*testing.T) { + require.Equal(t, SnapshotName(snapshot).Validate(), test.err, test.name+" :"+snapshot) + }) + } + } +} diff --git a/proxmox/util.go b/proxmox/util.go index 806f3a73..6779bf77 100644 --- a/proxmox/util.go +++ b/proxmox/util.go @@ -19,6 +19,15 @@ func inArray(arr []string, str string) bool { return false } +func Btoi(b bool) int { + switch b { + case true: + return 1 + default: + return 0 + } +} + func Itob(i int) bool { return i == 1 } @@ -112,7 +121,7 @@ func DiskSizeGB(dcSize interface{}) float64 { switch dcSize := dcSize.(type) { case string: diskString := strings.ToUpper(dcSize) - re := regexp.MustCompile("([0-9]+)([A-Z]*)") + re := regexp.MustCompile("([0-9]+(?:\.[0-9]+)?)([TGMK]B?)?") diskArray := re.FindStringSubmatch(diskString) diskSize, _ = strconv.ParseFloat(diskArray[1], 64) @@ -204,3 +213,28 @@ func createHeaderList(header_string string, sess *Session) (*Session, error) { } return sess, nil } + +// check if a key exists in a nested array of map[string]interface{} +func keyExists(array []interface{}, key string) (existence bool) { + for i := range array { + item := array[i].(map[string]interface{}) + if _, isSet := item[key]; isSet { + return true + } + } + return false +} + +func splitStringOfSettings(settings string) map[string]interface{} { + settingValuePairs := strings.Split(settings, ",") + settingMap := map[string]interface{}{} + for _, e := range settingValuePairs { + keyValuePair := strings.SplitN(e, "=", 2) + var value string + if len(keyValuePair) == 2 { + value = keyValuePair[1] + } + settingMap[keyValuePair[0]] = value + } + return settingMap +} diff --git a/proxmox/util_test.go b/proxmox/util_test.go new file mode 100644 index 00000000..1ac4211c --- /dev/null +++ b/proxmox/util_test.go @@ -0,0 +1,74 @@ +package proxmox + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_keyExists(t *testing.T) { + tests := []struct { + name string + input []interface{} + key string + output bool + }{ + {name: "key empty", + input: []interface{}{ + map[string]interface{}{"aaa": "", "bbb": "", "ccc": ""}, + map[string]interface{}{"aab": "", "bba": "", "cca": ""}, + map[string]interface{}{"aac": "", "bbc": "", "ccb": ""}, + }, + }, + {name: "Key in map", + input: []interface{}{ + map[string]interface{}{"aaa": "", "bbb": "", "ccc": ""}, + map[string]interface{}{"aab": "", "bba": "", "cca": ""}, + map[string]interface{}{"aac": "", "bbc": "", "ccb": ""}, + }, + key: "bba", + output: true, + }, + {name: "Key not in map", + input: []interface{}{ + map[string]interface{}{"aaa": "", "bbb": "", "ccc": ""}, + map[string]interface{}{"aab": "", "bba": "", "cca": ""}, + map[string]interface{}{"aac": "", "bbc": "", "ccb": ""}, + }, + key: "ddd", + }, + {name: "no array", + key: "aaa", + }, + {name: "no keys", + input: []interface{}{map[string]interface{}{}}, + key: "aaa", + }, + } + for _, test := range tests { + t.Run(test.name, func(*testing.T) { + require.Equal(t, test.output, keyExists(test.input, test.key), test.name) + }) + } +} + +func Test_splitStringOfSettings(t *testing.T) { + testData := []struct { + Input string + Output map[string]interface{} + }{ + { + Input: "setting=a,thing=b,randomString,doubleTest=value=equals,object=test", + Output: map[string]interface{}{ + "setting": "a", + "thing": "b", + "randomString": "", + "doubleTest": "value=equals", + "object": "test", + }, + }, + } + for _, e := range testData { + require.Equal(t, e.Output, splitStringOfSettings(e.Input)) + } +} diff --git a/scripts/vagrant-bootstrap.sh b/scripts/vagrant-bootstrap.sh index dcde89a0..4891c14f 100644 --- a/scripts/vagrant-bootstrap.sh +++ b/scripts/vagrant-bootstrap.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +hostnamectl set-hostname pve + export DEBIAN_FRONTEND=noninteractive # ensure required utilities are installed @@ -14,8 +16,11 @@ if [ -z "$(grep ${PVE_IP} /etc/hosts)" ]; then fi # add proxmox repository and its key -apt-add-repository 'deb http://download.proxmox.com/debian/pve buster pve-no-subscription' -wget -qO- http://download.proxmox.com/debian/proxmox-ve-release-6.x.gpg | apt-key add - +echo "deb [arch=amd64] http://download.proxmox.com/debian/pve bullseye pve-no-subscription" > /etc/apt/sources.list.d/pve-install-repo.list +wget https://enterprise.proxmox.com/debian/proxmox-release-bullseye.gpg -O /etc/apt/trusted.gpg.d/proxmox-release-bullseye.gpg + +# disable source-directory +sed -i 's$source-directory /etc/network/interfaces.d$#source-directory /etc/network/interfaces.d$g' /etc/network/interfaces # update repositories and system apt-get update diff --git a/scripts/vagrant-get-cloudinit-template.sh b/scripts/vagrant-get-cloudinit-template.sh new file mode 100644 index 00000000..2e734d08 --- /dev/null +++ b/scripts/vagrant-get-cloudinit-template.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +wget -O /tmp/jammy-server-cloudimg-amd64.img https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img \ No newline at end of file diff --git a/scripts/vagrant-get-container-template.sh b/scripts/vagrant-get-container-template.sh new file mode 100644 index 00000000..a2de7d41 --- /dev/null +++ b/scripts/vagrant-get-container-template.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +pveam download local alpine-3.17-default_20221129_amd64.tar.xz \ No newline at end of file diff --git a/test/api/AcmeAccount/acme_account_create_remove_test.go b/test/api/AcmeAccount/acme_account_create_remove_test.go new file mode 100644 index 00000000..c6c7655c --- /dev/null +++ b/test/api/AcmeAccount/acme_account_create_remove_test.go @@ -0,0 +1,57 @@ +package api_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + // "os" + pxapi "github.com/Telmate/proxmox-api-go/proxmox" + api_test "github.com/Telmate/proxmox-api-go/test/api" +) + +var account = ` +{ +"contact": [ +"a@nonexistantdomain.com", +"b@nonexistantdomain.com", +"c@nonexistantdomain.com", +"d@nonexistantdomain.com" +], +"directory": "https://acme-staging-v02.api.letsencrypt.org/directory", +"tos": true +}` + +func Test_Create_Acme_Account(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + acmeAccount, _ := pxapi.NewConfigAcmeAccountFromJson([]byte(account)) + err := acmeAccount.CreateAcmeAccount("test", Test.GetClient()) + require.NoError(t, err) +} + +func Test_Acme_Account_Is_Added(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + _, err := pxapi.NewConfigAcmeAccountFromApi("test", Test.GetClient()) + + require.NoError(t, err) +} + +func Test_Remove_Acme_Account(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + _, err := Test.GetClient().DeleteAcmeAccount("test") + + require.NoError(t, err) +} + +func Test_Acme_Account_Is_Removed(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + _, err := pxapi.NewConfigAcmeAccountFromApi("test", Test.GetClient()) + + require.Error(t, err) +} diff --git a/test/api/AcmeAccount/acme_account_list_test.go b/test/api/AcmeAccount/acme_account_list_test.go new file mode 100644 index 00000000..bad0e71a --- /dev/null +++ b/test/api/AcmeAccount/acme_account_list_test.go @@ -0,0 +1,14 @@ +package api_test + +import ( + "github.com/stretchr/testify/require" + "testing" + "github.com/Telmate/proxmox-api-go/test/api" +) + +func Test_List_Acme_Accounts(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + _, err := Test.GetClient().GetAcmeAccountList() + require.NoError(t, err) +} \ No newline at end of file diff --git a/test/api/Authentication/authentication_apikey_test.go b/test/api/Authentication/authentication_apikey_test.go new file mode 100644 index 00000000..70922632 --- /dev/null +++ b/test/api/Authentication/authentication_apikey_test.go @@ -0,0 +1,29 @@ +package api_test + +import ( + "testing" + + pxapi "github.com/Telmate/proxmox-api-go/proxmox" + api_test "github.com/Telmate/proxmox-api-go/test/api" + "github.com/stretchr/testify/require" +) + +func Test_Root_Login_Correct_Api_Key(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + user, _ := pxapi.NewConfigUserFromApi(pxapi.UserID{Name: "root", Realm: "pam"}, Test.GetClient()) + + token := pxapi.ApiToken{TokenId: "testing", Comment: "This is a test", Expire: 0, Privsep: false} + + value, _ := user.CreateApiToken(Test.GetClient(), token) + + NewTest := api_test.Test{} + NewTest.CreateClient() + NewTest.GetClient().SetAPIToken("root@pam!testing", value) + + _, err := NewTest.GetClient().GetVersion() + require.NoError(t, err) + + user.DeleteApiToken(Test.GetClient(), token) +} diff --git a/test/api/Authentication/authentication_test.go b/test/api/Authentication/authentication_test.go new file mode 100644 index 00000000..b0a1766f --- /dev/null +++ b/test/api/Authentication/authentication_test.go @@ -0,0 +1,38 @@ +package api_test + +import ( + "github.com/stretchr/testify/require" + "os" + "testing" + "github.com/Telmate/proxmox-api-go/test/api" +) + +func Test_Root_Login_Correct_Password(t *testing.T) { + api_test.SetEnvironmentVariables() + Test := api_test.Test{ + UserID: os.Getenv("PM_USER"), + Password: os.Getenv("PM_PASS"), + } + err := Test.Login() + require.NoError(t, err) +} + +func Test_Root_Login_Incorrect_Password(t *testing.T) { + api_test.SetEnvironmentVariables() + Test := api_test.Test{ + UserID: os.Getenv("PM_USER"), + Password: "xx", + } + err := Test.Login() + require.Error(t, err) +} + +func Test_Login_Incorrect_Username(t *testing.T) { + api_test.SetEnvironmentVariables() + Test := api_test.Test{ + UserID: "xx", + Password: "xx", + } + err := Test.Login() + require.Error(t, err) +} \ No newline at end of file diff --git a/test/api/CloudInit/cloudinit_test.go b/test/api/CloudInit/cloudinit_test.go new file mode 100644 index 00000000..68c5a275 --- /dev/null +++ b/test/api/CloudInit/cloudinit_test.go @@ -0,0 +1,55 @@ +package api_test + +import ( + "testing" + + pxapi "github.com/Telmate/proxmox-api-go/proxmox" + api_test "github.com/Telmate/proxmox-api-go/test/api" + "github.com/stretchr/testify/require" +) + +func Test_Cloud_Init_VM(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + config := _create_vm_spec(true) + vmref := _create_vmref() + + // Create network + configNetwork := _create_network_spec() + + err := configNetwork.CreateNetwork(Test.GetClient()) + require.NoError(t, err) + _, err = Test.GetClient().ApplyNetwork("pve") + require.NoError(t, err) + + disk := make(map[string]interface{}) + disk["import-from"] = "/tmp/jammy-server-cloudimg-amd64.img" + disk["type"] = "virtio" + disk["storage"] = "local" + + config.QemuDisks[0] = disk + config.Name = "Base-Image" + + err = config.CreateVm(vmref, Test.GetClient()) + require.NoError(t, err) + + config.Ipconfig = pxapi.IpconfigMap{} + config.Boot = "order=virtio0;ide2;net0" + + config.Ipconfig[0] = "gw=10.0.0.1,ip=10.0.0.2/24" + + err = config.UpdateConfig(vmref, Test.GetClient()) + require.NoError(t, err) + + testConfig, _ := pxapi.NewConfigQemuFromApi(vmref, Test.GetClient()) + + require.Equal(t, testConfig.Ipconfig[0], "gw=10.0.0.1,ip=10.0.0.2/24") + + _, err = Test.GetClient().DeleteVm(vmref) + require.NoError(t, err) + + _, err = Test.GetClient().DeleteNetwork("pve", "vmbr0") + require.NoError(t, err) + _, err = Test.GetClient().ApplyNetwork("pve") + require.NoError(t, err) +} diff --git a/test/api/CloudInit/shared_test.go b/test/api/CloudInit/shared_test.go new file mode 100644 index 00000000..ecdebb3a --- /dev/null +++ b/test/api/CloudInit/shared_test.go @@ -0,0 +1,59 @@ +package api_test + +import ( + pxapi "github.com/Telmate/proxmox-api-go/proxmox" +) + +func _create_vmref() (ref *pxapi.VmRef) { + ref = pxapi.NewVmRef(101) + ref.SetNode("pve") + ref.SetVmType("qemu") + return ref +} + +func _create_vm_spec(network bool) pxapi.ConfigQemu { + + disks := make(pxapi.QemuDevices) + + networks := make(pxapi.QemuDevices) + if network { + networks[0] = make(map[string]interface{}) + networks[0]["bridge"] = "vmbr0" + networks[0]["firewall"] = "true" + networks[0]["id"] = "0" + networks[0]["macaddr"] = "B6:8F:9D:7C:8F:BC" + networks[0]["model"] = "virtio" + } + + config := pxapi.ConfigQemu{ + Name: "test-qemu01", + Bios: "seabios", + Tablet: pxapi.PointerBool(true), + Memory: 2048, + QemuOs: "l26", + QemuCores: 1, + QemuSockets: 1, + QemuCpu: "kvm64", + QemuNuma: pxapi.PointerBool(false), + QemuKVM: pxapi.PointerBool(true), + Hotplug: "network,disk,usb", + QemuNetworks: networks, + QemuIso: "none", + Boot: "order=ide2;net0", + Scsihw: "virtio-scsi-pci", + QemuDisks: disks, + } + + return config +} + +func _create_network_spec() pxapi.ConfigNetwork { + config := pxapi.ConfigNetwork{ + Type: "bridge", + Iface: "vmbr0", + Node: "pve", + Autostart: true, + } + + return config +} diff --git a/test/api/Connection/connection_test.go b/test/api/Connection/connection_test.go new file mode 100644 index 00000000..cab38c07 --- /dev/null +++ b/test/api/Connection/connection_test.go @@ -0,0 +1,24 @@ +package api_test + +import ( + "testing" + + api_test "github.com/Telmate/proxmox-api-go/test/api" + "github.com/stretchr/testify/require" +) + +func Test_Connection_Certificate_No_Validation(t *testing.T) { + Test := api_test.Test{ + RequireSSL: false, + } + err := Test.CreateTest() + require.NoError(t, err) +} + +func Test_Connection_Certificate_Validation(t *testing.T) { + Test := api_test.Test{ + RequireSSL: true, + } + err := Test.CreateTest() + require.Error(t, err) +} diff --git a/test/api/Lxc/lxc_create_update_delete_test.go b/test/api/Lxc/lxc_create_update_delete_test.go new file mode 100644 index 00000000..ba0d9d52 --- /dev/null +++ b/test/api/Lxc/lxc_create_update_delete_test.go @@ -0,0 +1,69 @@ +package api_test + +import ( + "testing" + + pxapi "github.com/Telmate/proxmox-api-go/proxmox" + api_test "github.com/Telmate/proxmox-api-go/test/api" + "github.com/stretchr/testify/require" +) + +func Test_Create_Lxc_Container(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + config := _create_lxc_spec(true) + + err := config.CreateLxc(_create_vmref(), Test.GetClient()) + require.NoError(t, err) +} + +func Test_Lxc_Container_Is_Added(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + config, _ := pxapi.NewConfigLxcFromApi(_create_vmref(), Test.GetClient()) + + require.Equal(t, "alpine", config.OsType) +} + +func Test_Update_Lxc_Container(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + config, _ := pxapi.NewConfigLxcFromApi(_create_vmref(), Test.GetClient()) + + config.Cores = 2 + + err := config.UpdateConfig(_create_vmref(), Test.GetClient()) + + require.NoError(t, err) +} + +func Test_Lxc_Container_Is_Updated(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + config, _ := pxapi.NewConfigLxcFromApi(_create_vmref(), Test.GetClient()) + require.Equal(t, 2, config.Cores) +} + +func Test_Remove_Lxc_Container(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + _, err := Test.GetClient().DeleteVm(_create_vmref()) + + require.NoError(t, err) +} + +func Test_Create_Template_Lxc_Container(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + config := _create_lxc_spec(true) + + vmRef := _create_vmref() + err := config.CreateLxc(vmRef, Test.GetClient()) + require.NoError(t, err) + + err = Test.GetClient().CreateTemplate(vmRef) + require.NoError(t, err) +} diff --git a/test/api/Lxc/lxc_start_test.go b/test/api/Lxc/lxc_start_test.go new file mode 100644 index 00000000..64b4b6a9 --- /dev/null +++ b/test/api/Lxc/lxc_start_test.go @@ -0,0 +1,38 @@ +package api_test + +import ( + "testing" + + api_test "github.com/Telmate/proxmox-api-go/test/api" + "github.com/stretchr/testify/require" +) + +func Test_Start_Stop_Lxc_Container_Setup(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + config := _create_lxc_spec(false) + config.CreateLxc(_create_vmref(), Test.GetClient()) +} + +func Test_Start_Lxc_Container(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + _, err := Test.GetClient().StartVm(_create_vmref()) + require.NoError(t, err) +} + +func Test_Stop_Lxc_Container(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + _, err := Test.GetClient().StopVm(_create_vmref()) + require.NoError(t, err) +} + +func Test_Start_Stop_Lxc_Container_Cleanup(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + Test.GetClient().DeleteVm(_create_vmref()) +} diff --git a/test/api/Lxc/shared_test.go b/test/api/Lxc/shared_test.go new file mode 100644 index 00000000..b967b130 --- /dev/null +++ b/test/api/Lxc/shared_test.go @@ -0,0 +1,48 @@ +package api_test + +import ( + pxapi "github.com/Telmate/proxmox-api-go/proxmox" +) + +func _create_vmref() (ref *pxapi.VmRef) { + ref = pxapi.NewVmRef(200) + ref.SetNode("pve") + ref.SetVmType("lxc") + return ref +} + +func _create_lxc_spec(network bool) pxapi.ConfigLxc { + + disks := make(pxapi.QemuDevices) + disks[0] = make(map[string]interface{}) + disks[0]["type"] = "virtio" + disks[0]["storage"] = "local" + disks[0]["size"] = "8G" + + networks := make(pxapi.QemuDevices) + + config := pxapi.ConfigLxc{ + Hostname: "test-lxc01", + Cores: 1, + Memory: 128, + Password: "SuperSecretPassword", + Ostemplate: "local:vztmpl/alpine-3.17-default_20221129_amd64.tar.xz", + Storage: "local", + RootFs: disks[0], + Networks: networks, + Arch: "amd64", + CMode: "tty", + Console: true, + CPULimit: 0, + CPUUnits: 1024, + OnBoot: false, + Protection: false, + Start: false, + Swap: 512, + Template: false, + Tty: 2, + Unprivileged: false, + } + + return config +} diff --git a/test/api/Pool/pool_create_destroy_test.go b/test/api/Pool/pool_create_destroy_test.go new file mode 100644 index 00000000..2c90158c --- /dev/null +++ b/test/api/Pool/pool_create_destroy_test.go @@ -0,0 +1,34 @@ +package api_test + +import ( + "testing" + + api_test "github.com/Telmate/proxmox-api-go/test/api" + "github.com/stretchr/testify/require" +) + +func Test_Pool_Create(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + Test.GetClient().CreatePool("test-pool", "Test pool") +} + +func Test_Pool_Is_Created(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + _, err := Test.GetClient().GetPoolInfo("test-pool") + require.NoError(t, err) +} + +func Test_Pool_Delete(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + Test.GetClient().DeletePool("test-pool") +} + +func Test_Pool_Is_Deleted(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + _, err := Test.GetClient().GetPoolInfo("test-pool") + require.Error(t, err) +} diff --git a/test/api/Pool/pool_list_test.go b/test/api/Pool/pool_list_test.go new file mode 100644 index 00000000..d14656e0 --- /dev/null +++ b/test/api/Pool/pool_list_test.go @@ -0,0 +1,16 @@ +package api_test + +import ( + "testing" + + api_test "github.com/Telmate/proxmox-api-go/test/api" + "github.com/stretchr/testify/require" +) + +func Test_Pools_List(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + pools, err := Test.GetClient().GetPoolList() + require.NoError(t, err) + require.Equal(t, 1, len(pools)) +} diff --git a/test/api/Qemu/qemu_clone_test.go b/test/api/Qemu/qemu_clone_test.go new file mode 100644 index 00000000..20345bb0 --- /dev/null +++ b/test/api/Qemu/qemu_clone_test.go @@ -0,0 +1,52 @@ +package api_test + +import ( + "testing" + + pxapi "github.com/Telmate/proxmox-api-go/proxmox" + api_test "github.com/Telmate/proxmox-api-go/test/api" + "github.com/stretchr/testify/require" +) + +func _create_clone_vmref() (ref *pxapi.VmRef) { + ref = pxapi.NewVmRef(101) + ref.SetNode("pve") + ref.SetVmType("qemu") + return ref +} + +func Test_Clone_Qemu_VM(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + config := _create_vm_spec(false) + + config.CreateVm(_create_vmref(), Test.GetClient()) + + cloneConfig := _create_vm_spec(false) + + fullClone := 1 + + cloneConfig.Name = "test-qemu02" + cloneConfig.FullClone = &fullClone + + err := cloneConfig.CloneVm(_create_vmref(), _create_clone_vmref(), Test.GetClient()) + + require.NoError(t, err) + +} + +func Test_Qemu_VM_Is_Cloned(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + config, _ := pxapi.NewConfigQemuFromApi(_create_clone_vmref(), Test.GetClient()) + + require.Equal(t, "order=ide2;net0", config.Boot) +} + +func Test_Clone_Qemu_VM_Cleanup(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + Test.GetClient().DeleteVm(_create_clone_vmref()) + Test.GetClient().DeleteVm(_create_vmref()) +} diff --git a/test/api/Qemu/qemu_create_update_delete_test.go b/test/api/Qemu/qemu_create_update_delete_test.go new file mode 100644 index 00000000..30498821 --- /dev/null +++ b/test/api/Qemu/qemu_create_update_delete_test.go @@ -0,0 +1,56 @@ +package api_test + +import ( + "testing" + + pxapi "github.com/Telmate/proxmox-api-go/proxmox" + api_test "github.com/Telmate/proxmox-api-go/test/api" + "github.com/stretchr/testify/require" +) + +func Test_Create_Qemu_VM(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + config := _create_vm_spec(true) + + err := config.CreateVm(_create_vmref(), Test.GetClient()) + require.NoError(t, err) +} + +func Test_Qemu_VM_Is_Added(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + config, _ := pxapi.NewConfigQemuFromApi(_create_vmref(), Test.GetClient()) + + require.Equal(t, "order=ide2;net0", config.Boot) +} + +func Test_Update_Qemu_VM(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + config := _create_vm_spec(true) + + config.Boot = "order=net0;ide2" + + err := config.UpdateConfig(_create_vmref(), Test.GetClient()) + + require.NoError(t, err) +} + +func Test_Qemu_VM_Is_Updated(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + config, _ := pxapi.NewConfigQemuFromApi(_create_vmref(), Test.GetClient()) + require.Equal(t, "order=net0;ide2", config.Boot) +} + +func Test_Remove_Qemu_VM(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + _, err := Test.GetClient().DeleteVm(_create_vmref()) + + require.NoError(t, err) +} diff --git a/test/api/Qemu/qemu_start_test.go b/test/api/Qemu/qemu_start_test.go new file mode 100644 index 00000000..cd5f81b2 --- /dev/null +++ b/test/api/Qemu/qemu_start_test.go @@ -0,0 +1,38 @@ +package api_test + +import ( + "testing" + + api_test "github.com/Telmate/proxmox-api-go/test/api" + "github.com/stretchr/testify/require" +) + +func Test_Start_Stop_Qemu_VM_Setup(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + config := _create_vm_spec(false) + config.CreateVm(_create_vmref(), Test.GetClient()) +} + +func Test_Start_Qemu_VM(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + _, err := Test.GetClient().StartVm(_create_vmref()) + require.NoError(t, err) +} + +func Test_Stop_Qemu_VM(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + _, err := Test.GetClient().StopVm(_create_vmref()) + require.NoError(t, err) +} + +func Test_Start_Stop_Qemu_VM_Cleanup(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + Test.GetClient().DeleteVm(_create_vmref()) +} diff --git a/test/api/Qemu/shared_test.go b/test/api/Qemu/shared_test.go new file mode 100644 index 00000000..8999116d --- /dev/null +++ b/test/api/Qemu/shared_test.go @@ -0,0 +1,52 @@ +package api_test + +import ( + pxapi "github.com/Telmate/proxmox-api-go/proxmox" +) + +func _create_vmref() (ref *pxapi.VmRef) { + ref = pxapi.NewVmRef(100) + ref.SetNode("pve") + ref.SetVmType("qemu") + return ref +} + +func _create_vm_spec(network bool) pxapi.ConfigQemu { + + disks := make(pxapi.QemuDevices) + disks[0] = make(map[string]interface{}) + disks[0]["type"] = "virtio" + disks[0]["storage"] = "local" + disks[0]["size"] = "1G" + + networks := make(pxapi.QemuDevices) + if network { + networks[0] = make(map[string]interface{}) + networks[0]["bridge"] = "vmbr0" + networks[0]["firewall"] = "true" + networks[0]["id"] = "0" + networks[0]["macaddr"] = "B6:8F:9D:7C:8F:BC" + networks[0]["model"] = "virtio" + } + + config := pxapi.ConfigQemu{ + Name: "test-qemu01", + Bios: "seabios", + Tablet: pxapi.PointerBool(true), + Memory: 128, + QemuOs: "l26", + QemuCores: 1, + QemuSockets: 1, + QemuCpu: "kvm64", + QemuNuma: pxapi.PointerBool(false), + QemuKVM: pxapi.PointerBool(true), + Hotplug: "network,disk,usb", + QemuNetworks: networks, + QemuIso: "none", + Boot: "order=ide2;net0", + Scsihw: "virtio-scsi-pci", + QemuDisks: disks, + } + + return config +} diff --git a/test/api/Test.go b/test/api/Test.go new file mode 100644 index 00000000..6a71fa8a --- /dev/null +++ b/test/api/Test.go @@ -0,0 +1,58 @@ +package api_test + +import ( + "crypto/tls" + + pxapi "github.com/Telmate/proxmox-api-go/proxmox" +) + +type Test struct { + APIurl string + UserID string + Password string + OTP string + HttpHeaders string + RequireSSL bool + + _client *pxapi.Client +} + +func (test *Test) CreateClient() (err error) { + if test.APIurl == "" { + test.APIurl = "https://127.0.0.1:8006/api2/json" + } + if test.UserID == "" { + test.UserID = "root@pam" + } + if test.Password == "" { + test.Password = "root" + } + tlsConfig := &tls.Config{InsecureSkipVerify: true} + + if test.RequireSSL { + tlsConfig = nil + } + + test._client, err = pxapi.NewClient(test.APIurl, nil, test.HttpHeaders, tlsConfig, "", 300) + return err +} + +func (test *Test) GetClient() (client *pxapi.Client) { + return test._client +} + +func (test *Test) Login() (err error) { + if test._client == nil { + err = test.CreateClient() + if err != nil { + return err + } + } + err = test._client.Login(test.UserID, test.Password, test.OTP) + return err +} + +func (test *Test) CreateTest() (err error) { + err = test.Login() + return err +} diff --git a/test/api/UserManagement/create_token_test.go b/test/api/UserManagement/create_token_test.go new file mode 100644 index 00000000..80bc1725 --- /dev/null +++ b/test/api/UserManagement/create_token_test.go @@ -0,0 +1,81 @@ +package api_test + +import ( + "testing" + + pxapi "github.com/Telmate/proxmox-api-go/proxmox" + api_test "github.com/Telmate/proxmox-api-go/test/api" + "github.com/stretchr/testify/require" +) + +func Test_Create_Token(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + user, _ := pxapi.NewConfigUserFromApi(pxapi.UserID{Name: "root", Realm: "pam"}, Test.GetClient()) + + _, err := user.CreateApiToken(Test.GetClient(), pxapi.ApiToken{TokenId: "testing", Comment: "This is a test", Expire: 1679404904, Privsep: true}) + require.NoError(t, err) +} + +func Test_Token_Is_Created(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + user, _ := pxapi.NewConfigUserFromApi(pxapi.UserID{Name: "root", Realm: "pam"}, Test.GetClient()) + + tokens, _ := user.ListApiTokens(Test.GetClient()) + + listoftokens := *tokens + + t.Log(listoftokens[0].TokenId) + require.Equal(t, "testing", listoftokens[0].TokenId) +} + +func Test_Update_Token(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + user, _ := pxapi.NewConfigUserFromApi(pxapi.UserID{Name: "root", Realm: "pam"}, Test.GetClient()) + + tokens, _ := user.ListApiTokens(Test.GetClient()) + + listoftokens := *tokens + + listoftokens[0].Comment = "New Comment" + + err := user.UpdateApiToken(Test.GetClient(), listoftokens[0]) + require.NoError(t, err) +} + +func Test_Token_Is_Updated(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + user, _ := pxapi.NewConfigUserFromApi(pxapi.UserID{Name: "root", Realm: "pam"}, Test.GetClient()) + + tokens, _ := user.ListApiTokens(Test.GetClient()) + + listoftokens := *tokens + + require.Equal(t, "New Comment", listoftokens[0].Comment) +} + +func Test_Delete_Token(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + user, _ := pxapi.NewConfigUserFromApi(pxapi.UserID{Name: "root", Realm: "pam"}, Test.GetClient()) + + tokens, _ := user.ListApiTokens(Test.GetClient()) + + listoftokens := *tokens + + err := user.DeleteApiToken(Test.GetClient(), listoftokens[0]) + require.NoError(t, err) +} + +func Test_Token_Is_Deleted(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + user, _ := pxapi.NewConfigUserFromApi(pxapi.UserID{Name: "root", Realm: "pam"}, Test.GetClient()) + + tokens, _ := user.ListApiTokens(Test.GetClient()) + + require.Equal(t, 0, len(*tokens)) +} diff --git a/test/api/UserManagement/list_user_test.go b/test/api/UserManagement/list_user_test.go new file mode 100644 index 00000000..f3a100b1 --- /dev/null +++ b/test/api/UserManagement/list_user_test.go @@ -0,0 +1,18 @@ +package api_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + pxapi "github.com/Telmate/proxmox-api-go/proxmox" + api_test "github.com/Telmate/proxmox-api-go/test/api" +) + +func Test_List_Users(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + users, err := pxapi.ListUsers(Test.GetClient(), false) + require.NoError(t, err) + require.Equal(t, 1, len(*users)) +} diff --git a/test/api/UserManagement/user_management_test.go b/test/api/UserManagement/user_management_test.go new file mode 100644 index 00000000..ce47f59e --- /dev/null +++ b/test/api/UserManagement/user_management_test.go @@ -0,0 +1,66 @@ +package api_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + pxapi "github.com/Telmate/proxmox-api-go/proxmox" + api_test "github.com/Telmate/proxmox-api-go/test/api" +) + +var user = pxapi.ConfigUser{ + User: pxapi.UserID{ + Name: "Bob", + Realm: "pve", + }, + Comment: "", + Email: "bob@example.com", + Enable: true, + FirstName: "Bob", + LastName: "Bobson", +} + +func Test_Create_User(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + err := user.CreateUser(Test.GetClient()) + require.NoError(t, err) +} + +func Test_User_Is_Added(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + users, _ := pxapi.ListUsers(Test.GetClient(), false) + var found = false + for _, element := range *users { + if element == user { + found = true + } + } + + require.Equal(t, true, found) +} + +func Test_Remove_User(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + err := user.DeleteUser(Test.GetClient()) + require.NoError(t, err) +} + +func Test_User_Is_Removed(t *testing.T) { + Test := api_test.Test{} + _ = Test.CreateTest() + + users, _ := pxapi.ListUsers(Test.GetClient(), false) + var found = false + for _, element := range *users { + if element == user { + found = true + } + } + + require.Equal(t, false, found) +} diff --git a/test/api/preparations.go b/test/api/preparations.go new file mode 100644 index 00000000..0b4f7544 --- /dev/null +++ b/test/api/preparations.go @@ -0,0 +1,11 @@ +package api_test + +import ( + "os" +) + +func SetEnvironmentVariables() { + os.Setenv("PM_API_URL", "https://127.0.0.1:8006/api2/json") + os.Setenv("PM_USER", "root@pam") + os.Setenv("PM_PASS", "root") +} diff --git a/test/cli/Guest/Qemu/GuestQemu_100_test.go b/test/cli/Guest/Qemu/GuestQemu_100_test.go index b0e74e97..b550855c 100644 --- a/test/cli/Guest/Qemu/GuestQemu_100_test.go +++ b/test/cli/Guest/Qemu/GuestQemu_100_test.go @@ -25,7 +25,7 @@ func Test_GuestQemu_100_Create(t *testing.T) { "bios": "seabios", "tablet": true, "memory": 128, - "os": "l26", + "ostype": "l26", "cores": 1, "sockets": 1, "cpu": "host", @@ -60,7 +60,7 @@ func Test_GuestQemu_100_Get(t *testing.T) { "onboot": true, "tablet": true, "memory": 128, - "os": "l26", + "ostype": "l26", "cores": 1, "sockets": 1, "cpu": "host", diff --git a/test/cli/preperations.go b/test/cli/preperations.go index 08898b6b..abbfc5c0 100644 --- a/test/cli/preperations.go +++ b/test/cli/preperations.go @@ -5,7 +5,7 @@ import ( ) func SetEnvironmentVariables() { - os.Setenv("PM_API_URL", "https://192.168.67.50:8006/api2/json") + os.Setenv("PM_API_URL", "https://127.0.0.1:8006/api2/json") os.Setenv("PM_USER", "root@pam") - os.Setenv("PM_PASS", "Enter123!") + os.Setenv("PM_PASS", "root") } diff --git a/test/data/test_data_qemu/type_QemuDiskSerial.go b/test/data/test_data_qemu/type_QemuDiskSerial.go new file mode 100644 index 00000000..2bc96254 --- /dev/null +++ b/test/data/test_data_qemu/type_QemuDiskSerial.go @@ -0,0 +1,80 @@ +package test_data_qemu + +import "strings" + +// 60 valid charaters +func QemuDiskSerial_Max_Legal() string { + return "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ12345678" +} + +// 61 valid charaters +func QemuDiskSerial_Max_Illegal() string { + return QemuDiskSerial_Max_Legal() + "A" +} + +// Has all the legal runes for the QemuDiskSerial type. +func QemuDiskSerial_Legal() []string { + legalRunes := strings.Split("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_", "") + legalStrings := []string{ + "85__2-_2-p-d-___3GEEJ_6__--ccli_-_8--e-RJ3-_A_f_S_Z8-7Gga__5", + "___7_7-G-_rsMa_6---___a-TmE-H-AD_oV_K-0_9W_-y_4_k-_FU__fev-q", + "mU_a-8-_-3U--_D--o01_-T4E__4fsV-nX4kk_Nb-S_wYR7TktI-_vn1mcfR", + "5_GX4DevvIZ-_2-_u-_4_dKK19_P-K-kLpj-Hzw-b_12L20UU3--__Y_5_O-", + "h-m_B_J-mH_o3-r-JmE4-WqZ_tly--3aT-w_wznK_Q-0Hkk7W6b-Z____1IQ", + "-D-aS-dJSG_-s9_6_BWnt9z_6m_67oW__d4V8m-wb_6_8-_A--__-e--1X1-", + "0gLe0_P-r53-BfDk--1__23_-Zo0_V---f-__7_b52u6f72", + "dg-5d_Y1_v-7g-__I7__79_-j8-_-_", + "-7_G_-I-__a-w_-5GK-_9BMr-I_-_-f_7_-F-----6-FE-q7__0NJY-vL-e-", + "Xlgd__41_0E-S---1--_--_X1p8-_5YHt_1hO__2Op_73-5r-4", + "_0-Nn6__-u_07_A-1dqt-EG-_4-w90--A-ur_-3_", + "8_-xO-lN1_O_Q-4__-_7d-k-__s-p-_uQ2S_Ft_OR_-Ct_--Pb__U_U_g2t-", + "-q_-6h4--bbIl_A-xc_zl-v6Y-b-6__EG5_G__6_-q__pa5lAvq_F-Fhl-d3", + "53X-j50-ix--Iv_i", + "0_-7O6--51_4____-_-Zm4E__s-4_c_xN_Ik3_g-_t__-__C_---e-_--K_m", + "--_8__BvpmE6t-r_Ho_x-VZ_0___g__ui1v28ne--_-_k74_E3x_T_s--__B", + "__--9--c__7__9r-s-__yDTi-JSk-M_fH_-hGO9", + "_J-q_f_o_--l-MSe--9I_L_-lAs_-G-0--l-9_-6", + "--1tcB8J210JwYy22--c-_oXhHQ-Zyy--A1-dZ9394ieAaZrvC_U--KS2-r7", + "442_h_M-2-4G3K-_bN", + "b_NDX_3e6-k9_-HWZL_A5T_L-_-je66", + "__-_DfxD-9_l2_-n__Tn_-n_6aE_Bj_8chVS5p7Z_2812---h_4-_hsh-wAb", + "T08_-bRb-_zRKF1MPN6j7vyp0Pt_Q_x44__Y6_7_-XX-_-VqsRi_-0-s2-", + "Z___e_UT--G_9-9_E--__--ZT---1Leg__7-92_KErs-S-_t-K21_-OK_6Nx", + "-Plro-431-_", + "b__-_2_-u-K8--3-1-1", + "3_T40b6-Q__-Lz_3_4qN_", + "-p-_-T__MK-4D46-r7F_", + "Cn_f1-3J5T4x-g_-yoX3__0K_lV_W-glt-_eC_c_A_3l-m-_d_5_-5-iC___", + "-v7Y--G8T6-5k64-2-4kz-zqn70C__", + "7-7S70i_e-_a_8E---1X-_cW_--4z_8_4rm--6KCp_5_os8-_-4pvE_UC-_X", + "-mjp2--E8z-K-__2t", + "2b6-_-4jx6nM-Mm_-Sf_Wz-cv_---keD_6-_x_O0_D__d_Q-v04-h__x__6g", + "_-t0_Et_1_P0C5xSp_-3G2u68q4--YU7zQ_-_4-Y3OwT-8k-2F__Zf7_K78k", + "_R_-9-_5-K2R5zF_UJWfH_--_YQe8P4_nQ--JQ-Xu--lH-e_V0-c1_--e", + "J_y_-1X-_N84-Y-Ik6h-Y5Hz-_S__0-_-Y_V_-305-9i38TP_T--4y7Gb0g-", + "0Yl7_-Sn-_H__D_--_-_y-h-j1_-7mDVS9__R8Ty_-mwOJ-h__VmAy__q-_N", + "Yk-3Pt9VJqW-uu45w_f__a-TH_M_vkWq-O6__tk-P4-2-_CP-_7_owV_GyLZ", + "-3-M2362g3_9-___X404__0-E01TFHW3HV-1n_E_Ev_-_-E_-S----_F_k--", + "-0-5l1u_D_-Hyk_-_-s-3663_JE2-k", + "_-ML", + "75BHJ-tj-2Q-m-s4-f-y5-9L_-_dPC6A-_3y-3-Rp_-0V-3_I-6--_4THR2-", + "V__HM_guP-__ltfk___Et9V_v7U-83-TK15Xun", + "ax2_-7u_-GqG7d1BAe_4-dW3S0-1Vbj--_r_-63Hvn36P0---6N--e-_89_b", + "__-h_L-_3_Q__3czJ1_J_", + "-_-_-_9xGr-8207h9-4WerP5_-v0G_G9_-K9T8-gvQg-d__g____xf-F_---", + "M_6K2LVn--73f__-__A5V_t-l-_--5s92_u_f_n-R2Iy580M7J2vPe76GIt-", + "-f5oc-MG_GhR-8oxJ_---G____5--2-5___b-cTmfyO18-6Vip__-c_i5uV_", + "7-_17B__b113_9-cZ8S-", + "VvF1-MX6--Pj-M_-F_tU_6T-G___3U3-F4-5L__x-9uzgz_-_y4_-_-J--kE", + "", + QemuDiskSerial_Max_Legal(), + } + return append(legalRunes, legalStrings[:]...) +} + +// Has all the legal runes for the QemuDiskSerial type. +func QemuDiskSerial_Illegal() []string { + illegalRunes := strings.Split("`~!@#$%^&*()=+{}[]|\\;:'\"<,>.?/", "") + illegalSrings := []string{QemuDiskSerial_Max_Illegal()} + return append(illegalRunes, illegalSrings[:]...) +} diff --git a/test/data/test_data_qemu/type_QemuWorldWideName.go b/test/data/test_data_qemu/type_QemuWorldWideName.go new file mode 100644 index 00000000..3c10ad18 --- /dev/null +++ b/test/data/test_data_qemu/type_QemuWorldWideName.go @@ -0,0 +1,112 @@ +package test_data_qemu + +func QemuWorldWideName_Legal() []string { + return []string{ + "", + "0x8F14641E7F4B27D6", + "0x43135F8B89D7E9D9", + "0x0C92E69424B1609D", + "0x55049C1C8DA23E50", + "0x9F70557F3A5D09BC", + "0x2C04D021E89E41FD", + "0x3D7071A0976895A0", + "0x7914EAC8244DB5A2", + "0x30A8A0D6A24B9563", + "0x1DA1CDD690CE41FE", + "0x79D8D06D781F8D16", + "0x821F901FEFEA690F", + "0x13A9628C95F19A4F", + "0x6D9E42EFDE9EB1D1", + "0x2D7167DFCD4C5B48", + "0x5F792FE5AD87513D", + "0x9B9B9E1508D0B8B9", + "0x1C179E26A7B1A040", + "0x4C5DB30D3E9B1C0A", + "0x7AED3B1457078D6A", + "0x6F64A1761AB78825", + "0x5D8EDD9C5B95C478", + "0x3C2857A432EC3E9B", + "0x1EE0843512C6B6BE", + "0x45272E11136D0B4F", + "0x08C7D3C6E51EF776", + "0x28B3B1B41D53A770", + "0x4A937430E15BCE2B", + "0x183B7321B0F62A80", + "0x38C24563EAD1C289", + "0x5B1DAF347BEEF152", + "0x0E60D51E5CB50D1A", + "0x30B2393E28A3EDE4", + "0x47954C1E3B5CAB46", + "0x68C0D3D016D68C23", + "0x933BB8B523C8ACF6", + "0x1F25C742A9FBE60D", + "0x09B3EFEF2C88FFAC", + "0x8B0D9F41A15B7FA6", + "0x6E2A86B5E950857C", + "0x3F90A27347DC4F36", + "0x17619BC0F9F02DBB", + "0x31D591AE875E8712", + "0x787E9C1D6D25C12F", + "0x586AF239D285F6D5", + "0x961DDAA98CC9A58C", + "0x241FD8850C9CCDF2", + "0x9A4D8EAEBC50E3A9", + "0x6A33FEC7390CFC45", + "0x1B3CE82A3F2C8EFA", + "0x42C22809DB71A0E1", + } +} + +func QemuWorldWideName_Illegal() []string { + return []string{ + "0x44556677EEFF", + "0x09876:54321", + "0x1AB2:3C4D", + "586AF239D285F6D5,", + "0x0A0B0C0D0E0F", + "0x12:34:56:78", + "0x98:76:54:32", + "0x00:11:22:33:44:55:66:77", + "0xAA:BB:CC:DD:EE:FF", + "0x1:2:3:4:5:6:7", + "0122334455667788", + "x9A9B9C9D9E9F", + "0x1A1B1C1D1E1F", + "0x11223344AABBCC", + "x44556677", + "0x0987:6543", + "0x1A:B2:3C:4D", + "0x55667788", + "0x0A:0B:0C:0D", + "0x12:34:56", + "0x98:76:54", + "0x00:11:22:33:44:55:66", + "0xAA:BB:CC:DD:EE", + "1:2:3:4:5:6", + "0x11223344556677", + "9A9B9C9D9E9", + "0x1A1B1C1D1E", + "0x11223344AABBC", + "0x44556677EE", + "0x098765:54321", + "0x1AB:2C4D", + "0x556677889", + "0x0A0B0C0D0", + "12:34:5:78", + "0x98:76:54:3", + "0x00:11:22:33:44:55:6", + "0xAA:BB:CC:DD:EE:F", + "1:2:3:4:5:6:7", + "0x1122334455667", + "0x9A9B9C9D9E", + "0x1A1B1C1D1", + "11223344AABB", + "0x44556677EEF", + "0x09876:5432", + "0x1AB2:C4D", + "0x5566778899,", + "0x0A0B0C0D", + "0x12:34:56:7", + "x98:76:54:2", + } +} diff --git a/test/data/test_data_snapshot/type_SnapshotName.go b/test/data/test_data_snapshot/type_SnapshotName.go new file mode 100644 index 00000000..a08a49ec --- /dev/null +++ b/test/data/test_data_snapshot/type_SnapshotName.go @@ -0,0 +1,131 @@ +package test_data_snapshot + +// illegal character +func SnapshotName_Character_Illegal() []string { + return []string{ + "aBc123!4567890_-", + "Qwer@ty-1234_ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "x1y2#z3_4-5-6-7-8-9", + "HelloWo$rld_2023", + "Ab1_%cd2_ef3-gh4-ij5", + "a-_-^_-_-_-_-_-_-_-_-_-", + "snaps&hotName_2433242", + "A1_B2-*C3_D4-E5_F6", + "Xyz-123(_456_789-0", + "Test_Cas)e-123_456_789_0", + "a_1+", + "B-c_=2-D", + "E3_f4-G5_:H6-I7", + "JKL_MNO_PQ;R-STU_VWX_YZ0", + "aBgnhfjkfgd'ihfghudsfgio", + `Cdsdjfidshfu"isdghfsgffghdsufsdhfgdsfuah`, + "Ef-`gh", + "Ij-k~l-mn", + "Op-qr-st-u-vw-xy-z0-12-34-56-[78-90", + "Abcd_1234-EFGH_]5678-IJKL_9012", + "M-n-Op-qR-sT-uV{-wX-yZ", + "a_b-c_d_e-f_g_h_}i_j_k_l_m_n-o-p-q-r-s-t", + "Aa1_Bb2-C,c3_Dd4-Ee5_Ff6-Gg7_Hh8-Ii9", + "JjKkLl-MmNnOo.PpQqRrSsTtUuVvWwXxYyZz01", + "A->1", + "B-2<_C-3", + "D-4_?E-5-F-6", + "G-7-H/-8-I-9", + `J-0_K-\1-L-2-M-3-N-4-O-5-P-6-Q-7-R-8-S-9`, + "T-0_U-1-|V-2-W-3-X-4-Y-5-Z-6-7-8-9-0", + "a2😀", + } +} + +// 40 valid characters +func SnapshotName_Max_Legal() string { + return "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN" +} + +// 41 invalid characters +func SnapshotName_Max_Illegal() string { + return SnapshotName_Max_Legal() + "A" +} + +// 3 valid characters +func SnapshotName_Min_Legal() string { + return SnapshotName_Min_Illegal() + "c" +} + +// 2 invalid characters +func SnapshotName_Min_Illegal() string { + return "ab" +} + +// legal starting character +func SnapshotName_Start_Legal() string { + return "abc" +} + +// illegal starting character +func SnapshotName_Start_Illegal() []string { + return []string{ + "_" + SnapshotName_Start_Legal(), + "-" + SnapshotName_Start_Legal(), + "0" + SnapshotName_Start_Legal(), + "5" + SnapshotName_Start_Legal(), + } +} + +func SnapshotName_Legal() []string { + return []string{ + "aBc1234567890_-", + "Qwerty-1234_ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "x1y2z3_4-5-6-7-8-9", + "HelloWorld_2023", + "Ab1_cd2_ef3-gh4-ij5", + "a-_-_-_-_-_-_-_-_-_-_-", + "snapshotName_2433242", + "A1_B2-C3_D4-E5_F6", + "Xyz-123_456_789-0", + "Test_Case-123_456_789_0", + "a_1", + "B-c_2-D", + "E3_f4-G5_H6-I7", + "JKL_MNO_PQR-STU_VWX_YZ0", + "aBgnhfjkfgdihfghudsfgio", + "Cdsdjfidshfuisdghfsgffghdsufsdhfgdsfuahs", + "Ef-gh", + "Ij-kl-mn", + "Op-qr-st-u-vw-xy-z0-12-34-56-78-90", + "Abcd_1234-EFGH_5678-IJKL_9012", + "M-n-Op-qR-sT-uV-wX-yZ", + "a_b-c_d_e-f_g_h_i_j_k_l_m_n-o-p-q-r-s-t-", + "Aa1_Bb2-Cc3_Dd4-Ee5_Ff6-Gg7_Hh8-Ii9", + "JjKkLl-MmNnOoPpQqRrSsTtUuVvWwXxYyZz01", + "A-1", + "B-2_C-3", + "D-4_E-5-F-6", + "G-7-H-8-I-9", + "J-0_K-1-L-2-M-3-N-4-O-5-P-6-Q-7-R-8-S-9", + "T-0_U-1-V-2-W-3-X-4-Y-5-Z-6-7-8-9-0", + "a2B", + "c4D", + "e6F-g8H-i0J", + "k2L-m4N-o6P-q8R-s0T", + "u2V-w4X-y6Z-01-23-45-67-89-0", + "Abc_1234-Def_5678-Ghi_9012-Jkl_3456-Mno_", + "Pqr_2345-Stu_6789-Vwx_0123-Yz0_4567", + "a-B", + "c-D_e-F", + "g-H_i-J-k-L", + "m-N-o-P_q-R-s-T-u-V-w-X-y-Z-0", + "A_1b2-C3d4_E5f6-G7h8_I9j0-K1l2-M3n4", + "O5p6-Q7r8-S9t0-U1v2-W3x4-Y5z6-01", + "A2b3-C4d5-E6f7-G8h9-I0j1-K2l3-M4n5-O6", + "P7q8-R9s0-T1u2-V3w4-X5y6-Z7-89-01-23-45-", + "Ab_12-cD_34-eF_56-gH_78-iJ_90-kL_12-mN_3", + "O5p6-Q7r8-S9t0-U1v2-W3x4-Y5z6-01-23-45", + "A7b8-C9d0-E1f2-G3h4-I5j6-K7l8-M9n0-O1p2-", + "S5t6-U7v8-W9x0-Y1z2-34-56-78-90-12-34-56", + "Ab1C_d2E-F3G_h4I-J5k6L-m7N-o8P-q9R-s0T-u", + SnapshotName_Max_Legal(), + SnapshotName_Min_Legal(), + SnapshotName_Start_Legal(), + } +}