/* Copyright (c) Citrix Systems Inc. 
 * All rights reserved. 
 * 
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met: 
 * 
 * *   Redistributions of source code must retain the above 
 *     copyright notice, this list of conditions and the 
 *     following disclaimer. 
 * *   Redistributions in binary form must reproduce the above 
 *     copyright notice, this list of conditions and the 
 *     following disclaimer in the documentation and/or other 
 *     materials provided with the distribution. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
 * SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using XenAdmin.Network;
using XenAPI;
using XenAdmin.Core;
using System.Xml;


namespace XenAdmin.Actions.VMActions
{
    public enum InstallMethod
    {
        None,
        CD,
        Network
    }
    public class CreateVMAction : AsyncAction
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private readonly string NameLabel;
        private readonly string NameDescription;
        private readonly InstallMethod InsMethod;
        private readonly string PvArgs;
        private readonly VDI Cd;
        private readonly string Url;
        private readonly Host HomeServer;
        private readonly long Vcpus;
        private readonly long MemoryDynamicMin, MemoryDynamicMax, MemoryStaticMax;
        private readonly List<DiskDescription> Disks;
        private readonly List<VIF> Vifs;
        private readonly bool StartAfter;
        private readonly Host CopyBiosStringsFrom;
        private readonly SR FullCopySR;
        private readonly GPU_group GpuGroup;
        private readonly VGPU_type VgpuType;
        private readonly long CoresPerSocket;
        private readonly string cloudConfigDriveTemplateText;
        private SR firstSR = null;

        private Action<VMStartAbstractAction, Failure> _startDiagnosisForm;
        private Action<VM, bool> _warningDialogHAInvalidConfig;

        private bool PointOfNoReturn;

        private bool assignOrRemoveVgpu;

        /// <summary>
        /// These are the RBAC dependencies that you always need to create a VM. Check CreateVMAction constructor for runtime dependent dependencies.
        /// </summary>
        public static RbacMethodList StaticRBACDependencies = new RbacMethodList(
            // provision VM
            "vm.provision",
            "vm.set_other_config",
            // set VM Params
            "vm.set_name_label",
            "vm.set_name_description",
            "vm.set_VCPUs_max",
            "vm.set_VCPUs_at_startup",
            // set VM Boot Params
            "vm.set_HVM_boot_params",
            "vm.set_PV_args",
            "vm.set_other_config",
            // Add CD Drive
            "vbd.eject",
            "vbd.insert",
            // Create CD Drive
            "vbd.create",
            // Add disks
            "vdi.destroy",
            "vdi.create",
            "vdi.set_sm_config",
            "vbd.create",
            "vbd.destroy",
            "vdi.copy",
            // Add networks
            "vif.create",
            "vm.set_platform"
        );

        public CreateVMAction(IXenConnection connection, VM template, Host copyBiosStringsFrom,
            string name, string description, InstallMethod installMethod,
            string pvArgs, VDI cd, string url, Host homeServer, long vcpus,
            long memoryDynamicMin, long memoryDynamicMax, long memoryStaticMax,
            List<DiskDescription> disks, SR fullCopySR, List<VIF> vifs, bool startAfter,
            Action<VM, bool> warningDialogHAInvalidConfig,
            Action<VMStartAbstractAction, Failure> startDiagnosisForm,
            GPU_group gpuGroup, VGPU_type vgpuType, bool modifyVgpuSettings, long coresPerSocket, string cloudConfigDriveTemplateText)
            : base(connection, string.Format(Messages.CREATE_VM, name),
            string.Format(Messages.CREATE_VM_FROM_TEMPLATE, name, Helpers.GetName(template)))
        {
            Template = template;
            CopyBiosStringsFrom = copyBiosStringsFrom;
            FullCopySR = fullCopySR;
            NameLabel = name;
            NameDescription = description;
            InsMethod = installMethod;
            PvArgs = pvArgs;
            Cd = cd;
            Url = url;
            HomeServer = homeServer;
            Vcpus = vcpus;
            MemoryDynamicMin = memoryDynamicMin;
            MemoryDynamicMax = memoryDynamicMax;
            MemoryStaticMax = memoryStaticMax;
            Disks = disks;
            Vifs = vifs;
            StartAfter = startAfter;
            _warningDialogHAInvalidConfig = warningDialogHAInvalidConfig;
            _startDiagnosisForm = startDiagnosisForm;
            GpuGroup = gpuGroup;
            VgpuType = vgpuType;
            CoresPerSocket = coresPerSocket;
            this.cloudConfigDriveTemplateText = cloudConfigDriveTemplateText;

            Pool pool_of_one = Helpers.GetPoolOfOne(Connection);
            if (HomeServer != null || pool_of_one != null) // otherwise we have no where to put the action
                AppliesTo.Add(HomeServer != null ? HomeServer.opaque_ref : pool_of_one.opaque_ref);

            assignOrRemoveVgpu = GpuGroup != null && VgpuType != null || modifyVgpuSettings && Helpers.GpuCapability(Connection);

            #region RBAC Dependencies

            if (StartAfter)
                ApiMethodsToRoleCheck.Add("vm.start");
            if (HomeServerChanged())
                ApiMethodsToRoleCheck.Add("vm.set_affinity");
            if (Template.memory_dynamic_min != MemoryDynamicMin || Template.memory_dynamic_max != MemoryDynamicMax || Template.memory_static_max != MemoryStaticMax)
                ApiMethodsToRoleCheck.Add("vm.set_memory_limits");

            if (assignOrRemoveVgpu)
            {
                ApiMethodsToRoleCheck.Add("VGPU.destroy");
                ApiMethodsToRoleCheck.Add("VGPU.create");
            }

            ApiMethodsToRoleCheck.AddRange(StaticRBACDependencies);

            ApiMethodsToRoleCheck.AddRange(Role.CommonTaskApiList);
            ApiMethodsToRoleCheck.AddRange(Role.CommonSessionApiList);

            #endregion
        }

        protected override void Run()
        {
            if (FullCopySR != null)
            {
                // VM.copy is the best call to make if all target disks are on the same SR.
                // however, if the target disks are on the same SR as the source disks, then the user is 
                // given the choice of a fast-clone (VM.clone) or a full-copy (VM.copy) on the storage page of the wizard. If the
                // user chose a VM.clone, then FullCopySR will be null.

                RelatedTask = VM.async_copy(Session, Template.opaque_ref, HiddenVmName, FullCopySR.opaque_ref);
            }
            else
            {
                // if the target disks are on mixed storage or the user chose to a do a fast-clone on the storage
                // page then we end up here.

                RelatedTask = VM.async_clone(Session, Template.opaque_ref, HiddenVmName);
            }

            Description = string.Format(Messages.CLONING_TEMPLATE, Helpers.GetName(Template));
            PollToCompletion(0, 10);

            VM = Connection.WaitForCache(new XenRef<VM>(Result));

            CopyBiosStrings();
            SetXenCenterProperties();
            ProvisionVM();
            SetVMParams();
            SetVMBootParams();
            AddCdDrive();
            AddDisks();
            AddNetworks();
            XenAdminConfigManager.Provider.ShowObject(VM.opaque_ref);
            PointOfNoReturn = true;

            CloudCreateConfigDrive();
           
            AssignVgpu();

            if (StartAfter)
            {
                Description = Messages.STARTING_VM;
                var startAction = new VMStartAction(VM, _warningDialogHAInvalidConfig, _startDiagnosisForm);
                startAction.RunAsync();
            }

            Description = Messages.VM_SUCCESSFULLY_CREATED;
        }

        private void CloudCreateConfigDrive()
        {
            if (Template.CanHaveCloudConfigDrive && !string.IsNullOrEmpty(cloudConfigDriveTemplateText))
            {
                Description = Messages.CREATING_CLOUD_CONFIG_DRIVE; 

                var parameters = new Dictionary<string, string>();
                parameters.Add("vmuuid", VM.uuid);
                parameters.Add("sruuid", firstSR.uuid);
                parameters.Add("configuration", cloudConfigDriveTemplateText.Replace("\r\n", "\n"));

                var action = new ExecutePluginAction(Connection, HomeServer ?? Helpers.GetMaster(Connection),
                            "xscontainer",//plugin
                            "create_config_drive",//function
                            parameters,
                            true); //hidefromlogs

                action.RunExternal(Connection.Session);
                var result = action.Result.Replace("\n", Environment.NewLine);
            }
        }

        private void AssignVgpu()
        {
            if (assignOrRemoveVgpu)
            {
                var action = new GpuAssignAction(VM, GpuGroup, VgpuType);
                action.RunExternal(Session);
            }
        }

        private void CopyBiosStrings()
        {
            if (CopyBiosStringsFrom != null && Template.DefaultTemplate)
            {
                VM.copy_bios_strings(Session, this.VM.opaque_ref, CopyBiosStringsFrom.opaque_ref);
            }
        }

        private void SetXenCenterProperties()
        {
            XenAdminConfigManager.Provider.HideObject(VM.opaque_ref);
            AppliesTo.Add(VM.opaque_ref);
        }

        private void SetVMParams()
        {
            Description = Messages.SETTING_VM_PROPERTIES;
            XenAPI.VM.set_name_label(Session, VM.opaque_ref, NameLabel);
            XenAPI.VM.set_name_description(Session, VM.opaque_ref, NameDescription);
            ChangeVCPUSettingsAction vcpuAction = new ChangeVCPUSettingsAction(VM, Vcpus);
            vcpuAction.RunExternal(Session);

            // set cores-per-socket
            Dictionary<string, string> platform = VM.platform == null ?
                            new Dictionary<string, string>() :
                            new Dictionary<string, string>(VM.platform);
            platform["cores-per-socket"] = CoresPerSocket.ToString();
            VM.set_platform(Session, VM.opaque_ref, platform);

            // Check these values have changed before setting them, as they are RBAC protected
            if (HomeServerChanged())
                XenAPI.VM.set_affinity(Session, VM.opaque_ref, HomeServer != null ? HomeServer.opaque_ref : Helper.NullOpaqueRef);

            if (Template.memory_dynamic_min != MemoryDynamicMin || Template.memory_dynamic_max != MemoryDynamicMax || Template.memory_static_max != MemoryStaticMax)
                XenAPI.VM.set_memory_limits(Session, VM.opaque_ref, Template.memory_static_min, MemoryStaticMax, MemoryDynamicMin, MemoryDynamicMax);
        }

        private bool HomeServerChanged()
        {
            if (HomeServer == null)
            {
                return Template.affinity.opaque_ref != Helper.NullOpaqueRef;
            }
            return HomeServer.opaque_ref != Template.affinity.opaque_ref;
        }

        private void SetVMBootParams()
        {
            if (Template.IsHVM && (Disks.Count == 0 || InsMethod == InstallMethod.Network)) // CA-46213
            {
                // boot from network
                Dictionary<string, string> hvm_params = VM.HVM_boot_params;
                hvm_params["order"] = GetBootOrderNetworkFirst();
                XenAPI.VM.set_HVM_boot_params(Session, VM.opaque_ref, hvm_params);
            }
            else if (IsEli() && InsMethod == InstallMethod.Network)
            {
                Dictionary<string, string> other_config = VM.other_config;
                string normal_url = IsRhel() ? NormalizeRepoUrlForRHEL(Url) : Url;
                other_config["install-repository"] = normal_url;
                XenAPI.VM.set_other_config(Session, VM.opaque_ref, other_config);
            }
            else if (IsEli() && InsMethod == InstallMethod.CD)
            {
                Dictionary<string, string> other_config = VM.other_config;
                other_config["install-repository"] = "cdrom";
                XenAPI.VM.set_other_config(Session, VM.opaque_ref, other_config);
            }

            if (!Template.IsHVM)
            {
                XenAPI.VM.set_PV_args(Session, VM.opaque_ref, PvArgs);
            }
        }

        private bool IsEli()
        {
            return !Template.IsHVM && Template.PV_bootloader == "eliloader";
        }

        private bool IsRhel()
        {
            string distro = VM.InstallDistro;
            return distro == "rhel41" || distro == "rhel44" || distro == "rhlike";
        }

        private string NormalizeRepoUrlForRHEL(string url)
        {
            Uri uri = new Uri(url);
            return uri.Scheme == "nfs" ? string.Format("nfs:{0}:{1}", uri.Host, uri.PathAndQuery) : url;
        }

        private void ProvisionVM()
        {
            Description = Messages.PROVISIONING_VM;
            RewriteProvisionXML();
            RelatedTask = XenAPI.VM.async_provision(Session, VM.opaque_ref);

            PollToCompletion(10, 60);
        }

        private void RewriteProvisionXML()
        {
            XmlNode xml = VM.ProvisionXml;

            if (xml == null)
                return;

            // set the new vm's provision xml: remove "disks" entry, as we are going to explicitly create all the disks
            Dictionary<string, string> other_config = VM.other_config;
            other_config.Remove("disks");
            XenAPI.VM.set_other_config(Session, VM.opaque_ref, other_config);
        }

        private void AddCdDrive()
        {
            if (Helpers.CustomWithNoDVD(Template))
                return; // we have skipped the install media page because we are a cutom template with no cd drive - the user doesnt want a cd drive

            Description = Messages.CREATE_CD_DRIVE;
            VBD cd_drive = null;
            foreach (VBD vbd in Connection.ResolveAll(VM.VBDs))
            {
                if (vbd.type != vbd_type.CD)
                    continue;

                if ("0123".IndexOf(vbd.userdevice) < 0)  // userdevice is not 0, 1, 2 or 3: these are the valid positions for CD drives
                    continue;

                cd_drive = vbd;
                break;
            }

            if (cd_drive == null)
            {
                cd_drive = CreateCdDrive();
            }

            if (!cd_drive.empty)
            {
                RelatedTask = VBD.async_eject(Session, cd_drive.opaque_ref);
                PollToCompletion(65, 67);
            }

            if (InsMethod == InstallMethod.CD && Cd != null) // obviously dont insert the empty cd
            {
                RelatedTask = VBD.async_insert(Session, cd_drive.opaque_ref, Cd.opaque_ref);
                PollToCompletion(67, 70);
            }
        }

        private VBD CreateCdDrive()
        {
            List<string> devices = AllowedVBDs;
            if (devices.Count == 0)
                throw new Exception(Messages.NO_MORE_USERDEVICES);
            VBD vbd = new VBD();
            vbd.bootable = InsMethod == InstallMethod.CD;
            vbd.empty = true;
            vbd.unpluggable = true;
            vbd.mode = vbd_mode.RO;
            vbd.type = vbd_type.CD;
            vbd.userdevice = devices.Contains("3") ? "3" : devices[0];
            vbd.device = "";
            vbd.VM = new XenRef<VM>(VM.opaque_ref);
            vbd.VDI = null;
            RelatedTask = VBD.async_create(Session, vbd);
            PollToCompletion(60, 65);

            return Connection.WaitForCache(new XenRef<VBD>(Result));
        }


        private void AddDisks()
        {
            Description = Messages.CREATING_DISKS;
            List<VBD> vbds = Connection.ResolveAll(VM.VBDs);

            bool firstDisk = true;
            string suspendSr = null;

            double progress = 70;
            double step = 20.0 / (double)Disks.Count;
            foreach (DiskDescription disk in Disks)
            {
                VBD vbd = GetDiskVBD(disk, vbds);
                VDI vdi = null;
                if (vbd != null)
                {
                    vdi = Connection.Resolve<VDI>(vbd.VDI);
                }
                if (!DiskOk(disk, vbd))
                {
                    if (vbd != null)
                        vdi = MoveDisk(disk, vbd, progress, step);
                    else
                        vdi = CreateDisk(disk, progress, step);
                }

                if (vdi == null)
                    continue;

                if (vdi.name_description != disk.Disk.name_description)
                    VDI.set_name_description(Session, vdi.opaque_ref, disk.Disk.name_description);
                if (vdi.name_label != disk.Disk.name_label)
                    VDI.set_name_label(Session, vdi.opaque_ref, disk.Disk.name_label);

                if (firstDisk)
                {
                    //use the first disk to set the VM.suspend_SR
                    SR vdiSR = Connection.Resolve(vdi.SR);
                    this.firstSR = vdiSR;
                    if(vdiSR != null && !vdiSR.HBALunPerVDI)
                        suspendSr = vdi.SR;
                    firstDisk = false;
                }

                progress += step;
            }

			VM.set_suspend_SR(Session, VM.opaque_ref, suspendSr);
        }

        private VBD GetDiskVBD(DiskDescription disk, List<VBD> vbds)
        {
            foreach (VBD vbd in vbds)
            {
                if (disk.Device.userdevice == vbd.userdevice)
                    return vbd;
            }
            return null;
        }

        private bool DiskOk(DiskDescription disk, VBD vbd)
        {
            if (vbd == null)
                return false;

            VDI vdi = Connection.Resolve(vbd.VDI);

            return vdi != null && disk.Disk.SR.opaque_ref == vdi.SR.opaque_ref;
        }

        private VDI MoveDisk(DiskDescription disk, VBD vbd, double progress, double step)
        {
            string old_vdi_ref = vbd.VDI.opaque_ref;

            RelatedTask = XenAPI.VDI.async_copy(Session, vbd.VDI.opaque_ref, disk.Disk.SR.opaque_ref);
            PollToCompletion(progress, progress + 0.25 * step);
            AddVMHint(Connection.WaitForCache(new XenRef<VDI>(Result)));


            VDI new_vdi = Connection.Resolve(new XenRef<VDI>(Result));

            RelatedTask = XenAPI.VBD.async_destroy(Session, vbd.opaque_ref);
            PollToCompletion(progress + 0.25 * step, progress + 0.5 * step);

            RelatedTask = XenAPI.VDI.async_destroy(Session, old_vdi_ref);
            PollToCompletion(progress + 0.5 * step, progress + 0.75 * step);

            CreateVbd(disk, new_vdi, progress + 0.75 * step, progress + step, IsDeviceAtPositionZero(disk));
            return new_vdi;
        }

        /// <summary>
        /// Helper: Check if the disk is at the zeroth position in the VBD list
        /// </summary>
        /// <param name="disk"></param>
        /// <returns></returns>
        private bool IsDeviceAtPositionZero(DiskDescription disk)
        {
            return disk.Device.userdevice == "0";
        }

        /// <summary>
        /// Create a VDI/disk. 
        /// If disk type is existing use the VDI in disk description 
        /// Otherwise create a new disk (provision it from the SR)
        /// </summary>
        /// <param name="disk"></param>
        /// <param name="progress"></param>
        /// <param name="step"></param>
        /// <returns></returns>
        private VDI CreateDisk(DiskDescription disk, double progress, double step)
        {
            VDI vdi;
            bool bootable = false;
            if(disk.Type == DiskDescription.DiskType.Existing)
                vdi = disk.Disk;
            else
            {
                vdi = CreateVdi(disk, progress, progress + 0.75 * step);
                bootable = IsDeviceAtPositionZero(disk) && InsMethod != InstallMethod.CD;
            }

            AddVMHint(vdi);
            CreateVbd(disk, vdi, progress + 0.75 * step, progress + step, bootable);
            return vdi;
        }

        private void AddVMHint(VDI vdi)
        {
            Dictionary<string, string> sm_config = VDI.get_sm_config(Session, vdi.opaque_ref);
            sm_config["vmhint"] = VM.opaque_ref;
            VDI.set_sm_config(Session, vdi.opaque_ref, sm_config);
        }

        private VDI CreateVdi(DiskDescription disk, double progress1, double progress2)
        {
            VDI vdi = new VDI();
            vdi.name_label = disk.Disk.name_label;
            vdi.name_description = disk.Disk.name_description;
            vdi.read_only = false;
            vdi.sharable = false;
            vdi.SR = disk.Disk.SR;
            vdi.type = disk.Disk.type;
            vdi.virtual_size = disk.Disk.virtual_size;
            vdi.sm_config = disk.Disk.sm_config;

            RelatedTask = XenAPI.VDI.async_create(Session, vdi);
            PollToCompletion(progress1, progress2);
            return Connection.WaitForCache(new XenRef<VDI>(Result));
        }

        /// <summary>
        /// Create a VBD
        /// 
        /// ** vbd.bootable **
        /// 1. Windows ignores bootable flag
        /// 2. Eliloader changes the device "0" to bootable when booting linux
        /// </summary>
        /// <param name="disk"></param>
        /// <param name="vdi"></param>
        /// <param name="progress1"></param>
        /// <param name="progress2"></param>
        /// <param name="bootable">Set VBD.bootable to this value - see comments above</param>
        private void CreateVbd(DiskDescription disk, VDI vdi, double progress1, double progress2, bool bootable)
        {
            List<string> devices = AllowedVBDs;
            if (devices.Count == 0)
                throw new Exception(Messages.NO_MORE_USERDEVICES);
            VBD vbd = new VBD();
            vbd.IsOwner = true;
            vbd.bootable = bootable;
            vbd.empty = false;
            vbd.unpluggable = true;
            vbd.mode = vbd_mode.RW;
            vbd.type = vbd_type.Disk;
            vbd.userdevice = devices.Contains(disk.Device.userdevice) ? disk.Device.userdevice : devices[0];
            vbd.device = "";
            vbd.VM = new XenRef<VM>(VM.opaque_ref);
            vbd.VDI = new XenRef<VDI>(vdi.opaque_ref);
            RelatedTask = VBD.async_create(Session, vbd);
            PollToCompletion(progress1, progress2);
            Connection.WaitForCache(new XenRef<VBD>(Result));
        }

        private void AddNetworks()
        {
            // first of all we need to clear any vifs that we have cloned from the template 
            double progress = 90;
            VIF vif;
            List<VIF> existingTemplateVifs = Connection.ResolveAll(VM.VIFs);
            double step = 5.0 / (double)existingTemplateVifs.Count;
            for (int i = 0; i < existingTemplateVifs.Count; i++)
            {
                vif = existingTemplateVifs[i];
                RelatedTask = XenAPI.VIF.async_destroy(Session, vif.opaque_ref);

                PollToCompletion(progress, progress + step);
                progress += step;
            }

            // then we add the ones the user has specified
            step = 5.0 / (double)Vifs.Count;
            for (int i = 0; i < Vifs.Count; i++)
            {
                vif = Vifs[i];
                List<string> devices = AllowedVIFs;
                VIF new_vif = new VIF();

                if (devices.Count < 1)
                {
                    // If we have assigned more VIFs than we have space for then don't try to create them
                    log.Warn("Tried to create more VIFs than the server allows. Ignoring remaining vifs");
                    return;
                }
                new_vif.device = devices.Contains(vif.device) ? vif.device : devices[0];
                new_vif.MAC = vif.MAC;
                new_vif.network = vif.network;
                new_vif.VM = new XenRef<VM>(VM.opaque_ref);
                new_vif.qos_algorithm_type = vif.qos_algorithm_type;
                new_vif.qos_algorithm_params = vif.qos_algorithm_params;
                RelatedTask = XenAPI.VIF.async_create(Session, new_vif);

                PollToCompletion(progress, progress + step);
                progress += step;

                Connection.WaitForCache(new XenRef<VIF>(Result));
            }
        }

        private string HiddenVmName
        {
            get
            {
                return string.Format("{0}{1}", Helpers.GuiTempObjectPrefix, NameLabel);
            }
        }

        private List<string> AllowedVBDs
        {
            get
            {
                return new List<String>(XenAPI.VM.get_allowed_VBD_devices(Session, VM.opaque_ref));
            }
        }

        private List<string> AllowedVIFs
        {
            get
            {
                return new List<String>(XenAPI.VM.get_allowed_VIF_devices(Session, VM.opaque_ref));
            }
        }

        protected override void CleanOnError()
        {
            if (VM != null && !PointOfNoReturn && Connection.IsConnected)
            {
                try
                {
                    VMDestroyAction.DestroyVM(Session, VM, true);
                }
                catch (Exception e)
                {
                    // if the clean up has failed for whatever reason we just log it and give up.
                    log.Error(e);
                }
            }
        }

        private string GetBootOrderNetworkFirst()
        {
            // add "n" at the beginning of the order string
            if (VM.HVM_boot_params.ContainsKey("order"))
            {
                string order = VM.HVM_boot_params["order"].ToLower();
                int i = order.IndexOf("n");
                switch (i)
                {
                    case -1: return order.Insert(0, "n");
                    case 0: return order;
                    default: return order.Remove(i, 1).Insert(0, "n");
                }
            }
            else
            {
                return "ncd";
            }
        }
    }
    public class DiskDescription
    {
        public VDI Disk;
        public VBD Device;
        public DiskType Type;
        public enum DiskType { New, Existing }

        public DiskDescription(){}

        public DiskDescription(VDI disk, VBD device)
        {
            Disk = disk;
            Device = device;
            Type = DiskType.New;
        }
    }
}