mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-12-29 23:30:10 +01:00
445 lines
20 KiB
C#
445 lines
20 KiB
C#
/* 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.IO;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using DiscUtils;
|
|
using XenAdmin.Core;
|
|
using XenOvf;
|
|
using XenOvf.Definitions;
|
|
using XenOvf.Utilities;
|
|
using XenAPI;
|
|
|
|
|
|
namespace XenOvfTransport
|
|
{
|
|
public class Export : XenOvfTransportBase
|
|
{
|
|
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
|
private static readonly log4net.ILog auditLog = log4net.LogManager.GetLogger("Audit");
|
|
private static readonly log4net.ILog traceLog = log4net.LogManager.GetLogger("Trace");
|
|
|
|
private const long KB = 1024;
|
|
private const long MB = (KB * 1024);
|
|
private const long GB = (MB * 1024);
|
|
private const long MEMMIN = 128 * MB;
|
|
|
|
private List<XenRef<VDI>> _vdiRefs = new List<XenRef<VDI>>();
|
|
|
|
public Export(Uri xenserver, Session session)
|
|
: base(xenserver, session)
|
|
{
|
|
}
|
|
|
|
public EnvelopeType Process(string targetPath, string ovfname, string[] vmUuid)
|
|
{
|
|
List<EnvelopeType> envList = new List<EnvelopeType>();
|
|
|
|
foreach (string vmuuid in vmUuid)
|
|
envList.Add(_export(XenSession, targetPath, ovfname, vmuuid));
|
|
|
|
EnvelopeType ovfEnv = OVF.Merge(envList, ovfname);
|
|
|
|
if (AutoSave)
|
|
{
|
|
string ovffilename = Path.Combine(targetPath, string.Format(@"{0}.ovf", ovfname));
|
|
OVF.SaveAs(ovfEnv, ovffilename);
|
|
}
|
|
OnUpdate(new XenOvfTranportEventArgs(XenOvfTranportEventType.ExportThreadComplete, "Export", Messages.COMPLETED_EXPORT));
|
|
return ovfEnv;
|
|
}
|
|
|
|
public bool AutoSave { get; set; }
|
|
|
|
public bool ShouldVerifyDisks { get; set; }
|
|
|
|
public bool MetaDataOnly { get; set; }
|
|
|
|
private EnvelopeType _export(Session xenSession, string targetPath, string ovfname, string vmUuid)
|
|
{
|
|
EnvelopeType ovfEnv;
|
|
|
|
try
|
|
{
|
|
auditLog.DebugFormat("Export: {0}, {1}", ovfname, targetPath);
|
|
|
|
#region GET VM Reference
|
|
XenRef<VM> vmRef = null;
|
|
|
|
try
|
|
{
|
|
vmRef = VM.get_by_uuid(xenSession, vmUuid);
|
|
}
|
|
catch
|
|
{
|
|
log.WarnFormat("VM not found as uuid: {0}, trying as name-label", vmUuid);
|
|
vmRef = null;
|
|
}
|
|
if (vmRef == null)
|
|
{
|
|
try
|
|
{
|
|
List<XenRef<VM>> vmRefs = VM.get_by_name_label(xenSession, vmUuid);
|
|
vmRef = vmRefs[0];
|
|
traceLog.DebugFormat("{0} VM(s) found by label {1}", vmRefs.Count, vmUuid);
|
|
if (vmRefs.Count > 1)
|
|
log.WarnFormat("Only exporting FIRST VM with name {0}", vmUuid);
|
|
}
|
|
catch
|
|
{
|
|
log.ErrorFormat(Messages.ERROR_VM_NOT_FOUND, vmUuid);
|
|
throw;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
VM vm = VM.get_record(xenSession, vmRef);
|
|
|
|
if (vm.power_state != vm_power_state.Halted && vm.power_state != vm_power_state.Suspended)
|
|
{
|
|
var message = string.Format(Messages.ERROR_VM_NOT_HALTED, vm.Name);
|
|
OnUpdate(new XenOvfTranportEventArgs(XenOvfTranportEventType.ExportProgress, "Export", message));
|
|
log.Info(message);
|
|
throw new Exception(message);
|
|
}
|
|
|
|
#region CREATE ENVELOPE / ADD VIRTUAL SYSTEM
|
|
ovfEnv = OVF.CreateEnvelope(ovfname);
|
|
string vsId = OVF.AddVirtualSystem(ovfEnv, vm.name_label);
|
|
string vhsId = OVF.AddVirtualHardwareSection(ovfEnv, vsId);
|
|
#endregion
|
|
|
|
#region TRY TO ID OS
|
|
XenRef<VM_guest_metrics> vmgmRef = VM.get_guest_metrics(xenSession, vmRef);
|
|
if (!vmgmRef.opaque_ref.ToUpper().Contains("NULL"))
|
|
{
|
|
VM_guest_metrics vmgm = VM_guest_metrics.get_record(xenSession, vmgmRef);
|
|
//VM_metrics vmm = VM_metrics.get_record(xenSession, VM.get_metrics(xenSession, vmRef));
|
|
if (vmgm.os_version != null && vmgm.os_version.Count > 0)
|
|
{
|
|
foreach (string key in vmgm.os_version.Keys)
|
|
{
|
|
if (key.ToLower().Equals("name"))
|
|
{
|
|
ushort osid = ValueMaps.OperatingSystem(vmgm.os_version[key]);
|
|
if (osid == 0xFFFF) { osid = 1; } // change to OTHER since search failed.
|
|
string version = OVF.GetContentMessage("SECTION_OPERATINGSYSTEM_INFO");
|
|
if (vmgm.os_version.ContainsKey("major") &&
|
|
vmgm.os_version.ContainsKey("minor"))
|
|
{
|
|
version = string.Format(OVF.GetContentMessage("SECTION_OPERATINGSYSTEM_VERSION"), vmgm.os_version["major"], vmgm.os_version["minor"]);
|
|
}
|
|
string osname = (vmgm.os_version[key].Split(new [] { '|' }))[0];
|
|
OVF.UpdateOperatingSystemSection(ovfEnv, vsId, osname, version, osid);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region ADD VSSD
|
|
// IS PV'd? for VirtualSystemType identification.
|
|
string typeformat = @"{0}-{1}-{2}";
|
|
string vmtype = string.Format(typeformat, "hvm", "3.0", "unknown");
|
|
if (vm.HVM_boot_policy != null && vm.HVM_boot_policy == Properties.Settings.Default.xenBootOptions)
|
|
{
|
|
if (!string.IsNullOrEmpty(vm.domarch))
|
|
{
|
|
vmtype = string.Format(typeformat, vm.domarch, "3.0", "unknown");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!string.IsNullOrEmpty(vm.domarch))
|
|
{
|
|
vmtype = string.Format(typeformat, "xen", "3.0", vm.domarch);
|
|
}
|
|
else
|
|
{
|
|
vmtype = string.Format(typeformat, "xen", "3.0", "unknown");
|
|
}
|
|
}
|
|
OVF.AddVirtualSystemSettingData(ovfEnv, vsId, vhsId, vm.name_label, OVF.GetContentMessage("VSSD_CAPTION"), vm.name_description, Guid.NewGuid().ToString(), vmtype);
|
|
#endregion
|
|
|
|
#region ADD CPUS
|
|
OVF.SetCPUs(ovfEnv, vsId, (ulong)vm.VCPUs_max);
|
|
#endregion
|
|
|
|
#region ADD MEMORY
|
|
OVF.SetMemory(ovfEnv, vsId, (ulong)(vm.memory_dynamic_max / MB), "MB");
|
|
#endregion
|
|
|
|
#region ADD NETWORKS
|
|
List<XenRef<VIF>> vifs = VM.get_VIFs(xenSession, vmRef);
|
|
foreach (XenRef<VIF> vifref in vifs)
|
|
{
|
|
VIF vif = VIF.get_record(xenSession, vifref);
|
|
XenRef<Network> netRef = vif.network;
|
|
Network net = Network.get_record(xenSession, netRef);
|
|
|
|
// Why is the following call reference using name_label where other references use uuid?
|
|
OVF.AddNetwork(ovfEnv, vsId, net.uuid, net.name_label, net.name_description, vif.MAC);
|
|
}
|
|
#endregion
|
|
|
|
#region SET STARTUP OPTIONS
|
|
OVF.AddStartupSection(ovfEnv, true, vsId, vm.order, vm.start_delay, vm.shutdown_delay);
|
|
#endregion
|
|
|
|
#region GET AND EXPORT DISKS using iSCSI
|
|
List<XenRef<VBD>> vbdlist = VM.get_VBDs(xenSession, vmRef);
|
|
_vdiRefs.Clear();
|
|
|
|
int diskIndex = 0;
|
|
|
|
foreach (XenRef<VBD> vbdref in vbdlist)
|
|
{
|
|
VBD vbd = VBD.get_record(xenSession, vbdref);
|
|
|
|
if (vbd.type == vbd_type.CD)
|
|
{
|
|
string rasdid = OVF.AddCDROM(ovfEnv, vsId, vbd.uuid, OVF.GetContentMessage("RASD_16_CAPTION"), OVF.GetContentMessage("RASD_16_DESCRIPTION"));
|
|
OVF.SetTargetDeviceInRASD(ovfEnv, vsId, rasdid, vbd.userdevice);
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
XenRef<VDI> vdi = VBD.get_VDI(xenSession, vbdref);
|
|
if (vdi != null && !string.IsNullOrEmpty(vdi.opaque_ref) && !(vdi.opaque_ref.ToLower().Contains("null")))
|
|
{
|
|
_vdiRefs.Add(vdi);
|
|
VDI lVdi = VDI.get_record(xenSession, vdi);
|
|
string destinationFilename = Path.Combine(targetPath, string.Format(@"{0}.vhd", lVdi.uuid));
|
|
string diskid = Guid.NewGuid().ToString();
|
|
|
|
string diskName = lVdi.name_label;
|
|
|
|
if (diskName == null)
|
|
diskName = string.Format("{0} {1}", OVF.GetContentMessage("RASD_19_CAPTION"), diskIndex);
|
|
|
|
OVF.AddDisk(ovfEnv, vsId, diskid, Path.GetFileName(destinationFilename), vbd.bootable, diskName, lVdi.name_description, (ulong)lVdi.physical_utilisation, (ulong)lVdi.virtual_size);
|
|
OVF.SetTargetDeviceInRASD(ovfEnv, vsId, diskid, vbd.userdevice);
|
|
|
|
diskIndex++;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
log.InfoFormat("Export: VBD Skipped: {0}: {1}", vbdref, ex.Message);
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
if (!MetaDataOnly)
|
|
{
|
|
_copydisks(ovfEnv, ovfname, targetPath);
|
|
}
|
|
|
|
#region ADD XEN SPECIFICS
|
|
if (vm.HVM_boot_params != null)
|
|
{
|
|
Dictionary<string, string> _params = vm.HVM_boot_params;
|
|
foreach (string key in _params.Keys)
|
|
{
|
|
if (key.ToLower().Equals("order"))
|
|
{
|
|
OVF.AddOtherSystemSettingData(ovfEnv, vsId, "HVM_boot_params", _params[key], OVF.GetContentMessage("OTHER_SYSTEM_SETTING_DESCRIPTION_1"));
|
|
}
|
|
}
|
|
}
|
|
if (!string.IsNullOrEmpty(vm.HVM_boot_policy))
|
|
{
|
|
OVF.AddOtherSystemSettingData(ovfEnv, vsId, "HVM_boot_policy", vm.HVM_boot_policy, OVF.GetContentMessage("OTHER_SYSTEM_SETTING_DESCRIPTION_2"));
|
|
}
|
|
if (vm.HVM_shadow_multiplier != 1.0)
|
|
{
|
|
OVF.AddOtherSystemSettingData(ovfEnv, vsId, "HVM_shadow_multiplier", Convert.ToString(vm.HVM_shadow_multiplier), OVF.GetContentMessage("OTHER_SYSTEM_SETTING_DESCRIPTION_1"));
|
|
}
|
|
if (vm.platform != null)
|
|
{
|
|
Dictionary<string, string> platform = vm.platform;
|
|
StringBuilder sb = new StringBuilder();
|
|
foreach (string key in platform.Keys)
|
|
{
|
|
sb.AppendFormat(@"{0}={1};", key, platform[key]);
|
|
}
|
|
OVF.AddOtherSystemSettingData(ovfEnv, vsId, "platform", sb.ToString(), OVF.GetContentMessage("OTHER_SYSTEM_SETTING_DESCRIPTION_3"));
|
|
}
|
|
if (!string.IsNullOrEmpty(vm.PV_args))
|
|
{
|
|
OVF.AddOtherSystemSettingData(ovfEnv, vsId, "PV_args", vm.PV_args, OVF.GetContentMessage("OTHER_SYSTEM_SETTING_DESCRIPTION_1"));
|
|
}
|
|
if (!string.IsNullOrEmpty(vm.PV_bootloader))
|
|
{
|
|
OVF.AddOtherSystemSettingData(ovfEnv, vsId, "PV_bootloader", vm.PV_bootloader, OVF.GetContentMessage("OTHER_SYSTEM_SETTING_DESCRIPTION_1"));
|
|
}
|
|
if (!string.IsNullOrEmpty(vm.PV_bootloader_args))
|
|
{
|
|
OVF.AddOtherSystemSettingData(ovfEnv, vsId, "PV_bootloader_args", vm.PV_bootloader_args, OVF.GetContentMessage("OTHER_SYSTEM_SETTING_DESCRIPTION_1"));
|
|
}
|
|
if (!string.IsNullOrEmpty(vm.PV_kernel))
|
|
{
|
|
OVF.AddOtherSystemSettingData(ovfEnv, vsId, "PV_kernel", vm.PV_kernel, OVF.GetContentMessage("OTHER_SYSTEM_SETTING_DESCRIPTION_1"));
|
|
}
|
|
if (!string.IsNullOrEmpty(vm.PV_legacy_args))
|
|
{
|
|
OVF.AddOtherSystemSettingData(ovfEnv, vsId, "PV_legacy_args", vm.PV_legacy_args, OVF.GetContentMessage("OTHER_SYSTEM_SETTING_DESCRIPTION_1"));
|
|
}
|
|
if (!string.IsNullOrEmpty(vm.PV_ramdisk))
|
|
{
|
|
OVF.AddOtherSystemSettingData(ovfEnv, vsId, "PV_ramdisk", vm.PV_ramdisk, OVF.GetContentMessage("OTHER_SYSTEM_SETTING_DESCRIPTION_1"));
|
|
}
|
|
|
|
if (vm.VGPUs.Count != 0)
|
|
{
|
|
VGPU vgpu = VGPU.get_record(xenSession, vm.VGPUs[0]);
|
|
|
|
if (vgpu != null)
|
|
{
|
|
var vgpuGroup = GPU_group.get_record(xenSession, vgpu.GPU_group);
|
|
var vgpuType = VGPU_type.get_record(xenSession, vgpu.type);
|
|
|
|
var sb = new StringBuilder();
|
|
sb.AppendFormat("GPU_types={{{0}}};",
|
|
vgpuGroup.GPU_types == null || vgpuGroup.GPU_types.Length < 1
|
|
? ""
|
|
: string.Join(";", vgpuGroup.GPU_types));
|
|
sb.AppendFormat("VGPU_type_vendor_name={0};", vgpuType.vendor_name ?? "");
|
|
sb.AppendFormat("VGPU_type_model_name={0};", vgpuType.model_name ?? "");
|
|
OVF.AddOtherSystemSettingData(ovfEnv, vsId, "vgpu", sb.ToString(), OVF.GetContentMessage("OTHER_SYSTEM_SETTING_DESCRIPTION_4"));
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
OVF.FinalizeEnvelope(ovfEnv);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (ex is OperationCanceledException)
|
|
throw;
|
|
log.Error(Messages.ERROR_EXPORT_FAILED);
|
|
throw new Exception(Messages.ERROR_EXPORT_FAILED, ex);
|
|
}
|
|
return ovfEnv;
|
|
}
|
|
|
|
private void _copydisks(EnvelopeType ovfEnv, string label, string targetPath)
|
|
{
|
|
m_iscsi = new iSCSI
|
|
{
|
|
UpdateHandler = iscsi_UpdateHandler,
|
|
Cancel = Cancel//in case it has already been cancelled
|
|
};
|
|
m_iscsi.ConfigureTvmNetwork(m_networkUuid, m_isTvmIpStatic, m_tvmIpAddress, m_tvmSubnetMask, m_tvmGateway);
|
|
|
|
try
|
|
{
|
|
foreach (XenRef<VDI> vdiuuid in _vdiRefs)
|
|
{
|
|
string uuid = "";
|
|
string destinationFilename = "";
|
|
|
|
try
|
|
{
|
|
uuid = VDI.get_uuid(XenSession, vdiuuid);
|
|
destinationFilename = Path.Combine(targetPath, string.Format(@"{0}.vhd", uuid));
|
|
|
|
if (File.Exists(destinationFilename))
|
|
{
|
|
destinationFilename = Path.Combine(targetPath, string.Format(@"{0}_{1}.vhd", uuid, Thread.CurrentThread.ManagedThreadId));
|
|
OVF.UpdateFilename(ovfEnv, string.Format(@"{0}.vhd", uuid), string.Format(@"{0}_{1}.vhd", uuid, Thread.CurrentThread.ManagedThreadId));
|
|
log.InfoFormat("{0}: VHD Name collision, renamed {1}.vhd to {1}_{2}.vhd",
|
|
label, uuid, Thread.CurrentThread.ManagedThreadId);
|
|
}
|
|
|
|
OnUpdate(new XenOvfTranportEventArgs(XenOvfTranportEventType.MarqueeOn, "Export", string.Format(Messages.FILES_TRANSPORT_SETUP, uuid + ".vhd")));
|
|
|
|
using (Stream source = m_iscsi.Connect(XenSession, uuid, true))
|
|
{
|
|
OnUpdate(new XenOvfTranportEventArgs(XenOvfTranportEventType.MarqueeOff, "Export", ""));
|
|
using (FileStream fs = new FileStream(destinationFilename, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None))
|
|
{
|
|
// Create a geometry to give to DiscUtils.Vhd.Disk.InitializeDynamic() just so it doesn't override capacity
|
|
// when initializing the .
|
|
DiscUtils.Geometry geometry = DiscUtils.Geometry.FromCapacity(source.Length);
|
|
|
|
using (DiscUtils.Vhd.Disk destination = DiscUtils.Vhd.Disk.InitializeDynamic(fs, Ownership.None, source.Length, geometry))
|
|
{
|
|
m_iscsi.Copy(source, destination.Content, Path.GetFileName(destinationFilename), ShouldVerifyDisks);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ShouldVerifyDisks)
|
|
{
|
|
using (var target = new DiscUtils.Vhd.Disk(destinationFilename, FileAccess.Read))
|
|
{
|
|
m_iscsi.Verify(target.Content, destinationFilename);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (ex is OperationCanceledException)
|
|
throw;
|
|
var msg = string.Format(Messages.ISCSI_COPY_ERROR, destinationFilename);
|
|
log.Error(msg);
|
|
OnUpdate(new XenOvfTranportEventArgs(XenOvfTranportEventType.Failure, "Export", msg, ex));
|
|
throw new Exception(msg, ex);
|
|
}
|
|
finally
|
|
{
|
|
OnUpdate(new XenOvfTranportEventArgs(XenOvfTranportEventType.MarqueeOn, "Export", string.Format(Messages.FILES_TRANSPORT_CLEANUP, uuid + ".vhd")));
|
|
m_iscsi.Disconnect(XenSession);
|
|
OnUpdate(new XenOvfTranportEventArgs(XenOvfTranportEventType.MarqueeOff, "Export", ""));
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_vdiRefs.Clear();
|
|
}
|
|
}
|
|
|
|
private void iscsi_UpdateHandler(XenOvfTranportEventArgs e)
|
|
{
|
|
OnUpdate(e);
|
|
}
|
|
}
|
|
}
|