/* 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 System.Text; using System.Text.RegularExpressions; using System.IO; using XenAdmin.Network; using XenAPI; using System.Globalization; using System.Reflection; using System.Xml; using XenCenterLib; namespace XenAdmin.Core { public static class Helpers { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); private const long XLVHD_DEF_ALLOCATION_QUANTUM_DIVISOR = 10000; public const long XLVHD_MIN_ALLOCATION_QUANTUM_DIVISOR = 50000; public const long XLVHD_MAX_ALLOCATION_QUANTUM_DIVISOR = 4000; public const long XLVHD_MIN_ALLOCATION_QUANTUM = 16777216; // 16 MB public const int DEFAULT_NAME_TRIM_LENGTH = 50; public const string GuiTempObjectPrefix = "__gui__"; public const string PRODUCT_BRAND_XCP_NG = "XCP-ng"; public static NumberFormatInfo _nfi = new CultureInfo("en-US", false).NumberFormat; public static readonly Regex SessionRefRegex = new Regex(@"OpaqueRef:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); /// /// Return the given host's product version, or the pool master's product version if /// the host does not have one, or null if none can be found. /// /// May be null. public static string HostProductVersion(Host host) { return FromHostOrMaster(host, h => h.ProductVersion()); } public static string HostProductVersionText(Host host) { return FromHostOrMaster(host, h => h.ProductVersionText()); } public static string HostProductVersionTextShort(Host host) { return FromHostOrMaster(host, h => h.ProductVersionTextShort()); } public static string HostPlatformVersion(Host host) { if (host == null) return null; return host.PlatformVersion(); } private delegate string HostToStr(Host host); private static string FromHostOrMaster(Host host, HostToStr fn) { if (host == null) return null; string output = fn(host); if (output == null) { Host master = GetMaster(host.Connection); return master == null ? null : fn(master); } return output; } /// /// Only log the unrecognised version message once (CA-11201). /// private static bool _unrecognisedVersionWarned = false; /// /// Numbers should have three parts, i.e. be in the form a.b.c, otherwise they won't be parsed. /// /// May be null. /// May be null. /// public static int productVersionCompare(string version1, string version2) { // Assume version numbers are of form 'a.b.c' int a1 = 99, b1 = 99, c1 = 99, a2 = 99, b2 = 99, c2 = 99; string[] tokens = null; if (version1 != null) tokens = version1.Split('.'); if (tokens != null && tokens.Length == 3) { a1 = int.Parse(tokens[0]); b1 = int.Parse(tokens[1]); c1 = int.Parse(tokens[2]); } else { if (!_unrecognisedVersionWarned) { log.DebugFormat("Unrecognised version format {0} - treating as developer version", version1); _unrecognisedVersionWarned = true; } } tokens = null; if (version2 != null) tokens = version2.Split('.'); if (tokens != null && tokens.Length == 3) { a2 = int.Parse(tokens[0]); b2 = int.Parse(tokens[1]); c2 = int.Parse(tokens[2]); } else { if (!_unrecognisedVersionWarned) { log.DebugFormat("Unrecognised version format {0} - treating as developer version", version2); _unrecognisedVersionWarned = true; } } if (a2 > a1) { return -1; } else if (a2 == a1) { if (b2 > b1) { return -1; } else if (b2 == b1) { if (c2 > c1) { return -1; } else if (c1 == c2) { return 0; } } } return 1; } /// /// Gets the pool for the provided connection. Returns null if we have /// a standalone host (or the provided connection is null). /// /// To obtain the pool object for the case of a standalone host /// use GetPoolOfOne. public static Pool GetPool(IXenConnection connection) { if (connection == null) return null; foreach (Pool thePool in connection.Cache.Pools) { if (thePool != null && thePool.IsVisible()) return thePool; } return null; } /// /// Get the unique Pool object corresponding to the given connection. /// Returns the pool object even in the case of a standalone host. May /// return null if the cache is still being populated or the given /// connection is null. /// public static Pool GetPoolOfOne(IXenConnection connection) { if (connection == null) return null; foreach (Pool pool in connection.Cache.Pools) return pool; return null; } /// /// Return the host object representing the master of the given connection, or null if the /// cache is being populated. /// /// May not be null. /// public static Host GetMaster(IXenConnection connection) { Pool pool = GetPoolOfOne(connection); return pool == null ? null : connection.Resolve(pool.master); } /// /// Return the host object representing the master of the given pool. /// (If pool is null, returns null). /// public static Host GetMaster(Pool pool) { return pool == null ? null : pool.Connection.Resolve(pool.master); } public static bool HostIsMaster(Host host) { Pool pool = Helpers.GetPoolOfOne(host.Connection); if (pool == null) //Cache is being populated... what do we do? return false; return host.opaque_ref == pool.master.opaque_ref; } public static bool IsPool(IXenConnection connection) { return (GetPool(connection) != null); } /// May be null, in which case the empty string is returned. public static string GetName(Pool pool) { return pool == null ? "" : pool.Name(); } /// May be null, in which case the empty string is returned. public static string GetName(IXenConnection connection) { return connection == null ? "" : connection.Name; } /// May be null, in which case the empty string is returned. public static string GetName(IXenObject o) { return o == null ? "" : o.Name(); } public static bool IsConnected(IXenConnection connection) { return (connection != null && connection.IsConnected); } public static bool IsConnected(Pool pool) { return (pool != null && IsConnected(pool.Connection)); } public static bool IsConnected(Host host) { return (host != null && IsConnected(host.Connection)); } public static bool HasFullyConnectedSharedStorage(IXenConnection connection) { foreach (SR sr in connection.Cache.SRs) { if (sr.content_type != XenAPI.SR.Content_Type_ISO && sr.shared && sr.CanCreateVmOn()) return true; } return false; } /// May be null, in which case true is returned. public static bool DundeeOrGreater(IXenConnection conn) { return conn == null || DundeeOrGreater(Helpers.GetMaster(conn)); } /// Dundee is ver. 2.0.0 /// May be null, in which case true is returned. public static bool DundeeOrGreater(Host host) { if (host == null) return true; string platform_version = HostPlatformVersion(host); return platform_version != null && productVersionCompare(platform_version, "2.0.0") >= 0; } public static bool DundeePlusOrGreater(IXenConnection conn) { return conn == null || conn.Session == null || conn.Session.APIVersion >= API_Version.API_2_6; } /// May be null, in which case true is returned. public static bool ElyOrGreater(IXenConnection conn) { return conn == null || ElyOrGreater(Helpers.GetMaster(conn)); } /// Ely is ver. 2.1.1 /// May be null, in which case true is returned. public static bool ElyOrGreater(Host host) { if (host == null) return true; string platform_version = HostPlatformVersion(host); return platform_version != null && productVersionCompare(platform_version, "2.1.1") >= 0; } public static bool HavanaOrGreater(IXenConnection conn) { return conn == null || HavanaOrGreater(Helpers.GetMaster(conn)); } /// As Havana platform version is same with Ely and Honolulu, so use product version here /// May be null, in which case true is returned. public static bool HavanaOrGreater(Host host) { if (host == null) return true; string product_version = HostProductVersion(host); return product_version != null && ElyOrGreater(host) && !FalconOrGreater(host) && productVersionCompare(product_version, "[BRANDING_VERSION_7_1_2]") >= 0; } /// May be null, in which case true is returned. public static bool FalconOrGreater(IXenConnection conn) { return conn == null || FalconOrGreater(Helpers.GetMaster(conn)); } /// Falcon is ver. 2.3.0 /// May be null, in which case true is returned. public static bool FalconOrGreater(Host host) { if (host == null) return true; string platform_version = HostPlatformVersion(host); return platform_version != null && productVersionCompare(platform_version, "2.2.50") >= 0; } /// May be null, in which case true is returned. public static bool InvernessOrGreater(IXenConnection conn) { return conn == null || InvernessOrGreater(Helpers.GetMaster(conn)); } /// Inverness is ver. 2.4.0 /// May be null, in which case true is returned. public static bool InvernessOrGreater(Host host) { if (host == null) return true; string platform_version = HostPlatformVersion(host); return platform_version != null && productVersionCompare(platform_version, "2.3.50") >= 0; } /// May be null, in which case true is returned. public static bool JuraOrGreater(IXenConnection conn) { return conn == null || JuraOrGreater(Helpers.GetMaster(conn)); } /// Jura is ver. 2.5.0 /// May be null, in which case true is returned. public static bool JuraOrGreater(Host host) { if (host == null) return true; string platform_version = HostPlatformVersion(host); return platform_version != null && productVersionCompare(platform_version, "2.4.50") >= 0; } /// May be null, in which case true is returned. public static bool KolkataOrGreater(IXenConnection conn) { return conn == null || KolkataOrGreater(Helpers.GetMaster(conn)); } /// Kolkata platform version is 2.6.0 /// May be null, in which case true is returned. public static bool KolkataOrGreater(Host host) { if (host == null) return true; string platform_version = HostPlatformVersion(host); return platform_version != null && productVersionCompare(platform_version, "2.5.50") >= 0; } /// May be null, in which case true is returned. public static bool LimaOrGreater(IXenConnection conn) { return conn == null || LimaOrGreater(Helpers.GetMaster(conn)); } /// Lima platform version is 2.7.0 /// May be null, in which case true is returned. public static bool LimaOrGreater(Host host) { if (host == null) return true; string platform_version = HostPlatformVersion(host); return platform_version != null && productVersionCompare(platform_version, "2.6.50") >= 0; } /// May be null, in which case true is returned. public static bool NaplesOrGreater(IXenConnection conn) { return conn == null || NaplesOrGreater(Helpers.GetMaster(conn)); } /// Naples is ver. 3.0.0 /// May be null, in which case true is returned. public static bool NaplesOrGreater(Host host) { if (host == null) return true; return NaplesOrGreater(HostPlatformVersion(host)); } public static bool NaplesOrGreater(string platformVersion) { return platformVersion != null && productVersionCompare(platformVersion, "2.9.50") >= 0; } // CP-3435: Disable Check for Updates in Common Criteria Certification project public static bool CommonCriteriaCertificationRelease { get { return false; } } public static bool WlbEnabled(IXenConnection connection) { Pool pool = GetPoolOfOne(connection); return pool != null && pool.wlb_enabled; } public static bool WlbEnabledAndConfigured(IXenConnection conn) { return WlbEnabled(conn)&& WlbConfigured(conn); } public static bool WlbConfigured(IXenConnection conn) { Pool p = GetPoolOfOne(conn); return p != null && !string.IsNullOrEmpty(p.wlb_url); } public static bool CrossPoolMigrationRestrictedWithWlb(IXenConnection conn) { return WlbEnabledAndConfigured(conn) && !DundeeOrGreater(conn); } /// /// Determines whether two lists contain the same elements (but not necessarily in the same order). /// Compares list elements using reference equality. /// /// /// Must not be null. /// Must not be null. /// public static bool ListsContentsEqual(List lst1, List lst2) { if (lst1.Count != lst2.Count) return false; foreach (T item1 in lst1) if (!lst2.Contains(item1)) return false; return true; } /// /// /// /// /// Must not be null. /// Must not be null. /// public static List ListsCommonItems(List lst1, List lst2) { List common = new List(); foreach (T item1 in lst1) if (lst2.Contains(item1) && !common.Contains(item1)) common.Add(item1); foreach (T item2 in lst2) if (lst1.Contains(item2) && !common.Contains(item2)) common.Add(item2); return common; } /// /// Determines if two arrays have the same elements in the same order. /// Elements may be null. /// Uses Object.Equals() when comparing (pairs of non-null) elements. /// /// /// Must not be null. /// Must not be null. /// public static bool ArrayElementsEqual(T[] a, T[] b) { if (a.Length != b.Length) return false; for (int i = 0; i < a.Length; i++) { if (a[i] == null && b[i] == null) continue; if (a[i] == null) return false; if (!a[i].Equals(b[i])) return false; } return true; } internal static bool OrderOfMagnitudeDifference(long MinSize, long MaxSize) { if (MinSize < Util.BINARY_KILO && MaxSize < Util.BINARY_KILO) return false; else if (MinSize < Util.BINARY_KILO && MaxSize > Util.BINARY_KILO) return true; else if (MinSize > Util.BINARY_KILO && MaxSize < Util.BINARY_KILO) return true; if (MinSize < Util.BINARY_MEGA && MaxSize < Util.BINARY_MEGA) return false; else if (MinSize < Util.BINARY_MEGA && MaxSize > Util.BINARY_MEGA) return true; else if (MinSize > Util.BINARY_MEGA && MaxSize < Util.BINARY_MEGA) return true; if (MinSize < Util.BINARY_GIGA && MaxSize < Util.BINARY_GIGA) return false; else if (MinSize < Util.BINARY_GIGA && MaxSize > Util.BINARY_GIGA) return true; else if (MinSize > Util.BINARY_GIGA && MaxSize < Util.BINARY_GIGA) return true; return false; } public static string StringFromMaxMinSize(long min, long max) { if (min == -1 && max == -1) return Messages.SIZE_NEGLIGIBLE; else if (min == -1) return Util.LThanSize(max); else if (max == -1) return Util.GThanSize(min); else if (min == max) return Util.DiskSizeString(max); else if (Helpers.OrderOfMagnitudeDifference(min, max)) return string.Format("{0} - {1}", Util.DiskSizeString(min), Util.DiskSizeString(max)); else return string.Format("{0} - {1}", Util.DiskSizeStringWithoutUnits(min), Util.DiskSizeString(max)); } public static string StringFromMaxMinSizeList(List minList, List maxList) { bool lessFlag = false; bool moreFlag = false; bool negligFlag = false; long minSum = 0; long maxSum = 0; for (int i = 0; i < minList.Count; i++) { if (minList[i] < 0 && maxList[i] < 0) { negligFlag = true; } else if (minList[i] < 0) { maxSum += maxList[i]; lessFlag = true; } else if (maxList[i] < 0) { minSum += minList[i]; moreFlag = true; } else { minSum += minList[i]; maxSum += maxList[i]; } } if (moreFlag) return Util.GThanSize(minSum); if (lessFlag) return Util.LThanSize(maxSum); if (negligFlag && maxSum <= 0) return Util.DiskSizeString(maxSum); return StringFromMaxMinSize(minSum, maxSum); } public static string GetCPUProperties(Host_cpu cpu) { return string.Format("{0}\n{1}\n{2}", string.Format(Messages.GENERAL_CPU_VENDOR, cpu.vendor), string.Format(Messages.GENERAL_CPU_MODEL, cpu.modelname), string.Format(Messages.GENERAL_CPU_SPEED, cpu.speed)); } public static string GetAllocationProperties(string initial_allocation, string quantum_allocation) { return string.Format(Messages.SR_DISK_SPACE_ALLOCATION, Util.MemorySizeStringSuitableUnits(Convert.ToDouble(initial_allocation), true, Messages.VAL_MB), Util.MemorySizeStringSuitableUnits(Convert.ToDouble(quantum_allocation), true, Messages.VAL_MB)); } public static string GetHostRestrictions(Host host) { string output = ""; List restrictions = new List(); // Build license details info box foreach (String key in host.license_params.Keys) { if (host.license_params[key] == "true") { restrictions.Add(key); } } bool first = true; restrictions.Sort(); foreach (String restriction in restrictions) { string restrictionText = Messages.ResourceManager.GetString(restriction); if (restrictionText == null) continue; if (first) { output += restrictionText; first = false; continue; } output += "\n" + restrictionText; } return output; } public static string BoolToString(bool b) { return b ? Messages.YES : Messages.NO; } private static readonly Regex IqnRegex = new Regex(@"^iqn\.\d{4}-\d{2}\.([a-zA-Z0-9][-_a-zA-Z0-9]*(\.[a-zA-Z0-9][-_a-zA-Z0-9]*)*)(:.+)?$", RegexOptions.ECMAScript); public static bool ValidateIscsiIQN(string iqn) { return IqnRegex.IsMatch(iqn); } public static bool IsOlderThanMaster(Host host) { Host master = Helpers.GetMaster(host.Connection); if (master == null || master.opaque_ref == host.opaque_ref) return false; else if (Helpers.productVersionCompare(Helpers.HostProductVersion(host), Helpers.HostProductVersion(master)) >= 0) return false; else return true; } private static readonly Regex MacRegex = new Regex(@"^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$"); public static bool IsValidMAC(string macString) { return MacRegex.IsMatch(macString); } /// /// Recursively sums the total size of all files in the directory tree rooted at the given dir. /// public static long GetDirSize(DirectoryInfo dir) { long size = 0; FileInfo[] files = dir.GetFiles(); foreach (FileInfo file in files) { size += file.Length; } DirectoryInfo[] subdirs = dir.GetDirectories(); foreach (DirectoryInfo subdir in subdirs) { size += GetDirSize(subdir); } return size; } public static string ToWindowsLineEndings(string input) { return Regex.Replace(input, "\r?\n", "\r\n"); } public static bool PoolHasEnabledHosts(Pool pool) { foreach (Host host in pool.Connection.Cache.Hosts) { if (host.enabled) { return true; } } return false; } public static string MakeUniqueName(string stub, List compareAgainst) { string pre = stub; int i = 1; while (compareAgainst.Contains(stub)) { stub = string.Format(Messages.NEWVM_DEFAULTNAME, pre, i); i++; } return stub; } public static string MakeUniqueNameFromPattern(string pattern, List compareAgainst, int startAt) { int i = startAt; string val; do { val = string.Format(pattern, i++); } while (compareAgainst.Contains(val)); return val; } public static string MakeHiddenName(string name) { return string.Format("{0}{1}", GuiTempObjectPrefix, name); } public static string GetFriendlyLicenseName(Pool pool) { var hosts = new List(pool.Connection.Cache.Hosts); if (hosts.Count > 0) { var editions = Enum.GetValues(typeof(Host.Edition)); foreach (Host.Edition edition in editions) { Host.Edition edition1 = edition; Host host = hosts.Find(h => Host.GetEdition(h.edition) == edition1); if (host != null) return GetFriendlyLicenseName(host); } } return Messages.UNKNOWN; } public static string GetFriendlyLicenseName(Host host) { if (string.IsNullOrEmpty(host.edition)) return Messages.UNKNOWN; string legacy = NaplesOrGreater(host) ? "" : "legacy-"; string name = FriendlyNameManager.GetFriendlyName("Label-host.edition-" + legacy + host.edition); return name ?? Messages.UNKNOWN; } /// /// Used for determining which features are available on the current license. /// Note that all the features are per-pool: e.g., even if iXenObject is a Host, /// we return whether *any* the hosts in that pool are forbidden that feature. /// public static bool FeatureForbidden(IXenObject iXenObject, Predicate restrictionTest) { IXenConnection connection = (iXenObject == null ? null : iXenObject.Connection); return FeatureForbidden(connection, restrictionTest); } public static bool FeatureForbidden(IXenConnection xenConnection, Predicate restrictionTest) { if (xenConnection == null) return false; foreach (Host host in xenConnection.Cache.Hosts) { if (restrictionTest(host)) return true; } return false; } /// /// Parse string represented double to a double with en-US number format /// /// String represented double /// Default value to use if the string can't be parsed /// The parsed double. public static double ParseStringToDouble(string toParse, double defaultValue) { double doubleValue; if (!double.TryParse(toParse, NumberStyles.Any, _nfi, out doubleValue)) { doubleValue = defaultValue; } return doubleValue; } /// /// Return the UUID of the given XenObject, or the empty string if that can't be found. /// public static string GetUuid(IXenObject iXenObject) { if (iXenObject == null) return ""; Type t = iXenObject.GetType(); PropertyInfo p = t.GetProperty("uuid", BindingFlags.Public | BindingFlags.Instance); if (p == null) return ""; return (string)p.GetValue(iXenObject, null); } #region Reflexive other_config and gui_config functions public static Dictionary GetOtherConfig(IXenObject o) { return o.Get("other_config") as Dictionary; } public static void SetOtherConfig(Session session, IXenObject o, String key, String value) { //Program.AssertOffEventThread(); o.Do("remove_from_other_config", session, o.opaque_ref, key); o.Do("add_to_other_config", session, o.opaque_ref, key, value); } public static void RemoveFromOtherConfig(Session session, IXenObject o, string key) { //Program.AssertOffEventThread(); o.Do("remove_from_other_config", session, o.opaque_ref, key); } public static Dictionary GetGuiConfig(IXenObject o) { return o.Get("gui_config") as Dictionary; } public static void SetGuiConfig(Session session, IXenObject o, String key, String value) { //Program.AssertOffEventThread(); o.Do("remove_from_gui_config", session, o.opaque_ref, key); o.Do("add_to_gui_config", session, o.opaque_ref, key, value); } public static void RemoveFromGuiConfig(Session session, IXenObject o, string key) { //Program.AssertOffEventThread(); o.Do("remove_from_gui_config", session, o.opaque_ref, key); } #endregion public static Regex CpuRegex = new Regex("^cpu([0-9]+)$"); public static Regex CpuAvgFreqRegex = new Regex("^CPU([0-9]+)-avg-freq$"); public static Regex CpuStateRegex = new Regex("^cpu([0-9]+)-(C|P)([0-9]+)$"); static Regex VifRegex = new Regex("^vif_([0-9]+)_(tx|rx)((_errors)?)$"); static Regex PifEthRegex = new Regex("^pif_eth([0-9]+)_(tx|rx)((_errors)?)$"); static Regex PifVlanRegex = new Regex("^pif_eth([0-9]+).([0-9]+)_(tx|rx)((_errors)?)$"); static Regex PifBrRegex = new Regex("^pif_xenbr([0-9]+)_(tx|rx)((_errors)?)$"); static Regex PifXapiRegex = new Regex("^pif_xapi([0-9]+)_(tx|rx)((_errors)?)$"); static Regex PifTapRegex = new Regex("^pif_tap([0-9]+)_(tx|rx)((_errors)?)$"); static Regex PifLoRegex = new Regex("^pif_lo_(tx|rx)((_errors)?)$"); static Regex PifBondRegex = new Regex("^pif_(bond[0-9]+)_(tx|rx)((_errors)?)$"); static Regex DiskRegex = new Regex("^vbd_((xvd|hd)[a-z]+)_(read|write)((_latency)?)$"); static Regex DiskIopsRegex = new Regex("^vbd_((xvd|hd)[a-z]+)_iops_(read|write|total)$"); static Regex DiskThroughputRegex = new Regex("^vbd_((xvd|hd)[a-z]+)_io_throughput_(read|write|total)$"); static Regex DiskOtherRegex = new Regex("^vbd_((xvd|hd)[a-z]+)_(avgqu_sz|inflight|iowait)$"); static Regex NetworkLatencyRegex = new Regex("^network/latency$"); static Regex XapiLatencyRegex = new Regex("^xapi_healthcheck/latency$"); static Regex StatefileLatencyRegex = new Regex("^statefile/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/latency$"); static Regex LoadAvgRegex = new Regex("loadavg"); static Regex SrRegex = new Regex("^sr_[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}_cache_(size|hits|misses)"); static Regex SrIORegex = new Regex("^(io_throughput|iops)_(read|write|total)_([a-f0-9]{8})$"); static Regex SrOtherRegex = new Regex("^(latency|avgqu_sz|inflight|iowait)_([a-f0-9]{8})$"); static Regex SrReadWriteRegex = new Regex("^((read|write)(_latency)?)_([a-f0-9]{8})$"); static Regex GpuRegex = new Regex(@"^gpu_((memory_(free|used))|power_usage|temperature|(utilisation_(compute|memory_io)))_(([a-fA-F0-9]{4}\/)?[a-fA-F0-9]{2}\/[0-1][a-fA-F0-9].[0-7])$"); public static string GetFriendlyDataSourceName(string name, IXenObject iXenObject) { if (iXenObject == null) return name; string s = GetFriendlyDataSourceName_(name, iXenObject); return string.IsNullOrEmpty(s) ? name : s; } private static string GetFriendlyDataSourceName_(string name, IXenObject iXenObject) { Match m; m = CpuRegex.Match(name); if (m.Success) return FormatFriendly("Label-performance.cpu", m.Groups[1].Value); m = CpuAvgFreqRegex.Match(name); if (m.Success) return FormatFriendly("Label-performance.cpu-avg-freq", m.Groups[1].Value); m = VifRegex.Match(name); if (m.Success) { string device = m.Groups[1].Value; XenAPI.Network network = FindNetworkOfVIF(iXenObject, device); return network == null ? null //don't try to retrieve it in the FriendlyNames. : FormatFriendly(string.Format("Label-performance.vif_{0}{1}", m.Groups[2].Value, m.Groups[3].Value), network.Name()); } m = PifEthRegex.Match(name); if (m.Success) return FormatFriendly(string.Format("Label-performance.nic_{0}{1}", m.Groups[2].Value, m.Groups[3].Value), m.Groups[1].Value); m = PifVlanRegex.Match(name); if (m.Success) { string device = string.Format("eth{0}", m.Groups[1].Value); XenAPI.Network network = FindVlan(iXenObject, device, m.Groups[2].Value); return network == null ? null //don't try to retrieve it in the FriendlyNames. : FormatFriendly(string.Format("Label-performance.vlan_{0}{1}", m.Groups[3].Value, m.Groups[4].Value), network.Name()); } m = PifBrRegex.Match(name); if (m.Success) { string device = string.Format("eth{0}", m.Groups[1].Value); XenAPI.Network network = FindNetworkOfPIF(iXenObject, device); return network == null ? null //don't try to retrieve it in the FriendlyNames. : FormatFriendly(string.Format("Label-performance.xenbr_{0}{1}", m.Groups[2].Value, m.Groups[3].Value), network.Name()); } m = PifXapiRegex.Match(name); if (m.Success) return FormatFriendly(string.Format("Label-performance.xapi_{0}{1}", m.Groups[2].Value, m.Groups[3].Value), m.Groups[1].Value); m = PifBondRegex.Match(name); if (m.Success) { PIF pif = FindPIF(iXenObject, m.Groups[1].Value, false); return pif == null ? null //pif doesn't exist anymore so don't try to retrieve it in the FriendlyNames. : FormatFriendly(string.Format("Label-performance.bond_{0}{1}", m.Groups[2].Value, m.Groups[3].Value), pif.Name()); } m = PifLoRegex.Match(name); if (m.Success) return FormatFriendly(string.Format("Label-performance.lo_{0}{1}", m.Groups[1].Value, m.Groups[2].Value)); m = PifTapRegex.Match(name); if (m.Success) return FormatFriendly(string.Format("Label-performance.tap_{0}{1}", m.Groups[2].Value, m.Groups[3].Value), m.Groups[1].Value); m = DiskRegex.Match(name); if (m.Success) { VBD vbd = FindVBD(iXenObject, m.Groups[1].Value); return vbd == null ? null : FormatFriendly(string.Format("Label-performance.vbd_{0}{1}", m.Groups[3].Value, m.Groups[4].Value), vbd.userdevice); } m = DiskIopsRegex.Match(name); if (m.Success) { VBD vbd = FindVBD(iXenObject, m.Groups[1].Value); return vbd == null ? null : FormatFriendly(string.Format("Label-performance.vbd_iops_{0}", m.Groups[3].Value), vbd.userdevice); } m = DiskThroughputRegex.Match(name); if (m.Success) { VBD vbd = FindVBD(iXenObject, m.Groups[1].Value); return vbd == null ? null : FormatFriendly(string.Format("Label-performance.vbd_io_throughput_{0}", m.Groups[3].Value), vbd.userdevice); } m = DiskOtherRegex.Match(name); if (m.Success) { VBD vbd = FindVBD(iXenObject, m.Groups[1].Value); return vbd == null ? null : FormatFriendly(string.Format("Label-performance.vbd_{0}", m.Groups[3].Value), vbd.userdevice); } m = SrRegex.Match(name); if (m.Success) return FormatFriendly(string.Format("Label-performance.sr_cache_{0}", m.Groups[1].Value)); m = CpuStateRegex.Match(name); if (m.Success) return FormatFriendly("Label-performance.cpu-state", m.Groups[1].Value, m.Groups[2].Value, m.Groups[3].Value); m = SrIORegex.Match(name); if (m.Success) { SR sr = FindSr(iXenObject, m.Groups[3].Value); return sr == null ? null : FormatFriendly(string.Format("Label-performance.sr_{0}_{1}", m.Groups[1].Value, m.Groups[2].Value), sr.Name().Ellipsise(30)); } m = SrOtherRegex.Match(name); if (m.Success) { SR sr = FindSr(iXenObject, m.Groups[2].Value); return sr == null ? null : FormatFriendly(string.Format("Label-performance.sr_{0}", m.Groups[1].Value), sr.Name().Ellipsise(30)); } m = SrReadWriteRegex.Match(name); if (m.Success) { SR sr = FindSr(iXenObject, m.Groups[4].Value); return sr == null ? null : FormatFriendly(string.Format("Label-performance.sr_rw_{0}", m.Groups[1].Value), sr.Name().Ellipsise(30)); } m = GpuRegex.Match(name); if (m.Success) { string pciId = m.Groups[6].Value.Replace(@"/", ":"); PGPU gpu = FindGpu(iXenObject, pciId); return gpu == null ? null : FormatFriendly(string.Format("Label-performance.gpu_{0}", m.Groups[1].Value), gpu.Name(), pciId); } if (NetworkLatencyRegex.IsMatch(name)) return FriendlyNameManager.GetFriendlyName("Label-performance.network_latency"); if (XapiLatencyRegex.IsMatch(name)) return FriendlyNameManager.GetFriendlyName("Label-performance.xapi_latency"); if (StatefileLatencyRegex.IsMatch(name)) return FriendlyNameManager.GetFriendlyName("Label-performance.statefile_latency"); if (LoadAvgRegex.IsMatch(name)) return FriendlyNameManager.GetFriendlyName("Label-performance.loadavg"); return FriendlyNameManager.GetFriendlyName(string.Format("Label-performance.{0}", name)); } /// /// Lookup key using PropertyManager.GetFriendlyName, and then apply that as a format pattern to the given args. /// private static string FormatFriendly(string key, params string[] args) { return string.Format(FriendlyNameManager.GetFriendlyName(key), args); } private static PGPU FindGpu(IXenObject iXenObject, string pciId) { foreach (PCI pci in iXenObject.Connection.Cache.PCIs) { if (pci.pci_id != pciId) continue; foreach (PGPU gpu in iXenObject.Connection.Cache.PGPUs) { if (gpu.PCI.opaque_ref == pci.opaque_ref) return gpu; } } return null; } private static XenAPI.Network FindNetworkOfVIF(IXenObject iXenObject, string device) { foreach (VIF vif in iXenObject.Connection.Cache.VIFs) { if (vif.device == device && (iXenObject is Host && ((Host)iXenObject).resident_VMs.Contains(vif.VM) || iXenObject is VM && vif.VM.opaque_ref == iXenObject.opaque_ref)) { XenAPI.Network network = iXenObject.Connection.Resolve(vif.network); if (network != null) return network; } } return null; } private static XenAPI.Network FindNetworkOfPIF(IXenObject iXenObject, string device) { PIF pif = FindPIF(iXenObject, device, true); if (pif != null) { XenAPI.Network network = iXenObject.Connection.Resolve(pif.network); if (network != null) return network; } return null; } private static XenAPI.Network FindVlan(IXenObject iXenObject, string device, string tag) { foreach (PIF pif in iXenObject.Connection.Cache.PIFs) { if (pif.device == device && (iXenObject is Host && pif.host.opaque_ref == iXenObject.opaque_ref || iXenObject is VM && pif.host.opaque_ref == ((VM)iXenObject).resident_on.opaque_ref) && pif.VLAN == long.Parse(tag)) { return iXenObject.Connection.Resolve(pif.network); } } return null; } private static PIF FindPIF(IXenObject iXenObject, string device, bool physical) { foreach (PIF pif in iXenObject.Connection.Cache.PIFs) { if ((!physical || pif.IsPhysical()) && pif.device == device && (iXenObject is Host && pif.host.opaque_ref == iXenObject.opaque_ref || iXenObject is VM && pif.host.opaque_ref == ((VM)iXenObject).resident_on.opaque_ref)) return pif; } return null; } private static VBD FindVBD(IXenObject iXenObject, string device) { if (iXenObject is VM) { VM vm = (VM)iXenObject; foreach (VBD vbd in vm.Connection.ResolveAll(vm.VBDs)) { if (vbd.device == device) return vbd; } } return null; } private static SR FindSr(IXenObject iXenObject, string srUuid) { foreach (var sr in iXenObject.Connection.Cache.SRs) { if (sr.uuid.StartsWith(srUuid)) return sr; } return null; } /// /// Returns the first line of the given string, or the empty string if null was passed in. Will take the first occurence of \r or \n /// as the end of a line. /// /// /// public static string FirstLine(string s) { if (string.IsNullOrEmpty(s)) return string.Empty; s = s.Split('\n')[0]; return s.Split('\r')[0]; } public static double StringToDouble(string str) { if (str == "NaN") return Double.NaN; else if (str == "Infinity") return Double.PositiveInfinity; else if (str == "-Infinity") return Double.NegativeInfinity; else return Convert.ToDouble(str, CultureInfo.InvariantCulture); } public static bool HAEnabled(IXenConnection connection) { Pool pool = Helpers.GetPoolOfOne(connection); if (pool == null) return false; return pool.ha_enabled; } /// /// Returns "type 'name'" for the specified object, e.g. "pool 'foo'". /// /// /// public static string GetNameAndObject(IXenObject XenObject) { if (XenObject is Pool) return string.Format(Messages.POOL_X, GetName(XenObject)); if (XenObject is Host) return string.Format(Messages.SERVER_X, GetName(XenObject)); VM vm = XenObject as VM; if (vm != null) { if (vm.IsControlDomainZero()) return string.Format(Messages.SERVER_X, GetName(XenObject.Connection.Resolve(vm.resident_on))); else return string.Format(Messages.VM_X, GetName(XenObject)); } if (XenObject is SR) return string.Format(Messages.STORAGE_REPOSITORY_X, GetName(XenObject)); return Messages.UNKNOWN_OBJECT; } /// /// Gets the i18n'd name for a HA restart priority according to FriendlyNames. /// /// /// public static string RestartPriorityI18n(VM.HA_Restart_Priority? priority) { if (priority == null) { return ""; } else { return FriendlyNameManager.GetFriendlyName("Label-VM.ha_restart_priority." + priority.ToString()) ?? priority.ToString(); } } public static string RestartPriorityDescription(VM.HA_Restart_Priority? priority) { if (priority == null) { return ""; } else { return FriendlyNameManager.GetFriendlyName("Description-VM.ha_restart_priority." + priority.ToString()) ?? priority.ToString(); } } /// /// Builds up a dictionary of the current restart priorities for all the VMs in the given IXenConnection. /// /// Must not be null. /// public static Dictionary GetVmHaRestartPriorities(IXenConnection connection,bool showHiddenVMs) { Dictionary result = new Dictionary(); foreach (VM vm in connection.Cache.VMs) { if (vm.HaCanProtect(showHiddenVMs)) { result[vm] = vm.HARestartPriority(); } } return result; } /// /// Converts Dictionary, VM.HA_Restart_Priority> -> Dictionary, string>. /// The former is useful in the GUI, the latter is suitable for sending into compute_hypothetical_max. /// /// Must not be null. /// public static Dictionary, string> GetVmHaRestartPrioritiesForApi(Dictionary settings) { Dictionary, string> result = new Dictionary, string>(); foreach (VM vm in settings.Keys) { if (settings[vm] == VM.HA_Restart_Priority.BestEffort || settings[vm] == VM.HA_Restart_Priority.DoNotRestart) { // The server doesn't want to know about best-effort/do not restart VMs. // (They don't influence the plan, and sending in the dictionary gives an error). continue; } result[new XenRef(vm.opaque_ref)] = VM.PriorityToString(settings[vm]); } return result; } /// /// Builds up a dictionary of the current startup options for all the VMs in the given IXenConnection. /// /// Must not be null. /// public static Dictionary GetVmStartupOptions(IXenConnection connection, bool showHiddenVMs) { Dictionary result = new Dictionary(); foreach (VM vm in connection.Cache.VMs) { if (vm.HaCanProtect(showHiddenVMs)) { result[vm] = new VMStartupOptions(vm.order, vm.start_delay, vm.HARestartPriority()); } } return result; } /// /// Retrieves a IXenObject from a message. May return null if type not recognised. /// /// /// public static IXenObject XenObjectFromMessage(XenAPI.Message message) { switch (message.cls) { case cls.Pool: Pool pool = message.Connection.Cache.Find_By_Uuid(message.obj_uuid); if (pool != null) return pool; break; case cls.Host: Host host = message.Connection.Cache.Find_By_Uuid(message.obj_uuid); if (host != null) return host; break; case cls.VM: VM vm = message.Connection.Cache.Find_By_Uuid(message.obj_uuid); if (vm != null) return vm; break; case cls.SR: SR sr = message.Connection.Cache.Find_By_Uuid(message.obj_uuid); if (sr != null) return sr; break; case cls.VMSS: VMSS vmss = message.Connection.Cache.Find_By_Uuid(message.obj_uuid); if (vmss != null) return vmss; break; case cls.PVS_proxy: PVS_proxy proxy = message.Connection.Cache.Find_By_Uuid(message.obj_uuid); if (proxy != null) return proxy; break; } return null; } /// /// Load an xml stream and ignore comments and whitespace /// /// /// public static XmlDocument LoadXmlDocument(Stream xmlStream) { XmlDocument doc = new XmlDocument(); XmlReaderSettings settings = new XmlReaderSettings(); settings.IgnoreComments = true; settings.IgnoreWhitespace = true; settings.IgnoreProcessingInstructions = true; doc.Load(XmlReader.Create(xmlStream, settings)); return doc; } public static Regex HostnameOrIpRegex = new Regex(@"[\w.]+"); public static string HostnameFromLocation(string p) { foreach (Match m in HostnameOrIpRegex.Matches(p)) { return m.Value; // we only want the hostname or ip which should be the first match } return ""; } public static string GetStringXmlAttribute(XmlNode Node, string AttributeName) { if (Node.Attributes[AttributeName] == null) return null; return Node.Attributes[AttributeName].Value; } public static string GetStringXmlAttribute(XmlNode Node, string AttributeName, string Default) { if (Node.Attributes[AttributeName] == null) return Default; return Node.Attributes[AttributeName].Value; } /// /// Retrieves a true of false value from an XML attribute. Returns null-bool if the attribute doesnt exist or the /// value is malformed. /// /// /// /// public static bool? GetBoolXmlAttribute(XmlNode Node, string AttributeName) { bool b; if (Node.Attributes[AttributeName] == null) return null; if (bool.TryParse(Node.Attributes[AttributeName].Value, out b)) return b; return null; } /// /// Retrieves a true of false value from an XML attribute. Returns Default if the attribute doesnt exist or the /// value is malformed. /// /// /// /// public static bool GetBoolXmlAttribute(XmlNode Node, string AttributeName, bool Default) { bool? b = GetBoolXmlAttribute(Node, AttributeName); if (!b.HasValue) return Default; return b.Value; } /// /// Retrieves a float value from an XML attribute. Defaults to null-float if the attribute doesnt exist or the /// value is malformed. /// /// /// /// public static float? GetFloatXmlAttribute(XmlNode Node, string AttributeName) { float f; if (Node.Attributes[AttributeName] == null) return null; if (float.TryParse(Node.Attributes[AttributeName].Value, out f)) return f; return null; } /// /// Retrieves a float value from an XML attribute. Returns Default if the attribute doesnt exist or the /// value is malformed. /// /// /// /// public static float GetFloatXmlAttribute(XmlNode Node, string AttributeName, float Default) { float? f = GetFloatXmlAttribute(Node, AttributeName); if (!f.HasValue) return Default; return f.Value; } /// /// Retrieves an int value from an XML attribute. Defaults to null-int if the attribute doesnt exist or the /// value is malformed. /// /// /// /// public static int? GetIntXmlAttribute(XmlNode Node, string AttributeName) { int i; if (Node.Attributes[AttributeName] == null) return null; if (int.TryParse(Node.Attributes[AttributeName].Value, out i)) return i; return null; } /// /// Retrieves an int value from an XML attribute. Returns Default if the attribute doesnt exist or the /// value is malformed. /// /// /// /// public static int GetIntXmlAttribute(XmlNode Node, string AttributeName, int Default) { int? i = GetIntXmlAttribute(Node, AttributeName); if (!i.HasValue) return Default; return i.Value; } /// /// Retrieves the string content of an XmlNode attribute or throws an I18NException if it missing. /// /// /// /// public static string GetXmlAttribute(XmlNode Node, string Attribute) { if (Node.Attributes[Attribute] == null) throw new I18NException(I18NExceptionType.XmlAttributeMissing, Attribute, Node.Name); return Node.Attributes[Attribute].Value; } /// /// Retrieves the enum content of an XmlNode attribute or Default if it is missing. /// /// WARNING: Runtime check that typeof(T).IsEnum (Sorry! C# doesnt support Enum generics very well). /// /// /// /// public static T GetEnumXmlAttribute(XmlNode Node, string Attribute, T Default) { if (Node.Attributes[Attribute] == null) return Default; System.Diagnostics.Trace.Assert(typeof(T).IsEnum, "Supplied type to GetEnumXmlAttribute is not an enum"); try { T result = (T)Enum.Parse(typeof(T), Node.Attributes[Attribute].Value); return result; } catch { return Default; } } public static string PrettyFingerprint(string p) { List pairs = new List(); for (int i = 0; i < p.Length; i += 2) { pairs.Add(p.Substring(i, 2)); } return string.Join(":", pairs.ToArray()); } public static string GetUrl(IXenConnection connection) { UriBuilder uriBuilder = new UriBuilder(connection.UriScheme, connection.Hostname); uriBuilder.Port = connection.Port; return uriBuilder.ToString(); } public static string DefaultVMName(string p, IXenConnection connection) { for (int i = 1; true; i++) { bool willDo = true; string name = string.Format(Messages.NEWVM_DEFAULTNAME, p, i); string hiddenName = Helpers.GuiTempObjectPrefix + name; // Check to see if name is in use foreach (VM v in connection.Cache.VMs) { if (v.name_label == name || v.name_label == hiddenName) { willDo = false; break; } } if (willDo) return name; } } public static bool CDsExist(IXenConnection connection) { if (connection == null) return false; foreach (SR sr in connection.Cache.SRs) { if (sr.content_type != SR.Content_Type_ISO) continue; if (sr.VDIs.Count > 0) return true; } return false; } public static bool CompareLists(List l1, List l2) { if (l1 == l2) return true; else if (l1 == null || l2 == null) return false; bool same = l1.Count == l2.Count; foreach (T item in l1) { if (!l2.Contains(item)) { same = false; break; } } return same; } public static bool ListsIntersect(List l1, List l2) { foreach (T item in l1) { if (l2.Contains(item)) return true; } return false; } public static bool CustomWithNoDVD(VM template) { return template != null && !template.DefaultTemplate() && template.FindVMCDROM() == null; } public static string GetMacString(string mac) { return mac == "" ? Messages.MAC_AUTOGENERATE : mac; } public static PIF FindPIF(XenAPI.Network network, Host owner) { foreach (PIF pif in network.Connection.ResolveAll(network.PIFs)) { if (owner == null || pif.host == owner.opaque_ref) return pif; } return null; } public static string VlanString(PIF pif) { if (pif == null || pif.VLAN == -1) return Messages.SPACED_HYPHEN; return pif.VLAN.ToString(); } /// /// Return a string version of a list, in the form "L1, L2, L3 and L4" /// public static string StringifyList(List list) { if (list == null) return String.Empty; StringBuilder ans = new StringBuilder(); for (int i = 0; i < list.Count; ++i) { ans.Append(list[i].ToString()); if (i < list.Count - 2) ans.Append(Messages.STRINGIFY_LIST_INNERSEP); else if (i == list.Count - 2) ans.Append(Messages.STRINGIFY_LIST_LASTSEP); } return ans.ToString(); } /// /// Does the connection support Link aggregation (LACP) bonds (i.e. on the vSwitch backend)? /// public static bool SupportsLinkAggregationBond(IXenConnection connection) { Host master = GetMaster(connection); return master != null && master.vSwitchNetworkBackend(); } /// /// Number of alloowed NICs per bond /// public static int BondSizeLimit(IXenConnection connection) { Host master = GetMaster(connection); // For hosts on the vSwitch backend, we allow 4 NICs per bond; otherwise, 2 return master != null && master.vSwitchNetworkBackend() ? 4 : 2; } public static Host GetHostAncestor(IXenObject xenObject) { if (xenObject == null || xenObject.Connection == null) return null; var h = xenObject as Host; if (h != null) return h; var sr = xenObject as SR; if (sr != null) return sr.Home(); var vm = xenObject as VM; if (vm != null) return vm.Home(); return null; } public static bool SameServerVersion(Host host, string longProductVersion) { return host != null && host.LongProductVersion() == longProductVersion; } public static bool EnabledTargetExists(Host host, IXenConnection connection) { if (host != null) return host.enabled; return connection.Cache.Hosts.Any(h => h.enabled); } public static bool GpuCapability(IXenConnection connection) { if (FeatureForbidden(connection, Host.RestrictGpu)) return false; var pool = GetPoolOfOne(connection); return pool != null && pool.HasGpu(); } public static bool VGpuCapability(IXenConnection connection) { if (FeatureForbidden(connection, Host.RestrictVgpu)) return false; var pool = GetPoolOfOne(connection); return pool != null && pool.HasVGpu(); } /// /// Whether creation of VLAN 0 is allowed. /// public static bool VLAN0Allowed(IXenConnection connection) { Host master = GetMaster(connection); // For Creedence or later on the vSwitch backend, we allow creation of VLAN 0 return master != null && master.vSwitchNetworkBackend(); } public static bool ContainerCapability(IXenConnection connection) { var master = GetMaster(connection); if (master == null) return false; if (master.ProductBrand() == PRODUCT_BRAND_XCP_NG) return true; if (ElyOrGreater(connection)) return master.AppliedUpdates().Any(update => update.Name().ToLower().StartsWith("xscontainer")); return master.SuppPacks().Any(suppPack => suppPack.Name.ToLower().StartsWith("xscontainer")); } public static bool PvsCacheCapability(IXenConnection connection) { var master = GetMaster(connection); return master != null && master.AppliedUpdates().Any(update => update.Name().ToLower().StartsWith("pvsaccelerator")); } /// /// This method returns the disk space required (bytes) on the provided SR for the provided VDI. /// /// Disk size required in bytes. public static long GetRequiredSpaceToCreateVdiOnSr(SR sr, VDI vdi) { if (sr == null) throw new ArgumentNullException("sr"); if (vdi == null) throw new ArgumentNullException("vdi"); return vdi.virtual_size; } public static string UrlEncode(this string str) { if (string.IsNullOrEmpty(str)) return str; return System.Net.WebUtility.UrlEncode(str); } public static string UpdatesFriendlyName(string propertyName) { return FriendlyNameManager.FriendlyNames.GetString(string.Format("Label-{0}", propertyName)) ?? propertyName; } public static string UpdatesFriendlyNameAndVersion(Pool_update update) { var friendlyName = UpdatesFriendlyName(update.Name()); if (string.IsNullOrEmpty(update.version)) return friendlyName; return string.Format(Messages.SUPP_PACK_DESCRIPTION, friendlyName, update.version); } public static List HostAppliedPatchesList(Host host) { List result = new List(); if (ElyOrGreater(host)) { foreach (var update in host.AppliedUpdates()) result.Add(UpdatesFriendlyNameAndVersion(update)); } else { foreach (Pool_patch patch in host.AppliedPatches()) result.Add(patch.Name()); } result.Sort(StringUtility.NaturalCompare); return result; } public static List FindIpAddresses(Dictionary networks, string device) { if (networks == null || string.IsNullOrWhiteSpace(device)) return new List(); // PR-1373 - VM_guest_metrics.networks is a dictionary of IP addresses in the format: // [["0/ip", ], // ["0/ipv4/0", ], ["0/ipv4/1", ], // ["0/ipv6/0", ], ["0/ipv6/1", ]] return (from network in networks where network.Key.StartsWith(string.Format("{0}/ip", device)) orderby network.Key select network.Value.Split(new[] { "\n", "%n" }, StringSplitOptions.RemoveEmptyEntries)).SelectMany(x => x).Distinct().ToList(); } } }