/* 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.Xml; using XenOvf.Definitions; using XenOvf.Utilities; namespace XenOvf { /// /// /// public partial class OVF { /// /// ValidationFlags are binary flags for the individual selection of what to validate /// None = No validation /// Version = Check the OVF Version (if present) /// Files = Ensure the files listed in References.Files are present and accessible. /// Cpu = Ensure a CPU RASD is present /// Memory = Ensure a Memory RASD is present /// Networks = Validate Network RASD is connected to a NETWORK /// Capability = Ensure import can handle required RASDs and Sections. /// Schema = Validate the OVF XML against the Schema. /// [Flags] public enum ValidationFlags { /// /// /// None = 0, /// /// /// Version = 1, /// /// /// Files = 2, /// /// /// Cpu = 4, /// /// /// Memory = 8, /// /// /// Networks = 16, /// /// /// Capability = 32, /// /// /// Schema = 64 }; #region VALIDATION ROUTINES /// /// Statically validate the OVF, perform check and values based upon a static Envelope. /// A more through check could be done IF destination resources where available but that is out of scope for this method. /// /// OVF Filename with full Path to where vhd/ovf files currently exist /// true=valid; false=not-valid; true can also have warnings in ValidationErrorMessage public static bool Validate(string ovffilename, out List validationErrorMessages) { validationErrorMessages = new List(); var validationFlags = ValidationFlags.None; string[] lvls = Properties.Settings.Default.RequiredValidations.Split(new char[] { ',' }); foreach (string lvl in lvls) validationFlags = validationFlags | (ValidationFlags)Enum.Parse(typeof(ValidationFlags), lvl, true); bool isValid = true; string StartPath = Directory.GetCurrentDirectory(); if (!File.Exists(ovffilename)) { throw new FileNotFoundException(ovffilename); } string ovfpath = Path.GetDirectoryName(ovffilename); if (!Directory.Exists(ovfpath)) { throw new FileNotFoundException(string.Format(Messages.FILE_MISSING, ovfpath)); } Directory.SetCurrentDirectory(ovfpath); EnvelopeType ovfEnv = Tools.LoadOvfXml(ovffilename); File_Type[] files = null; VirtualDiskDesc_Type[] disks = null; if (ovfEnv.References != null) { files = ovfEnv.References.File; } NetworkSection_TypeNetwork[] networks = null; Content_Type[] systems = null; RASD_Type[] rasds = null; #region : REQUIRED : CHECK IF VHD HARD DISK IMAGES // Cannot turn this OFF. if (files != null) { isValid = ValidateVHD(files); } #endregion #region : REQUIRED : CHECK VERSION isValid = ValidateVersion(ovfEnv, validationFlags, ref validationErrorMessages); #endregion #region : REQUIRED : VALIDATE AGAINST SCHEMA isValid = ValidateSchema(ovffilename, validationFlags, ref validationErrorMessages); #endregion #region : REQUIRED : GET OUTER STRUCTURE ELEMENTS foreach (var section in ovfEnv.Sections) { if (section is DiskSection_Type) { disks = ((DiskSection_Type)section).Disk; } else if (section is NetworkSection_Type) { networks = ((NetworkSection_Type)section).Network; } } if (ovfEnv.Item is VirtualSystemCollection_Type) { systems = ((VirtualSystemCollection_Type)ovfEnv.Item).Content; } else if (ovfEnv.Item is VirtualSystem_Type) { systems = new [] { ovfEnv.Item }; } else { isValid = false; var message = string.Format(Messages.VALIDATION_INVALID_TYPE, ovfEnv.Item.GetType().ToString()); log.Error(message); validationErrorMessages.Add(message); } #endregion #region : REQUIRED : Validate : Files, Cpu, Memory, Networks, Capabilities if (isValid) { foreach (VirtualSystem_Type system in systems) { isValid = ValidateVHS(system, ref validationErrorMessages); foreach (var section in system.Items) { if (section is VirtualHardwareSection_Type) { VirtualHardwareSection_Type vhs = (VirtualHardwareSection_Type)section; rasds = vhs.Item; if ((!ValidateFiles(ovfpath, files, disks, rasds, validationFlags, ref validationErrorMessages)) || (!ValidateCpu(rasds, validationFlags, ref validationErrorMessages)) || (!ValidateMemory(rasds, validationFlags, ref validationErrorMessages)) || (!ValidateNetworks(networks, rasds, validationFlags, ref validationErrorMessages)) || (!ValidateCapability(rasds, validationFlags, ref validationErrorMessages)) ) { validationErrorMessages.Add(string.Format(Messages.VALIDATION_FAILURE, ovffilename)); isValid = false; } } } } } #endregion if (!isValid) { bool enforceValidation = Properties.Settings.Default.enforceValidation; log.Error(enforceValidation ? "OVF Failed Validation, return failure" : "OVF Failed Validation, OVERRIDE return success"); if (!enforceValidation) isValid = true; } Directory.SetCurrentDirectory(StartPath); return isValid; } #endregion #region PRIVATE private static bool ValidateVersion(EnvelopeType ovfEnv, ValidationFlags validationFlags, ref List validationErrorMessages) { bool isValid = true; if ((validationFlags & ValidationFlags.Version) != 0) { if (ovfEnv.version == null) { if (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("Version not set, applying 1.0.0"); ovfEnv.version = "1.0.0"; } } if (!Properties.Settings.Default.Versions.Contains(ovfEnv.version)) { isValid = false; var message = string.Format(Messages.VALIDATION_INVALID_VERSION, ovfEnv.version); log.Warn(message); validationErrorMessages.Add(message); } } return isValid; } private static bool ValidateVHD(File_Type[] files) { // MUST BE Performed cannot skip. bool isValid = true; foreach (File_Type file in files) { string ext = Path.GetExtension(file.href).ToLower(); if (ext == Properties.Settings.Default.manifestFileExtension || ext == Properties.Settings.Default.certificateFileExtension) continue; if (File.Exists(file.href)) { if (!Properties.Settings.Default.knownFileExtensions.ToLower().Contains(ext)) { log.WarnFormat(Messages.VALIDATION_INVALID_FILETYPE, file.href); } } else { var message = string.Format(Messages.VALIDATION_FILE_NOTFOUND, file.href); log.Error(message); throw new Exception(message); } } return isValid; } private static bool ValidateFiles(string ovfpath, File_Type[] files, VirtualDiskDesc_Type[] disks, RASD_Type[] rasds, ValidationFlags validationFlags, ref List validationErrorMessages) { bool isValid = true; if ((validationFlags & ValidationFlags.Files) != 0) { if (files != null && files.Length > 0) { foreach (File_Type file in files) { string ext = Path.GetExtension(file.href).ToLower(); if (ext == Properties.Settings.Default.manifestFileExtension || ext == Properties.Settings.Default.certificateFileExtension) continue; string filename = string.Format(@"{0}{1}{2}", string.IsNullOrEmpty(ovfpath) ? "" : ovfpath, string.IsNullOrEmpty(ovfpath) ? "" : @"\", file.href); if (!File.Exists(filename)) { var message = string.Format(Messages.VALIDATION_FILE_NOTFOUND, file.href); validationErrorMessages.Add(message); log.Error(message); throw new Exception(message); } } } else { log.Info("ValidateFiles: no attached files defined, continuing"); return isValid; } if (isValid) { bool validlink = false; foreach (File_Type file in files) { validlink = false; foreach (VirtualDiskDesc_Type disk in disks) { if (file.id == disk.fileRef) { foreach (RASD_Type rasd in rasds) { if (rasd.ResourceType.Value == 17 || rasd.ResourceType.Value == 19 || rasd.ResourceType.Value == 20 || rasd.ResourceType.Value == 21) { if (rasd.HostResource != null && rasd.HostResource.Length > 0) { if (rasd.HostResource[0].Value.Contains(disk.diskId)) { validlink = true; break; } } else if (disk.diskId == rasd.InstanceID.Value) { validlink = true; break; } } } } } if (!validlink) { log.WarnFormat("Disk linkage [File to RASD] does not exist: {0}", file.href); break; } } } } return isValid; } private static bool ValidateCpu(RASD_Type[] rasds, ValidationFlags validationFlags, ref List validationErrorMessages) { bool isValid = true; if ((validationFlags & ValidationFlags.Cpu) != 0) { foreach (RASD_Type rasd in rasds) { if (rasd.ResourceType.Value == 3) { if (rasd.VirtualQuantity == null || rasd.VirtualQuantity.Value <= 0) { var message = string.Format(Messages.VALIDATION_INVALID_CPU_QUANTITY, rasd.VirtualQuantity.Value); log.Error(message); validationErrorMessages.Add(message); break; } if (rasd.Limit != null && rasd.VirtualQuantity.Value > rasd.Limit.Value) { var message = string.Format(Messages.VALIDATION_INVALID_CPU_EXCEEDS_LIMIT, rasd.VirtualQuantity.Value, rasd.Limit.Value); log.Error(message); validationErrorMessages.Add(message); isValid = false; break; } if (rasd.InstanceID == null || rasd.InstanceID.Value.Length <= 0) { log.Info("CPU has an invalid InstanceID, creating new."); validationErrorMessages.Add(Messages.VALIDATION_INVALID_INSTANCEID); rasd.InstanceID = new cimString(Guid.NewGuid().ToString()); break; } } } } return isValid; } private static bool ValidateMemory(RASD_Type[] rasds, ValidationFlags validationFlags, ref List validationErrorMessages) { bool isValid = true; if ((validationFlags & ValidationFlags.Memory) != 0) { foreach (RASD_Type rasd in rasds) { if (rasd.ResourceType.Value == 4) { if (rasd.VirtualQuantity == null || rasd.VirtualQuantity.Value <= 0) { log.Error("Memory invalid Virtual Quantity"); validationErrorMessages.Add(Messages.VALIDATION_INVALID_MEMORY_QUANTITY); isValid = false; break; } if (rasd.AllocationUnits == null || rasd.AllocationUnits.Value.Length <= 0) { log.Error("Memory AllocationUnits not valid"); validationErrorMessages.Add(Messages.VALIDATION_INVALID_MEMORY_ALLOCATIONUNITS); isValid = false; break; } if (rasd.InstanceID == null || rasd.InstanceID.Value.Length <= 0) { log.Info("Memory has an invalid InstanceID, creating new."); validationErrorMessages.Add(Messages.VALIDATION_INVALID_INSTANCEID); rasd.InstanceID = new cimString(Guid.NewGuid().ToString()); break; } } } } return isValid; } private static bool ValidateNetworks(NetworkSection_TypeNetwork[] networks, RASD_Type[] rasds, ValidationFlags validationFlags, ref List validationErrorMessages) { bool isValid = true; if ((validationFlags & ValidationFlags.Networks) != 0) { foreach (RASD_Type rasd in rasds) { if (rasd.ResourceType.Value == 10) { bool linkage = false; if (rasd.InstanceID == null || rasd.InstanceID.Value.Length <= 0) { log.Info("Network has an invalid InstanceID, creating new."); validationErrorMessages.Add(Messages.VALIDATION_INVALID_INSTANCEID); rasd.InstanceID = new cimString(Guid.NewGuid().ToString()); break; } foreach (NetworkSection_TypeNetwork net in networks) { // // this may only work for Citrix Created VOFs // haven't looked at others, 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); validationErrorMessages.Add(Messages.VALIDATION_NETWORK_NO_DEVICE); isValid = false; break; } } } } return isValid; } private static bool ValidateCapability(RASD_Type[] rasds, ValidationFlags validationFlags, ref List validationErrorMessages) { bool isValid = true; if ((validationFlags & ValidationFlags.Capability) != 0) { foreach (RASD_Type rasd in rasds) { 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: // Ehternet 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 { if (rasd.required) { isValid = true; } 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); validationErrorMessages.Add(message); isValid = false; } break; } } if (!isValid) break; } } return isValid; } private static bool ValidateSchema(string ovffilename, ValidationFlags validationFlags, ref List validationErrorMessages) { bool isValid = true; if ((validationFlags & ValidationFlags.Schema) != 0) { try { isValid = Tools.ValidateXmlToSchema(ovffilename); } catch (Exception ex) { isValid = false; validationErrorMessages.Add(ex.Message); } if (!isValid) { isValid = false; log.Error(Messages.VALIDATION_SCHEMA_FAILED); validationErrorMessages.Add(Messages.VALIDATION_SCHEMA_FAILED); } } return isValid; } private static bool ValidateVHS(VirtualSystem_Type vs, ref List validationErrorMessages) { bool isValid = true; VirtualHardwareSection_Type vhs = null; foreach (Section_Type section in vs.Items) { if (section is VirtualHardwareSection_Type) { vhs = (VirtualHardwareSection_Type)section; if (vhs.System != null && vhs.System.VirtualSystemIdentifier != null && vhs.System.VirtualSystemIdentifier.Value != null) { if (!Properties.Settings.Default.knownVirtualSystemTypes.Contains(vhs.System.VirtualSystemType.Value)) { var message = string.Format(Messages.VALIDATION_UNKNOWN_HARDWARE_TYPE, vhs.System.VirtualSystemType.Value); log.Warn(message); validationErrorMessages.Add(message); isValid = false; } } } } return isValid; } #endregion } }