/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Authors:
Petr Krutov, <petr.krutov@digitalenergy.online>
Stanislav Solovev, <spsolovev@digitalenergy.online>
Kasim Baybikov, <kmbaybikov@basistech.ru>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/*
Terraform DECORT provider - manage resources provided by DECORT (Digital Energy Cloud
Orchestration Technology) with Terraform by Hashicorp.

Source code: https://repository.basistech.ru/BASIS/terraform-provider-decort

Please see README.md to learn where to place source code so that it
builds seamlessly.

Documentation: https://repository.basistech.ru/BASIS/terraform-provider-decort/wiki
*/

package kvmvm

import (
	"context"
	"errors"
	"fmt"
	"regexp"
	"sort"
	"strconv"

	"github.com/hashicorp/go-cty/cty"
	log "github.com/sirupsen/logrus"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/compute"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/disks"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/controller"

	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func matchComputes(computeList *compute.ListComputes) *compute.ListComputes {
	matched, _ := regexp.Compile(`[a-zA-Z]+\\d+-[a-zA-Z]+\\d+-[a-zA-Z]+\\d+`)
	result := computeList.FilterFunc(func(ic compute.ItemCompute) bool {
		res := matched.Match([]byte(ic.Name))
		return !res
	})

	return &result
}

func utilityComputeExtraDisksConfigure(ctx context.Context, d *schema.ResourceData, m interface{}, do_delta bool, disks compute.ListComputeDisks) error {
	c := m.(*controller.ControllerCfg)

	log.Debugf("utilityComputeExtraDisksConfigure: called for Compute ID %s with do_delta = %t", d.Id(), do_delta)

	old_set, new_set := d.GetChange("extra_disks")

	apiErrCount := 0
	var lastSavedError error

	if !do_delta {
		if new_set.(*schema.Set).Len() < 1 {
			return nil
		}

		for _, disk := range new_set.(*schema.Set).List() {
			computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
			req := compute.DiskAttachRequest{
				ComputeID: computeId,
				DiskID:    uint64(disk.(int)),
			}

			_, err := c.CloudAPI().Compute().DiskAttach(ctx, req)
			if err != nil {
				apiErrCount++
				lastSavedError = err
			}
		}

		if apiErrCount > 0 {
			log.Errorf("utilityComputeExtraDisksConfigure: there were %d error(s) when attaching disks to Compute ID %s. Last error was: %s",
				apiErrCount, d.Id(), lastSavedError)
			return lastSavedError
		}

		return nil
	}

	detach_set := old_set.(*schema.Set).Difference(new_set.(*schema.Set))
	log.Debugf("utilityComputeExtraDisksConfigure: detach set has %d items for Compute ID %s", detach_set.Len(), d.Id())

	if detach_set.Len() > 0 {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

		for _, diskId := range detach_set.List() {
			req := compute.DiskDetachRequest{
				ComputeID: computeId,
				DiskID:    uint64(diskId.(int)),
			}
			_, err := c.CloudAPI().Compute().DiskDetach(ctx, req)
			if err != nil {
				log.Errorf("utilityComputeExtraDisksConfigure: failed to detach disk ID %d from Compute ID %s: %s", diskId.(int), d.Id(), err)
				apiErrCount++
				lastSavedError = err
			}
		}

	}

	attach_set := new_set.(*schema.Set).Difference(old_set.(*schema.Set))
	log.Debugf("utilityComputeExtraDisksConfigure: attach set has %d items for Compute ID %s", attach_set.Len(), d.Id())
	for _, diskId := range attach_set.List() {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		req := compute.DiskAttachRequest{
			ComputeID: computeId,
			DiskID:    uint64(diskId.(int)),
		}

		log.Debug("before attach HERE0")

		flag := false

		// check if there is an extra disk in the disks from the platform, so as not to attach it if it is already attached
		for _, elem := range disks {
			if elem.ID == uint64(diskId.(int)) {
				flag = true
				break
			}
		}

		if flag {
			continue
		}

		_, err := c.CloudAPI().Compute().DiskAttach(ctx, req)
		if err != nil {
			log.Errorf("utilityComputeExtraDisksConfigure: failed to attach disk ID %d to Compute ID %s: %s", diskId.(int), d.Id(), err)
			apiErrCount++
			lastSavedError = err
		}
	}

	if apiErrCount > 0 {
		log.Errorf("utilityComputeExtraDisksConfigure: there were %d error(s) when managing disks of Compute ID %s. Last error was: %s",
			apiErrCount, d.Id(), lastSavedError)
		return lastSavedError
	}

	return nil
}

func utilityComputeNetworksConfigure(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	oldSet, newSet := d.GetChange("network")

	oldList := oldSet.(*schema.Set).List()
	newList := newSet.(*schema.Set).List()

	updateNetwork := differenceNetwork(oldList, newList)

	apiErrCount := 0
	var lastSavedError error

	log.Debugf("utilityComputeNetworksConfigure: detach set has %d items for Compute ID %s", len(updateNetwork.DetachMap), d.Id())
	for _, netData := range updateNetwork.DetachMap {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		req := compute.NetDetachRequest{
			ComputeID: computeId,
			IPAddr:    netData["ip_address"].(string),
			MAC:       netData["mac"].(string),
		}

		_, err := c.CloudAPI().Compute().NetDetach(ctx, req)
		if err != nil {
			log.Errorf("utilityComputeNetworksConfigure: failed to detach net ID %d of type %s from Compute ID %s: %s",
				netData["net_id"].(int), netData["net_type"].(string), d.Id(), err)
			apiErrCount++
			lastSavedError = err
		}
	}

	log.Debugf("utilityComputeNetworksConfigure: changeIp set has %d items for Compute ID %s", len(updateNetwork.ChangeIPMap), d.Id())
	for _, netData := range updateNetwork.ChangeIPMap {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		req := compute.ChangeIPRequest{
			ComputeID: computeId,
			NetType:   netData["net_type"].(string),
			NetID:     uint64(netData["net_id"].(int)),
			IPAddr:    netData["ip_address"].(string),
		}

		_, err := c.CloudAPI().Compute().ChangeIP(ctx, req)
		if err != nil {
			log.Errorf("utilityComputeNetworksConfigure: failed to change net ID %d of type %s from Compute ID %s: %s",
				netData["net_id"].(int), netData["net_type"].(string), d.Id(), err)
			apiErrCount++
			lastSavedError = err
		}
	}

	needStart := false

	if oldSet.(*schema.Set).Len() == len(updateNetwork.DetachMap) || oldSet.(*schema.Set).Len() == 0 || hasDPDKnetwork(updateNetwork.AttachMap) || len(updateNetwork.ChangeMacMap) != 0 {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		if err := utilityComputeStop(ctx, computeId, m); err != nil {
			apiErrCount++
			lastSavedError = err
		}
		if start := d.Get("started"); start.(bool) {
			needStart = true
		}
	}

	log.Debugf("utilityComputeNetworksConfigure: changeMac set has %d items for Compute ID %s", len(updateNetwork.ChangeMacMap), d.Id())
	for _, netData := range updateNetwork.ChangeMacMap {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		req := compute.ChangeMACRequest{
			ComputeID:  computeId,
			NewMAC:     netData["mac"].(string),
			СurrentMAC: netData["old_mac"].(string),
		}

		_, err := c.CloudAPI().Compute().ChangeMAC(ctx, req)
		if err != nil {
			log.Errorf("utilityComputeNetworksConfigure: failed to change mac %s to  %s from Compute ID %s: %s",
				req.СurrentMAC, req.NewMAC, d.Id(), err)
			apiErrCount++
			lastSavedError = err
		}
	}

	sort.Slice(updateNetwork.AttachMap, func(i, j int) bool {
		weightI := updateNetwork.AttachMap[i]["weight"].(int)
		weightJ := updateNetwork.AttachMap[j]["weight"].(int)
		if weightI == 0 {
			return false
		}
		if weightJ == 0 {
			return true
		}
		return weightI < weightJ
	})

	log.Debugf("utilityComputeNetworksConfigure: attach set has %d items for Compute ID %s", len(updateNetwork.AttachMap), d.Id())
	for _, netData := range updateNetwork.AttachMap {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		req := compute.NetAttachRequest{
			ComputeID: computeId,
			NetType:   netData["net_type"].(string),
			NetID:     uint64(netData["net_id"].(int)),
		}

		if netData["ip_address"].(string) != "" {
			req.IPAddr = netData["ip_address"].(string)
		}

		if netData["mac"].(string) != "" {
			req.MACAddr = netData["mac"].(string)
		}

		if netData["sdn_interface_id"].(string) != "" {
			req.SDNInterfaceID = netData["sdn_interface_id"].(string)
		}

		if enabledNetwork(d.GetRawConfig().GetAttr("network"), req.NetID, req.NetType) {
			req.Enabled = netData["enabled"].(bool)
		}

		if req.NetType == "DPDK" || req.NetType == "EXTNET" {
			req.MTU = uint64(netData["mtu"].(int))
		}

		if req.NetType == "DPDK" || req.NetType == "VFNIC" {
			if netMask, ok := netData["net_mask"].(int); ok && netMask > 0 {
				req.NetMask = uint64(netMask)
			}
		}

		_, err := c.CloudAPI().Compute().NetAttach(ctx, req)
		if err != nil {
			log.Errorf("utilityComputeNetworksConfigure: failed to attach net ID %d of type %s to Compute ID %s: %s",
				netData["net_id"].(int), netData["net_type"].(string), d.Id(), err)
			apiErrCount++
			lastSavedError = err
		}
	}

	if needStart {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		var altBootID uint64
		if altBootIDRaw, ok := d.Get("alt_boot_id").(int); ok {
			altBootID = uint64(altBootIDRaw)
		} else {
			altBootID = 0
		}
		if numErr, err := utilityComputeStart(ctx, computeId, altBootID, m); err != nil {
			apiErrCount += numErr
			lastSavedError = err
		}
	}

	log.Debugf("utilityComputeNetworksConfigure: changeMTU set has %d items for Compute ID %s", len(updateNetwork.ChangeMTUMap), d.Id())
	for _, netData := range updateNetwork.ChangeMTUMap {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		req := compute.ChangeMTURequest{
			ComputeID: computeId,
			Interface: netData["mac"].(string),
			MTU:       uint64(netData["mtu"].(int)),
		}

		_, err := c.CloudAPI().Compute().ChangeMTU(ctx, req)
		if err != nil {
			log.Errorf("utilityComputeNetworksConfigure: failed to change MTU ID %d of type %s from Compute ID %s: %s",
				netData["net_id"].(int), netData["net_type"].(string), d.Id(), err)
			apiErrCount++
			lastSavedError = err
		}
	}

	log.Debugf("utilityComputeNetworksConfigure: enableMap set has %d items for Compute ID %s", len(updateNetwork.EnableMap), d.Id())
	for _, netData := range updateNetwork.EnableMap {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		req := compute.ChangeLinkStateRequest{
			ComputeID: computeId,
			Interface: netData["mac"].(string),
			State:     "off",
		}

		if netData["enabled"].(bool) {
			req.State = "on"
		}

		_, err := c.CloudAPI().Compute().ChangeLinkState(ctx, req)
		if err != nil {
			log.Errorf("utilityComputeNetworksConfigure: failed to change link state network ID %d of type %s from Compute ID %s: %s",
				netData["net_id"].(int), netData["net_type"].(string), d.Id(), err)
			apiErrCount++
			lastSavedError = err
		}
	}

	if apiErrCount > 0 {
		log.Errorf("utilityComputeNetworksConfigure: there were %d error(s) when managing networks of Compute ID %s. Last error was: %s",
			apiErrCount, d.Id(), lastSavedError)
		return lastSavedError
	}

	return nil
}

func utilityComputeSecGroupsConfigure(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
	simpleCompRec, err := utilityComputeCheckPresence(ctx, d, m)
	if err != nil {
		return err
	}

	apiErrCount := 0
	var lastSavedError error

	oldSecGroups, newSecGroups := d.GetChange("security_groups")
	updateSecGroups := (newSecGroups.(*schema.Set).Difference(oldSecGroups.(*schema.Set))).List()

	log.Debugf("utilityComputeSecGroupsConfigure: update security groups has %d items for Compute ID %s", len(updateSecGroups), d.Id())
	if len(updateSecGroups) > 0 {
		for _, elem := range updateSecGroups {
			secGroupsMap := elem.(map[string]interface{})

			netType := secGroupsMap["net_type"].(string)
			netId := uint64(secGroupsMap["net_id"].(int))
			var mac string
			for _, iface := range simpleCompRec.Interfaces {
				if iface.NetID == netId && iface.NetType == netType {
					mac = iface.MAC
					break
				}
			}
			if mac == "" {
				log.Errorf("utilityComputeSecGroupsConfigure: Network with type %s and id %d is not connected to the compute %s",
					netType, netId, d.Id())
				apiErrCount++
				lastSavedError = errors.New(fmt.Sprintf("utilityComputeSecGroupsConfigure: Network with type %s and id %d is not connected to the compute %s",
					netType, netId, d.Id()))
				continue
			}
			secGroupsIDs := make([]uint64, 0)
			for _, id := range secGroupsMap["security_groups"].(*schema.Set).List() {
				secGroupsIDs = append(secGroupsIDs, uint64(id.(int)))
			}
			log.Debugf("utilityComputeSecGroupsConfigure: Configure security groups interface parameters on Network with type %s and id %d", netType, netId)
			req := compute.ChangeSecGroupsRequest{
				ComputeID: computeId,
				Interface: mac,
				SecGroups: secGroupsIDs,
			}

			if secGroupsMap["enable_secgroups"] != nil {
				req.EnableSecGroups = secGroupsMap["enable_secgroups"].(bool)
			}

			_, err := c.CloudAPI().Compute().ChangeSecGroups(ctx, req)
			if err != nil {
				apiErrCount++
				lastSavedError = err
			}
		}
	}

	if apiErrCount > 0 {
		log.Errorf("utilityComputeSecGroupsConfigure: there were %d error(s) when managing security groups of Compute ID %s. Last error was: %s",
			apiErrCount, d.Id(), lastSavedError)
		return lastSavedError
	}

	return nil
}

func hasDPDKnetwork(networkAttachMap []map[string]interface{}) bool {
	for _, elem := range networkAttachMap {
		if elem["net_type"].(string) == "DPDK" {
			return true
		}
	}
	return false
}

func utilityComputeCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (compute.RecordCompute, error) {
	c := m.(*controller.ControllerCfg)
	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
	req := compute.GetRequest{
		ComputeID: computeId,
	}

	computeRecord, err := c.CloudAPI().Compute().Get(ctx, req)
	if err != nil {
		return compute.RecordCompute{}, err
	}

	return *computeRecord, nil
}

func utilityComputeStop(ctx context.Context, computeID uint64, m interface{}) error {
	c := m.(*controller.ControllerCfg)
	req := compute.StopRequest{
		ComputeID: computeID,
		Force:     true,
	}

	log.Debugf("utilityComputeNetworksConfigure: stopping compute %d", computeID)
	_, err := c.CloudAPI().Compute().Stop(ctx, req)
	if err != nil {
		return err
	}
	return nil
}

func utilityComputeStart(ctx context.Context, computeID uint64, altBootID uint64, m interface{}) (int, error) {
	c := m.(*controller.ControllerCfg)
	startReq := compute.StartRequest{ComputeID: computeID}

	if altBootID > 0 {
		startReq.AltBootID = altBootID
	}

	log.Debugf("utilityComputeNetworksConfigure: starting compute %d", computeID)
	_, err := c.CloudAPI().Compute().Start(ctx, startReq)
	if err != nil {
		return 1, err
	}
	return 0, nil
}

func utilityComputeUpdatePciDevices(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)
	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
	oldSet, newSet := d.GetChange("pci_devices")
	deletedDevices := (oldSet.(*schema.Set).Difference(newSet.(*schema.Set))).List()
	if len(deletedDevices) > 0 {
		for _, ar := range deletedDevices {
			arConv := ar.(int)
			req := compute.DetachPCIDeviceRequest{
				ComputeID: computeId,
				DeviceID:  uint64(arConv),
			}
			_, err := c.CloudAPI().Compute().DetachPCIDevice(ctx, req)
			if err != nil {
				return err
			}
		}
	}

	added := (newSet.(*schema.Set).Difference(oldSet.(*schema.Set))).List()
	if len(added) > 0 {
		for _, ar := range added {
			arConv := ar.(int)
			req := compute.AttachPCIDeviceRequest{
				ComputeID: computeId,
				DeviceID:  uint64(arConv),
			}

			_, err := c.CloudAPI().Compute().AttachPCIDevice(ctx, req)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func differenceNetwork(oldList, newList []interface{}) *updatedNetwork {
	attachMap := make([]map[string]interface{}, 0)
	changeIpMap := make([]map[string]interface{}, 0)
	changeMacMap := make([]map[string]interface{}, 0)
	changeMTUMap := make([]map[string]interface{}, 0)
	detachMap := make([]map[string]interface{}, 0)
	enableMap := make([]map[string]interface{}, 0)
	for _, oldNetwork := range oldList {
		oldMap := oldNetwork.(map[string]interface{})
		found := false
		for _, newNetwork := range newList {
			newMap := newNetwork.(map[string]interface{})
			if compareNetwork(newMap, oldMap) {
				found = true
				if (newMap["net_type"].(string) == "EXTNET" || newMap["net_type"].(string) == "VINS") && (newMap["ip_address"] != oldMap["ip_address"] && newMap["ip_address"].(string) != "") {
					changeIpMap = append(changeIpMap, newMap)
				}
				if newMap["mac"] != oldMap["mac"] && newMap["mac"].(string) != "" {
					newMap["old_mac"] = oldMap["mac"]
					changeMacMap = append(changeMacMap, newMap)
				}
				if (newMap["net_type"].(string) == "EXTNET" || newMap["net_type"].(string) == "DPDK") && (newMap["mtu"] != oldMap["mtu"] && newMap["mtu"].(int) != 0) {
					changeMTUMap = append(changeMTUMap, newMap)
				}
				if newMap["enabled"].(bool) != oldMap["enabled"].(bool) {
					mac, _ := newMap["mac"].(string)
					if mac == "" {
						newMap["mac"] = oldMap["mac"]
					}
					enableMap = append(enableMap, newMap)
				}
			}
			if found {
				break
			}
		}
		if found {
			continue
		}
		detachMap = append(detachMap, oldMap)
	}

	for _, newNetwork := range newList {
		newMap := newNetwork.(map[string]interface{})
		found := false
		for _, oldNetwork := range oldList {
			oldMap := oldNetwork.(map[string]interface{})
			if compareNetwork(newMap, oldMap) {
				found = true
				break
			}
		}
		if found {
			continue
		}
		attachMap = append(attachMap, newMap)
	}

	res := updatedNetwork{
		DetachMap:    detachMap,
		ChangeIPMap:  changeIpMap,
		ChangeMacMap: changeMacMap,
		ChangeMTUMap: changeMTUMap,
		AttachMap:    attachMap,
		EnableMap:    enableMap,
	}

	return &res
}

func compareNetwork(newMap, oldMap map[string]interface{}) bool {
	return newMap["net_type"] == oldMap["net_type"] && newMap["net_id"] == oldMap["net_id"] && newMap["sdn_interface_id"] == oldMap["sdn_interface_id"] && newMap["weight"] == oldMap["weight"]
}

func utilityComputeUpdateZoneID(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	req := compute.MigrateToZoneRequest{
		ComputeID: computeId,
	}

	zoneID, ok := d.GetOk("zone_id")
	if ok {
		req.ZoneID = uint64(zoneID.(int))
	}

	_, err := c.CloudAPI().Compute().MigrateToZone(ctx, req)
	if err != nil {
		return err
	}

	return nil
}

func enabledNetwork(rawNetworkConfig cty.Value, netID uint64, netType string) bool {
	for _, netConfigVal := range rawNetworkConfig.AsValueSlice() {
		if netConfigVal.IsNull() {
			continue
		}

		netConfig := netConfigVal.AsValueMap()

		tempID, _ := netConfig["net_id"].AsBigFloat().Int64()
		configNetID := uint64(tempID)

		configNetType := netConfig["net_type"].AsString()

		if configNetID == netID && configNetType == netType {
			enabledVal := netConfig["enabled"]
			return !enabledVal.IsNull()
		}
	}

	return false
}

func getComputeDiskIDsAPI(disksList compute.ListComputeDisks, disksBlocks, extraDisks []interface{}, bootDiskId uint64) []interface{} {
	res := make([]interface{}, 0)

	if len(disksBlocks) == 0 {
		return res
	}

	sort.Slice(disksList, func(i, j int) bool {
		return disksList[i].ID < disksList[j].ID
	})

	for _, disk := range disksList {
		if disk.ID == bootDiskId || findInExtraDisks(uint(disk.ID), extraDisks) {
			continue
		}
		res = append(res, disk.ID)
	}

	return res
}

func utilityComputeCreateIOTune(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)
	diskList := d.Get("disks").([]interface{})

	iotuneArr := make([]interface{}, 0, len(diskList))
	hasAny := false
	for _, elem := range diskList {
		diskVal := elem.(map[string]interface{})
		iotune := diskVal["iotune"].([]interface{})
		iotuneArr = append(iotuneArr, iotune)
		if len(iotune) > 0 {
			hasAny = true
		}
	}

	if !hasAny {
		return nil
	}

	computeRec, err := utilityComputeCheckPresence(ctx, d, m)
	if err != nil {
		return err
	}
	bootDisk := findBootDisk(computeRec.Disks, computeRec.Chipset)
	computeDisksIDs := getComputeDiskIDsAPI(computeRec.Disks, diskList, d.Get("extra_disks").(*schema.Set).List(), bootDisk.ID)

	for i, diskID := range computeDisksIDs {
		if i >= len(iotuneArr) {
			continue
		}
		iotune, ok := iotuneArr[i].([]interface{})
		if !ok || len(iotune) == 0 {
			continue
		}
		iotuneMap := iotune[0].(map[string]interface{})
		req := disks.LimitIORequest{
			DiskID:           diskID.(uint64),
			ReadBytesSec:     uint64(iotuneMap["read_bytes_sec"].(int)),
			ReadBytesSecMax:  uint64(iotuneMap["read_bytes_sec_max"].(int)),
			ReadIOPSSec:      uint64(iotuneMap["read_iops_sec"].(int)),
			ReadIOPSSecMax:   uint64(iotuneMap["read_iops_sec_max"].(int)),
			SizeIOPSSec:      uint64(iotuneMap["size_iops_sec"].(int)),
			TotalBytesSec:    uint64(iotuneMap["total_bytes_sec"].(int)),
			TotalBytesSecMax: uint64(iotuneMap["total_bytes_sec_max"].(int)),
			TotalIOPSSec:     uint64(iotuneMap["total_iops_sec"].(int)),
			TotalIOPSSecMax:  uint64(iotuneMap["total_iops_sec_max"].(int)),
			WriteBytesSec:    uint64(iotuneMap["write_bytes_sec"].(int)),
			WriteBytesSecMax: uint64(iotuneMap["write_bytes_sec_max"].(int)),
			WriteIOPSSec:     uint64(iotuneMap["write_iops_sec"].(int)),
			WriteIOPSSecMax:  uint64(iotuneMap["write_iops_sec_max"].(int)),
		}
		_, err := c.CloudAPI().Disks().LimitIO(ctx, req)
		if err != nil {
			return err
		}
	}

	return nil
}
