xenadmin/XenOvfApi/Validation.cs
Konstantina Chremmou d7b519a53c Updated copyright notice on files.
Signed-off-by: Konstantina Chremmou <Konstantina.Chremmou@cloud.com>
2023-01-30 16:24:16 +00:00

433 lines
17 KiB
C#

/* Copyright (c) Cloud Software Group, Inc.
*
* 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.Linq;
using System.Xml;
using XenOvf.Definitions;
using XenOvf.Utilities;
namespace XenOvf
{
public partial class OVF
{
//TODO: does it need to be configurabe by XenAdmin?
private const int DEFAULT_VALIDATION_FLAGS = 63;
/// <summary>
/// Binary flags describing things to validate
/// </summary>
[Flags]
private enum ValidationFlags
{
/// <summary>
/// No validation
/// </summary>
None = 0,
/// <summary>
/// Check the OVF Version (if present)
/// </summary>
Version = 1,
/// <summary>
/// Ensure the files listed in References.Files are present and accessible
/// </summary>
Files = 2,
/// <summary>
/// Ensure a CPU RASD is present
/// </summary>
Cpu = 4,
/// <summary>
/// Ensure a Memory RASD is present
/// </summary>
Memory = 8,
/// <summary>
/// Validate Network RASD is connected to a NETWORK
/// </summary>
Networks = 16,
/// <summary>
/// Ensure import can handle required RASDs and Sections
/// </summary>
Capability = 32,
/// <summary>
/// Validate the OVF XML against the Schema
/// </summary>
Schema = 64
}
private static readonly string[] KnownFileExtensions =
{
".vhd", ".pvp", ".vmdk", ".mf", ".cert", ".xva", ".ovf", ".wim", ".vdi", ".sdi", ".iso", ".gz"
};
private static readonly string[] KnownVirtualSystemTypes =
{
"xen-3.0-unknown",
"xen-3.0-x32",
"xen-3.0-x86",
"xen-3.0-x64",
"hvm-3.0-unknown",
"hvm-3.0-x32",
"hvm-3.0-x86",
"hvm-3.0-x64",
"hvm-3.0-hvm",
"301",
"vmx-4",
"vmx-04",
"vmx-6",
"vmx-06",
"vmx-7",
"vmx-07",
"DMTF:xen:pv",
"DMTF:xen:hvm",
"virtualbox-2.2"
};
private static readonly string[] KnownVersions =
{
"0.9", "1.0", "1.0.0", "1.0.0a", "1.0.0.b", "1.0.0c", "1.0.0d", "1.0.1", "1.0.0e"
};
public static bool Validate(Package package, out List<string> warnings)
{
warnings = new List<string>();
var ovfEnv = package.OvfEnvelope;
if (ovfEnv == null)
{
warnings.Add(Messages.VALIDATION_INVALID_OVF);
return false;
}
log.InfoFormat("Started validation of package {0}", package.Name);
var validationFlags = (ValidationFlags)DEFAULT_VALIDATION_FLAGS;
if (validationFlags.HasFlag(ValidationFlags.Version))
ValidateVersion(ovfEnv, ref warnings);
if (validationFlags.HasFlag(ValidationFlags.Schema))
ValidateSchema(package.DescriptorXml, ref warnings);
var files = package.OvfEnvelope?.References?.File ?? new File_Type[0];
if (validationFlags.HasFlag(ValidationFlags.Files))
{
foreach (File_Type file in files)
{
string ext = Path.GetExtension(file.href).ToLower();
if (ext == Package.MANIFEST_EXT || ext == Package.CERTIFICATE_EXT)
continue;
if (!package.HasFile(file.href))
{
warnings.Add(string.Format(Messages.VALIDATION_FILE_NOT_FOUND, file.href));
log.Error($"Failed to find file {file.href} listed in the reference section.");
return false;
}
if (!KnownFileExtensions.Contains(ext))
warnings.Add(string.Format(Messages.VALIDATION_FILE_UNSUPPORTED_EXTENSION, file.href));
}
}
VirtualDiskDesc_Type[] disks = null;
NetworkSection_TypeNetwork[] networks = null;
foreach (var section in ovfEnv.Sections)
{
if (section is DiskSection_Type diskSection)
disks = diskSection.Disk;
else if (section is NetworkSection_Type netSection)
networks = netSection.Network;
}
var systems = new Content_Type[0];
switch (ovfEnv.Item)
{
case VirtualSystemCollection_Type vsCol:
systems = vsCol.Content;
break;
case VirtualSystem_Type _:
systems = new [] { ovfEnv.Item };
break;
default:
log.Error($"OVF envelope item type {ovfEnv.Item.GetType()} is not recognized.");
warnings.Add(string.Format(Messages.VALIDATION_INVALID_TYPE, ovfEnv.Item.GetType()));
break;
}
var linkedFiles = new List<File_Type>();
foreach (var system in systems)
{
foreach (var section in system.Items)
{
if (!(section is VirtualHardwareSection_Type vhs))
continue;
var hardwareType = vhs.System?.VirtualSystemType?.Value;
if (hardwareType != null && !KnownVirtualSystemTypes.Contains(hardwareType))
{
log.Warn($"Found unexpected virtual hardware type {hardwareType}.");
warnings.Add(string.Format(Messages.VALIDATION_UNKNOWN_HARDWARE_TYPE, hardwareType));
}
RASD_Type[] rasds = vhs.Item;
foreach (RASD_Type rasd in rasds)
{
if ((rasd.ResourceType.Value == 17 || rasd.ResourceType.Value == 19 ||
rasd.ResourceType.Value == 20 || rasd.ResourceType.Value == 21) &&
validationFlags.HasFlag(ValidationFlags.Files))
ValidateDisks(rasd, files, disks, ref linkedFiles, ref warnings);
if (rasd.ResourceType.Value == 3 && validationFlags.HasFlag(ValidationFlags.Cpu))
ValidateCpu(rasd, ref warnings);
if (rasd.ResourceType.Value == 4 && validationFlags.HasFlag(ValidationFlags.Memory))
ValidateMemory(rasd, ref warnings);
if (rasd.ResourceType.Value == 10 && validationFlags.HasFlag(ValidationFlags.Networks))
ValidateNetworks(rasd, networks, ref warnings);
if (validationFlags.HasFlag(ValidationFlags.Capability))
ValidateCapability(rasd, ref warnings);
}
}
}
foreach (File_Type file in files)
{
if (!linkedFiles.Contains(file))
{
log.WarnFormat("Disk linkage (file to RASD) does not exist for {0}", file.href);
warnings.Add(string.Format(Messages.VALIDATION_FILE_INVALID_LINKAGE, file.href));
}
}
log.InfoFormat("Finished validation of package {0}", package.Name);
return true;
}
private static void ValidateVersion(EnvelopeType ovfEnv, ref List<string> warnings)
{
log.Info("Validating OVF version");
if (ovfEnv.version == null && ovfEnv.AnyAttr != null && ovfEnv.AnyAttr.Length > 0)
{
foreach (XmlAttribute attr in ovfEnv.AnyAttr)
{
if (attr.Name.ToLower().Contains("version"))
{
ovfEnv.version = attr.Value;
break;
}
}
}
if (ovfEnv.version == null)
{
log.Warn("OVF version not set, assuming 1.0.0");
warnings.Add(string.Format(Messages.VALIDATION_VERSION_UNSET, ovfEnv.version));
ovfEnv.version = "1.0.0";
}
else if (!KnownVersions.Contains(ovfEnv.version))
{
warnings.Add(string.Format(Messages.VALIDATION_VERSION_INVALID, ovfEnv.version));
log.WarnFormat($"OVF version {ovfEnv.version} is not supported.");
}
}
private static void ValidateDisks(RASD_Type rasd, File_Type[] files, VirtualDiskDesc_Type[] disks,
ref List<File_Type> linkedFiles, ref List<string> warnings)
{
log.Info("Validating disks");
VirtualDiskDesc_Type disk;
if (rasd.HostResource != null && rasd.HostResource.Length > 0)
disk = disks.FirstOrDefault(d => rasd.HostResource[0].Value.Contains(d.diskId));
else
disk = disks.FirstOrDefault(d => rasd.InstanceID.Value == d.diskId);
File_Type file = null;
if (disk != null)
file = files.FirstOrDefault(f => f.id == disk.fileRef);
if (file != null && !linkedFiles.Contains(file))
linkedFiles.Add(file);
}
private static void ValidateCpu(RASD_Type rasd, ref List<string> warnings)
{
log.Info("Validating CPU");
if (rasd.VirtualQuantity == null || rasd.VirtualQuantity.Value <= 0)
{
log.Warn("CPU invalid Virtual Quantity");
warnings.Add(Messages.VALIDATION_CPU_INVALID_QUANTITY);
}
else if (rasd.Limit != null && rasd.VirtualQuantity.Value > rasd.Limit.Value)
{
log.WarnFormat("Processor quantity {0} exceeds the limit of {1}.", rasd.VirtualQuantity.Value, rasd.Limit.Value);
warnings.Add(string.Format(Messages.VALIDATION_CPU_EXCEEDS_LIMIT, rasd.VirtualQuantity.Value, rasd.Limit.Value));
}
else if (rasd.InstanceID == null || rasd.InstanceID.Value.Length <= 0)
{
log.Info("CPU has an invalid InstanceID, creating a new one.");
warnings.Add(Messages.VALIDATION_INVALID_INSTANCEID);//ovf is valid
rasd.InstanceID = new cimString(Guid.NewGuid().ToString());
}
}
private static void ValidateMemory(RASD_Type rasd, ref List<string> warnings)
{
log.Info("Validating Memory");
if (rasd.VirtualQuantity == null || rasd.VirtualQuantity.Value <= 0)
{
log.Warn("Memory invalid Virtual Quantity");
warnings.Add(Messages.VALIDATION_INVALID_MEMORY_QUANTITY);
}
else if (rasd.AllocationUnits == null || rasd.AllocationUnits.Value.Length <= 0)
{
log.Warn("Memory AllocationUnits not valid");
warnings.Add(Messages.VALIDATION_INVALID_MEMORY_ALLOCATIONUNITS);
}
else if (rasd.InstanceID == null || rasd.InstanceID.Value.Length <= 0)
{
log.Info("Memory has an invalid InstanceID, creating a new one.");
warnings.Add(Messages.VALIDATION_INVALID_INSTANCEID);
rasd.InstanceID = new cimString(Guid.NewGuid().ToString());//ovf is valid
}
}
private static void ValidateNetworks(RASD_Type rasd, NetworkSection_TypeNetwork[] networks, ref List<string> warnings)
{
log.Info("Validating Networks");
if (rasd.InstanceID == null || rasd.InstanceID.Value.Length <= 0)
{
log.Info("Network has an invalid InstanceID, creating a new one.");
warnings.Add(Messages.VALIDATION_INVALID_INSTANCEID);
rasd.InstanceID = new cimString(Guid.NewGuid().ToString());
return;
}
bool linkage = false;
foreach (NetworkSection_TypeNetwork net in networks)
{
// TODO: this may only work for Citrix created VIFs; for others we may need to use a different key to validate linkage.
if (rasd.Connection != null && rasd.Connection.Length > 0 && net.name == rasd.Connection[0].Value ||
net.Description.msgid == rasd.InstanceID.Value)
{
linkage = true;
}
}
if (!linkage)
{
log.Error(Messages.VALIDATION_NETWORK_NO_DEVICE);
warnings.Add(Messages.VALIDATION_NETWORK_NO_DEVICE);
}
}
private static void ValidateCapability(RASD_Type rasd, ref List<string> warnings)
{
log.Info("Validating Capabilities");
switch (rasd.ResourceType.Value)
{
// CIM SCHEMA 2.19.0
case 3: // Processor
case 4: // Memory
case 5: // IDE Controller
case 6: // Parallel SCSI HBA
case 7: // FC HBA
case 8: // iSCSI HBA
case 9: // IB HCA
case 10: // Ethernet Adapter
case 15: // CD Drive
case 16: // DVD Drive
case 17: // Disk Drive
case 19: // Storage Extent
case 20: // Other storage Device
case 21: // Serial Port // Microsoft uses this for Hard Disk Image also, based on an OLDER schema
break;
case 1: // Other
case 2: // Computer System
case 11: // Other Network Adapter
case 12: // I/O Slot
case 13: // I/O Device
case 14: // Floppy Drive
case 18: // Tape Drive
case 22: // Parallel Port
case 23: // USB Controller
case 24: // Graphics Controller
case 25: // IEEE 1394 Controller
case 26: // Partitionable Unit
case 27: // Base Partitionable Unit
case 28: // Power
case 29: // Cooling Capacity
case 30: // Ethernet Switch Port
case 31: // Logical Disk
case 32: // Storage Volume
case 33: // Ethernet Connection
default:
if (rasd.required)
{
var message = string.Format(Messages.VALIDATION_REQUIRED_ELEMENT_NOT_RECOGNIZED, rasd.ResourceType.Value, rasd.ElementName.Value);
log.Error(message);
warnings.Add(message);
}
break;
}
}
private static void ValidateSchema(string ovfContent, ref List<string> warnings)
{
log.Info("Validating OVF XML schema");
try
{
Tools.ValidateXmlToSchema(ovfContent);
}
catch (Exception ex)
{
log.Warn("OVF descriptor does not comply with the OVF XML schema.", ex);
warnings.Add(Messages.VALIDATION_SCHEMA_FAILED);
}
}
}
}