2017-01-16 20:59:50 +01:00
|
|
|
|
/* Copyright (c) Citrix Systems, Inc.
|
2013-06-24 13:41:48 +02:00
|
|
|
|
* 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;
|
2017-11-17 02:04:45 +01:00
|
|
|
|
using XenCenterLib;
|
2013-06-24 13:41:48 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace XenAdmin.Core
|
|
|
|
|
{
|
|
|
|
|
public static class Helpers
|
|
|
|
|
{
|
|
|
|
|
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
2015-09-07 18:45:57 +02:00
|
|
|
|
|
2015-08-25 15:50:41 +02:00
|
|
|
|
private const long XLVHD_DEF_ALLOCATION_QUANTUM_DIVISOR = 10000;
|
2015-09-07 18:45:57 +02:00
|
|
|
|
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
|
2013-06-24 13:41:48 +02:00
|
|
|
|
|
|
|
|
|
public const int DEFAULT_NAME_TRIM_LENGTH = 50;
|
2015-08-25 15:50:41 +02:00
|
|
|
|
|
2013-06-24 13:41:48 +02:00
|
|
|
|
public const string GuiTempObjectPrefix = "__gui__";
|
|
|
|
|
|
|
|
|
|
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}");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 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.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="Host">May be null.</param>
|
|
|
|
|
public static string HostProductVersion(Host host)
|
|
|
|
|
{
|
2017-09-03 04:33:29 +02:00
|
|
|
|
return FromHostOrMaster(host, h => h.ProductVersion());
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string HostProductVersionText(Host host)
|
|
|
|
|
{
|
2017-09-03 04:33:29 +02:00
|
|
|
|
return FromHostOrMaster(host, h => h.ProductVersionText());
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string HostProductVersionTextShort(Host host)
|
|
|
|
|
{
|
2017-09-03 04:33:29 +02:00
|
|
|
|
return FromHostOrMaster(host, h => h.ProductVersionTextShort());
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string HostPlatformVersion(Host host)
|
|
|
|
|
{
|
|
|
|
|
if (host == null)
|
|
|
|
|
return null;
|
|
|
|
|
|
2017-09-03 04:33:29 +02:00
|
|
|
|
return host.PlatformVersion();
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Only log the unrecognised version message once (CA-11201).
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static bool _unrecognisedVersionWarned = false;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Numbers should have three parts, i.e. be in the form a.b.c, otherwise they won't be parsed.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="version1">May be null.</param>
|
|
|
|
|
/// <param name="version2">May be null.</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2013-09-18 14:06:47 +02:00
|
|
|
|
/// Gets the pool for the provided connection. Returns null if we have
|
|
|
|
|
/// a standalone host (or the provided connection is null).
|
2013-06-24 13:41:48 +02:00
|
|
|
|
/// </summary>
|
2013-09-18 14:06:47 +02:00
|
|
|
|
/// <remarks> To obtain the pool object for the case of a standalone host
|
|
|
|
|
/// use GetPoolOfOne.</remarks>
|
2013-06-24 13:41:48 +02:00
|
|
|
|
public static Pool GetPool(IXenConnection connection)
|
|
|
|
|
{
|
|
|
|
|
if (connection == null)
|
|
|
|
|
return null;
|
|
|
|
|
|
2013-09-18 14:06:47 +02:00
|
|
|
|
foreach (Pool thePool in connection.Cache.Pools)
|
2013-06-24 13:41:48 +02:00
|
|
|
|
{
|
2017-09-03 04:33:29 +02:00
|
|
|
|
if (thePool != null && thePool.IsVisible())
|
2013-09-18 14:06:47 +02:00
|
|
|
|
return thePool;
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2013-09-18 14:06:47 +02:00
|
|
|
|
/// 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.
|
2013-06-24 13:41:48 +02:00
|
|
|
|
/// </summary>
|
|
|
|
|
public static Pool GetPoolOfOne(IXenConnection connection)
|
|
|
|
|
{
|
2013-09-18 14:06:47 +02:00
|
|
|
|
if (connection == null)
|
|
|
|
|
return null;
|
|
|
|
|
|
2013-06-24 13:41:48 +02:00
|
|
|
|
foreach (Pool pool in connection.Cache.Pools)
|
|
|
|
|
return pool;
|
2013-09-18 14:06:47 +02:00
|
|
|
|
|
2013-06-24 13:41:48 +02:00
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Return the host object representing the master of the given connection, or null if the
|
|
|
|
|
/// cache is being populated.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="connection">May not be null.</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static Host GetMaster(IXenConnection connection)
|
|
|
|
|
{
|
|
|
|
|
Pool pool = GetPoolOfOne(connection);
|
|
|
|
|
return pool == null ? null : connection.Resolve(pool.master);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Return the host object representing the master of the given pool.
|
|
|
|
|
/// (If pool is null, returns null).
|
|
|
|
|
/// </summary>
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <param name="pool">May be null, in which case the empty string is returned.</param>
|
|
|
|
|
public static string GetName(Pool pool)
|
|
|
|
|
{
|
2017-09-03 04:33:29 +02:00
|
|
|
|
return pool == null ? "" : pool.Name();
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <param name="connection">May be null, in which case the empty string is returned.</param>
|
|
|
|
|
public static string GetName(IXenConnection connection)
|
|
|
|
|
{
|
|
|
|
|
return connection == null ? "" : connection.Name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <param name="o">May be null, in which case the empty string is returned.</param>
|
|
|
|
|
public static string GetName(IXenObject o)
|
|
|
|
|
{
|
2017-09-03 04:33:29 +02:00
|
|
|
|
return o == null ? "" : o.Name();
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool IsConnected(IXenConnection connection)
|
|
|
|
|
{
|
2017-04-10 10:10:00 +02:00
|
|
|
|
return (connection != null && connection.IsConnected);
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool IsConnected(Pool pool)
|
|
|
|
|
{
|
2017-04-10 10:10:00 +02:00
|
|
|
|
return (pool != null && IsConnected(pool.Connection));
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool IsConnected(Host host)
|
|
|
|
|
{
|
2017-04-10 10:10:00 +02:00
|
|
|
|
return (host != null && IsConnected(host.Connection));
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-28 07:44:37 +02:00
|
|
|
|
/// <param name="conn">May be null, in which case true is returned.</param>
|
|
|
|
|
public static bool CreedenceOrGreater(IXenConnection conn)
|
|
|
|
|
{
|
2017-04-10 10:10:00 +02:00
|
|
|
|
return conn == null || CreedenceOrGreater(Helpers.GetMaster(conn));
|
2014-04-28 07:44:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
2014-04-29 05:12:53 +02:00
|
|
|
|
/// Creedence is ver. 1.9.0
|
2014-04-28 07:44:37 +02:00
|
|
|
|
/// <param name="host">May be null, in which case true is returned.</param>
|
|
|
|
|
public static bool CreedenceOrGreater(Host host)
|
|
|
|
|
{
|
|
|
|
|
if (host == null)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
string platform_version = HostPlatformVersion(host);
|
|
|
|
|
return
|
2017-04-11 17:01:20 +02:00
|
|
|
|
platform_version != null && productVersionCompare(platform_version, "1.8.90") >= 0;
|
2014-04-28 07:44:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-03-09 21:05:31 +01:00
|
|
|
|
/// <param name="conn">May be null, in which case true is returned.</param>
|
|
|
|
|
public static bool DundeeOrGreater(IXenConnection conn)
|
|
|
|
|
{
|
2017-04-10 10:10:00 +02:00
|
|
|
|
return conn == null || DundeeOrGreater(Helpers.GetMaster(conn));
|
2015-03-09 21:05:31 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Dundee is ver. 2.0.0
|
|
|
|
|
/// <param name="host">May be null, in which case true is returned.</param>
|
|
|
|
|
public static bool DundeeOrGreater(Host host)
|
|
|
|
|
{
|
|
|
|
|
if (host == null)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
string platform_version = HostPlatformVersion(host);
|
|
|
|
|
return
|
2017-04-11 17:01:20 +02:00
|
|
|
|
platform_version != null && productVersionCompare(platform_version, "2.0.0") >= 0;
|
2015-03-09 21:05:31 +01:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-01 10:25:10 +02:00
|
|
|
|
/// <param name="conn">May be null, in which case true is returned.</param>
|
|
|
|
|
public static bool ElyOrGreater(IXenConnection conn)
|
|
|
|
|
{
|
2017-04-10 10:10:00 +02:00
|
|
|
|
return conn == null || ElyOrGreater(Helpers.GetMaster(conn));
|
2016-08-01 10:25:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Ely is ver. 2.1.1
|
|
|
|
|
/// <param name="host">May be null, in which case true is returned.</param>
|
|
|
|
|
public static bool ElyOrGreater(Host host)
|
|
|
|
|
{
|
|
|
|
|
if (host == null)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
string platform_version = HostPlatformVersion(host);
|
|
|
|
|
return
|
2017-04-11 17:01:20 +02:00
|
|
|
|
platform_version != null && productVersionCompare(platform_version, "2.1.1") >= 0;
|
2016-08-01 10:25:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-03 13:12:24 +01:00
|
|
|
|
/// <param name="conn">May be null, in which case true is returned.</param>
|
|
|
|
|
public static bool FalconOrGreater(IXenConnection conn)
|
|
|
|
|
{
|
2017-04-10 10:10:00 +02:00
|
|
|
|
return conn == null || FalconOrGreater(Helpers.GetMaster(conn));
|
2017-02-03 13:12:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Falcon is ver. 2.3.0
|
|
|
|
|
/// <param name="host">May be null, in which case true is returned.</param>
|
|
|
|
|
public static bool FalconOrGreater(Host host)
|
|
|
|
|
{
|
|
|
|
|
if (host == null)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
string platform_version = HostPlatformVersion(host);
|
|
|
|
|
return
|
2017-04-11 17:01:20 +02:00
|
|
|
|
platform_version != null && productVersionCompare(platform_version, "2.2.50") >= 0;
|
2016-08-01 10:25:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-02 12:29:39 +02:00
|
|
|
|
/// <param name="conn">May be null, in which case true is returned.</param>
|
|
|
|
|
public static bool InvernessOrGreater(IXenConnection conn)
|
|
|
|
|
{
|
|
|
|
|
return conn == null || InvernessOrGreater(Helpers.GetMaster(conn));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Inverness is ver. 2.4.0
|
|
|
|
|
/// <param name="host">May be null, in which case true is returned.</param>
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-18 12:39:05 +01:00
|
|
|
|
/// <param name="conn">May be null, in which case true is returned.</param>
|
2017-12-19 14:12:39 +01:00
|
|
|
|
public static bool JuraOrGreater(IXenConnection conn)
|
2017-12-18 12:39:05 +01:00
|
|
|
|
{
|
|
|
|
|
return conn == null || JuraOrGreater(Helpers.GetMaster(conn));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Jura is ver. 2.5.0
|
|
|
|
|
/// <param name="host">May be null, in which case true is returned.</param>
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-04 12:19:01 +01:00
|
|
|
|
/// <param name="conn">May be null, in which case true is returned.</param>
|
|
|
|
|
public static bool KolkataOrGreater(IXenConnection conn)
|
|
|
|
|
{
|
|
|
|
|
return conn == null || KolkataOrGreater(Helpers.GetMaster(conn));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Kolkata platform version is 2.6.0
|
|
|
|
|
/// <param name="host">May be null, in which case true is returned.</param>
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-26 17:01:02 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Cream (Creedence SP1) has API version 2.4
|
|
|
|
|
/// </summary>
|
2015-01-09 04:24:26 +01:00
|
|
|
|
/// <param name="conn">May be null, in which case true is returned.</param>
|
2015-01-26 17:01:02 +01:00
|
|
|
|
/// <returns></returns>
|
2015-01-09 04:24:26 +01:00
|
|
|
|
public static bool CreamOrGreater(IXenConnection conn)
|
|
|
|
|
{
|
2015-01-26 17:01:02 +01:00
|
|
|
|
return conn == null || conn.Session == null || conn.Session.APIVersion >= API_Version.API_2_4;
|
2015-01-09 04:24:26 +01:00
|
|
|
|
}
|
|
|
|
|
|
2016-06-24 19:05:22 +02:00
|
|
|
|
public static bool DundeePlusOrGreater(IXenConnection conn)
|
|
|
|
|
{
|
|
|
|
|
return conn == null || conn.Session == null || conn.Session.APIVersion >= API_Version.API_2_6;
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-28 07:44:37 +02:00
|
|
|
|
/// Clearwater is ver. 1.7.0
|
2014-04-29 05:12:53 +02:00
|
|
|
|
/// <param name="conn">May be null, in which case true is returned.</param>
|
|
|
|
|
public static bool IsClearwater(IXenConnection conn)
|
2014-04-28 07:44:37 +02:00
|
|
|
|
{
|
2014-04-29 05:12:53 +02:00
|
|
|
|
if(conn == null) return true;
|
|
|
|
|
else {
|
|
|
|
|
Host host = Helpers.GetMaster(conn);
|
|
|
|
|
return (ClearwaterOrGreater(host) && !CreedenceOrGreater(host));
|
|
|
|
|
}
|
2014-04-28 07:44:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
2013-06-24 13:41:48 +02:00
|
|
|
|
/// <param name="conn">May be null, in which case true is returned.</param>
|
|
|
|
|
public static bool ClearwaterOrGreater(IXenConnection conn)
|
|
|
|
|
{
|
2017-04-10 10:10:00 +02:00
|
|
|
|
return conn == null || ClearwaterOrGreater(Helpers.GetMaster(conn));
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Clearwater is ver. 1.7.0
|
|
|
|
|
/// <param name="host">May be null, in which case true is returned.</param>
|
|
|
|
|
public static bool ClearwaterOrGreater(Host host)
|
|
|
|
|
{
|
|
|
|
|
if (host == null)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
string platform_version = HostPlatformVersion(host);
|
|
|
|
|
return
|
2017-04-11 17:01:20 +02:00
|
|
|
|
platform_version != null && productVersionCompare(platform_version, "1.6.900") >= 0;
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-01-19 17:49:47 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Clearwater SP1 has API version 2.1
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="conn">May be null, in which case true is returned.</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static bool ClearwaterSp1OrGreater(IXenConnection conn)
|
|
|
|
|
{
|
|
|
|
|
return conn == null || conn.Session == null || conn.Session.APIVersion >= API_Version.API_2_1;
|
|
|
|
|
}
|
|
|
|
|
|
2013-06-24 13:41:48 +02:00
|
|
|
|
// CP-3435: Disable Check for Updates in Common Criteria Certification project
|
|
|
|
|
public static bool CommonCriteriaCertificationRelease
|
|
|
|
|
{
|
|
|
|
|
get { return false; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// WLB: Whether pool has wlb enabled.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="connection">May not be null.</param>
|
|
|
|
|
/// <returns>true when wlb is enabled, otherwise false</returns>
|
|
|
|
|
public static bool WlbEnabled(IXenConnection connection)
|
|
|
|
|
{
|
2014-04-28 07:44:37 +02:00
|
|
|
|
//Clearwater doesn't has WLB
|
2014-04-29 05:12:53 +02:00
|
|
|
|
if (IsClearwater(connection))
|
2014-04-28 07:44:37 +02:00
|
|
|
|
return false;
|
|
|
|
|
|
2013-06-24 13:41:48 +02:00
|
|
|
|
Pool pool = GetPoolOfOne(connection);
|
|
|
|
|
if (pool == null)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return pool.wlb_enabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool WlbEnabledAndConfigured(IXenConnection conn)
|
|
|
|
|
{
|
|
|
|
|
return WlbEnabled(conn)&& WlbConfigured(conn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool WlbConfigured(IXenConnection conn)
|
|
|
|
|
{
|
2014-04-28 07:44:37 +02:00
|
|
|
|
//Clearwater doesn't has WLB
|
2014-04-29 05:12:53 +02:00
|
|
|
|
if (IsClearwater(conn))
|
2014-04-28 07:44:37 +02:00
|
|
|
|
return false;
|
|
|
|
|
|
2013-06-24 13:41:48 +02:00
|
|
|
|
Pool p = GetPoolOfOne(conn);
|
|
|
|
|
return (p != null && !String.IsNullOrEmpty(p.wlb_url));
|
|
|
|
|
}
|
2015-12-16 13:58:24 +01:00
|
|
|
|
|
|
|
|
|
public static bool CrossPoolMigrationRestrictedWithWlb(IXenConnection conn)
|
|
|
|
|
{
|
|
|
|
|
return WlbEnabledAndConfigured(conn) && !DundeeOrGreater(conn);
|
|
|
|
|
}
|
2013-06-24 13:41:48 +02:00
|
|
|
|
|
2015-08-25 15:50:41 +02:00
|
|
|
|
|
2013-06-24 13:41:48 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Determines whether two lists contain the same elements (but not necessarily in the same order).
|
|
|
|
|
/// Compares list elements using reference equality.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
|
|
|
|
/// <param name="lst1">Must not be null.</param>
|
|
|
|
|
/// <param name="lst2">Must not be null.</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static bool ListsContentsEqual<T>(List<T> lst1, List<T> lst2)
|
|
|
|
|
{
|
|
|
|
|
if (lst1.Count != lst2.Count)
|
|
|
|
|
return false;
|
|
|
|
|
foreach (T item1 in lst1)
|
|
|
|
|
if (!lst2.Contains(item1))
|
|
|
|
|
return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
///
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
|
|
|
|
/// <param name="lst1">Must not be null.</param>
|
|
|
|
|
/// <param name="lst2">Must not be null.</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static List<T> ListsCommonItems<T>(List<T> lst1, List<T> lst2)
|
|
|
|
|
{
|
|
|
|
|
List<T> common = new List<T>();
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 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.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
|
|
|
|
/// <param name="a">Must not be null.</param>
|
|
|
|
|
/// <param name="b">Must not be null.</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static bool ArrayElementsEqual<T>(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 StringFromMaxMinTime(long min, long max)
|
|
|
|
|
{
|
|
|
|
|
if (min == -1 && max == -1)
|
|
|
|
|
return Messages.TIME_NEGLIGIBLE;
|
|
|
|
|
else if (min == -1)
|
|
|
|
|
return Util.LThanTime(max);
|
|
|
|
|
else if (max == -1)
|
|
|
|
|
return Util.GThanTime(min);
|
|
|
|
|
else if (min == max)
|
|
|
|
|
return Util.TimeString(max);
|
|
|
|
|
else
|
|
|
|
|
return Util.TimeRangeString(min, max);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string StringFromMaxMinTimeList(List<long> minList, List<long> 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.GThanTime(minSum);
|
|
|
|
|
if (lessFlag)
|
|
|
|
|
return Util.LThanTime(maxSum);
|
|
|
|
|
if (negligFlag && maxSum <= 0)
|
|
|
|
|
return Util.TimeString(maxSum);
|
|
|
|
|
return StringFromMaxMinTime(minSum, maxSum);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string StringFromMaxMinSizeList(List<long> minList, List<long> 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));
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-21 15:16:40 +02:00
|
|
|
|
public static string GetAllocationProperties(string initial_allocation, string quantum_allocation)
|
|
|
|
|
{
|
|
|
|
|
return string.Format(Messages.SR_DISK_SPACE_ALLOCATION,
|
2015-09-17 14:24:21 +02:00
|
|
|
|
Util.MemorySizeStringSuitableUnits(Convert.ToDouble(initial_allocation), true, Messages.VAL_MB),
|
|
|
|
|
Util.MemorySizeStringSuitableUnits(Convert.ToDouble(quantum_allocation), true, Messages.VAL_MB));
|
2015-08-21 15:16:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
2013-06-24 13:41:48 +02:00
|
|
|
|
public static string GetHostRestrictions(Host host)
|
|
|
|
|
{
|
|
|
|
|
string output = "";
|
|
|
|
|
|
|
|
|
|
List<string> restrictions = new List<string>();
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Recursively sums the total size of all files in the directory tree rooted at the given dir.
|
|
|
|
|
/// </summary>
|
|
|
|
|
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<string> 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<string> compareAgainst, int startAt)
|
|
|
|
|
{
|
|
|
|
|
int i = startAt;
|
|
|
|
|
string val;
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
val = string.Format(pattern, i++);
|
|
|
|
|
} while (compareAgainst.Contains(val));
|
|
|
|
|
|
|
|
|
|
return val;
|
|
|
|
|
}
|
|
|
|
|
|
[CA-233454] PVS tab doesn't show a new VM when it's created (#1314)
* [CA-233454] PVS tab doesn't show a new VM when it's created
Refined the rules for not adding a VM to the table, if it is a template (thus not_a_real_vm), and it has the __gui__ prefix (thus hidden), we still add it, but hide it.
When its name is changed (to remove the __gui__ prefix), we update its name and re-calculate whether it should be visible (in the case of a new VM this will be true once the __gui__ prefix is gone). Also resort the table if a node changes from hidden to visible, because it appears as an addition to the table.
Signed-off-by: Callum McIntyre <callumiandavid.mcintyre@citrix.com>
* [CA-233454] Update criteria for VM visibility to include is_a_real_vm
is_a_template is changed before the name_label removes the __gui__ prefix, so this works with no other changes to the vm property changed event.
Signed-off-by: Callum McIntyre <callumiandavid.mcintyre@citrix.com>
* [CA-233454] Update when the VMs are shown in the PVS list
New observable property IsBeingCreated for VMs, set to true when they're made a hidden object, and false when they're removed from hidden objects (both in CreateVMAction). In the PVS Page when this is set to false, we re-evaluate whether a VM can be shown. This means that new VMs show here at the same time they're added to the tree (only different is tree refresh time), instead of far earlier (and before their networks were added).
Signed-off-by: Callum McIntyre <callumiandavid.mcintyre@citrix.com>
* [CA-233454] Set IsbeingCreated in the CreateVMFastAction
* [CA-233454] Properly support the VM Fast Create action
Further changes to CreateVMFastAction, to ensure it works with PVS tab - use the __gui__ prefix when the VM is created and then change it back just before showing.
* [CA-233454] Small logic adjustments/tidying up
2016-11-30 13:24:02 +01:00
|
|
|
|
public static string MakeHiddenName(string name)
|
|
|
|
|
{
|
|
|
|
|
return string.Format("{0}{1}", GuiTempObjectPrefix, name);
|
|
|
|
|
}
|
|
|
|
|
|
2013-06-24 13:41:48 +02:00
|
|
|
|
public static string GetFriendlyLicenseName(Host host)
|
|
|
|
|
{
|
2015-10-26 17:01:55 +01:00
|
|
|
|
if (string.IsNullOrEmpty(host.edition))
|
|
|
|
|
return Messages.UNKNOWN;
|
2013-06-24 13:41:48 +02:00
|
|
|
|
|
2015-10-26 17:01:55 +01:00
|
|
|
|
string name = PropertyManager.GetFriendlyName("Label-host.edition-" + host.edition);
|
|
|
|
|
return name ?? Messages.UNKNOWN;
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 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.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static bool FeatureForbidden(IXenObject iXenObject, Predicate<Host> restrictionTest)
|
|
|
|
|
{
|
|
|
|
|
IXenConnection connection = (iXenObject == null ? null : iXenObject.Connection);
|
|
|
|
|
return FeatureForbidden(connection, restrictionTest);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool FeatureForbidden(IXenConnection xenConnection, Predicate<Host> restrictionTest)
|
|
|
|
|
{
|
|
|
|
|
if (xenConnection == null)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
foreach (Host host in xenConnection.Cache.Hosts)
|
|
|
|
|
{
|
|
|
|
|
if (restrictionTest(host))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Shuffles a list in-place.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
|
|
|
|
/// <param name="listToShuffle"></param>
|
|
|
|
|
public static void ShuffleList<T>(List<T> listToShuffle)
|
|
|
|
|
{
|
|
|
|
|
Random r = new Random();
|
|
|
|
|
|
|
|
|
|
for (int k = listToShuffle.Count - 1; k > 1; --k)
|
|
|
|
|
{
|
|
|
|
|
int randIndx = r.Next(k);
|
|
|
|
|
T temp = listToShuffle[k];
|
|
|
|
|
listToShuffle[k] = listToShuffle[randIndx];
|
|
|
|
|
listToShuffle[randIndx] = temp;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Parse string represented double to a double with en-US number format
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="toParse">String represented double</param>
|
|
|
|
|
/// <param name="defaultValue">Default value to use if the string can't be parsed</param>
|
|
|
|
|
/// <returns>The parsed double.</returns>
|
|
|
|
|
public static double ParseStringToDouble(string toParse, double defaultValue)
|
|
|
|
|
{
|
|
|
|
|
double doubleValue;
|
|
|
|
|
if (!double.TryParse(toParse, NumberStyles.Any, _nfi, out doubleValue))
|
|
|
|
|
{
|
|
|
|
|
doubleValue = defaultValue;
|
|
|
|
|
}
|
|
|
|
|
return doubleValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Return the UUID of the given XenObject, or the empty string if that can't be found.
|
|
|
|
|
/// </summary>
|
|
|
|
|
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<String, String> GetOtherConfig(IXenObject o)
|
|
|
|
|
{
|
|
|
|
|
return o.Get("other_config") as Dictionary<String, String>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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<String, String> GetGuiConfig(IXenObject o)
|
|
|
|
|
{
|
|
|
|
|
return o.Get("gui_config") as Dictionary<String, String>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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]+)$");
|
2018-04-23 15:29:44 +02:00
|
|
|
|
public static Regex CpuAvgFreqRegex = new Regex("^CPU([0-9]+)-avg-freq$");
|
2013-06-24 13:41:48 +02:00
|
|
|
|
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)$");
|
2015-04-23 18:38:40 +02:00
|
|
|
|
static Regex DiskThroughputRegex = new Regex("^vbd_((xvd|hd)[a-z]+)_io_throughput_(read|write|total)$");
|
2013-06-24 13:41:48 +02:00
|
|
|
|
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})$");
|
2016-10-26 17:03:12 +02:00
|
|
|
|
static Regex SrReadWriteRegex = new Regex("^((read|write)(_latency)?)_([a-f0-9]{8})$");
|
2013-11-14 12:33:59 +01:00
|
|
|
|
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])$");
|
2013-06-24 13:41:48 +02:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2018-04-23 15:29:44 +02:00
|
|
|
|
m = CpuAvgFreqRegex.Match(name);
|
|
|
|
|
if (m.Success)
|
|
|
|
|
return FormatFriendly("Label-performance.cpu-avg-freq", m.Groups[1].Value);
|
|
|
|
|
|
2013-06-24 13:41:48 +02:00
|
|
|
|
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}",
|
2017-09-03 04:33:29 +02:00
|
|
|
|
m.Groups[2].Value, m.Groups[3].Value), network.Name());
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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}",
|
2017-09-03 04:33:29 +02:00
|
|
|
|
m.Groups[3].Value, m.Groups[4].Value), network.Name());
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.
|
2017-09-03 04:33:29 +02:00
|
|
|
|
: FormatFriendly(string.Format("Label-performance.xenbr_{0}{1}", m.Groups[2].Value, m.Groups[3].Value), network.Name());
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.
|
2017-09-03 04:33:29 +02:00
|
|
|
|
: FormatFriendly(string.Format("Label-performance.bond_{0}{1}", m.Groups[2].Value, m.Groups[3].Value), pif.Name());
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-23 18:38:40 +02:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2013-06-24 13:41:48 +02:00
|
|
|
|
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),
|
2017-09-03 04:33:29 +02:00
|
|
|
|
sr.Name().Ellipsise(30));
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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),
|
2017-09-03 04:33:29 +02:00
|
|
|
|
sr.Name().Ellipsise(30));
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-26 17:03:12 +02:00
|
|
|
|
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),
|
2017-09-03 04:33:29 +02:00
|
|
|
|
sr.Name().Ellipsise(30));
|
2016-10-26 17:03:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
2013-11-14 12:33:59 +01:00
|
|
|
|
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),
|
2017-09-03 04:33:29 +02:00
|
|
|
|
gpu.Name(), pciId);
|
2013-11-14 12:33:59 +01:00
|
|
|
|
}
|
|
|
|
|
|
2013-06-24 13:41:48 +02:00
|
|
|
|
if (NetworkLatencyRegex.IsMatch(name))
|
|
|
|
|
return PropertyManager.GetFriendlyName("Label-performance.network_latency");
|
|
|
|
|
|
|
|
|
|
if (XapiLatencyRegex.IsMatch(name))
|
|
|
|
|
return PropertyManager.GetFriendlyName("Label-performance.xapi_latency");
|
|
|
|
|
|
|
|
|
|
if (StatefileLatencyRegex.IsMatch(name))
|
|
|
|
|
return PropertyManager.GetFriendlyName("Label-performance.statefile_latency");
|
|
|
|
|
|
|
|
|
|
if (LoadAvgRegex.IsMatch(name))
|
|
|
|
|
return PropertyManager.GetFriendlyName("Label-performance.loadavg");
|
|
|
|
|
|
|
|
|
|
return PropertyManager.GetFriendlyName(string.Format("Label-performance.{0}", name));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Lookup key using PropertyManager.GetFriendlyName, and then apply that as a format pattern to the given args.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static string FormatFriendly(string key, params string[] args)
|
|
|
|
|
{
|
|
|
|
|
return string.Format(PropertyManager.GetFriendlyName(key), args);
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-14 12:33:59 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2013-06-24 13:41:48 +02:00
|
|
|
|
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)
|
|
|
|
|
{
|
2017-09-03 04:33:29 +02:00
|
|
|
|
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))
|
2013-06-24 13:41:48 +02:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 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.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="s"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static string FirstLine(string s)
|
|
|
|
|
{
|
|
|
|
|
if (s == null)
|
|
|
|
|
{
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
s = s.Split(new char[] { '\n' })[0];
|
|
|
|
|
return s.Split(new char[] { '\r' })[0];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns empty string if mainline
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static string OEMName(Host host)
|
|
|
|
|
{
|
2016-06-13 14:12:19 +02:00
|
|
|
|
if (host.software_version == null)
|
|
|
|
|
return string.Empty;
|
|
|
|
|
|
2013-06-24 13:41:48 +02:00
|
|
|
|
if (!host.software_version.ContainsKey("oem_manufacturer"))
|
|
|
|
|
return "";
|
|
|
|
|
|
|
|
|
|
return host.software_version["oem_manufacturer"].ToLowerInvariant();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string HostProductVersionWithOEM(Host host)
|
|
|
|
|
{
|
|
|
|
|
string oem = OEMName(host);
|
|
|
|
|
if (string.IsNullOrEmpty(oem))
|
|
|
|
|
return HostProductVersion(host);
|
|
|
|
|
else
|
|
|
|
|
return string.Format("{0}.{1}", HostProductVersion(host), OEMName(host));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns "type 'name'" for the specified object, e.g. "pool 'foo'".
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="XenObject"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static string GetNameAndObject(IXenObject XenObject)
|
|
|
|
|
{
|
|
|
|
|
if (XenObject is Pool)
|
2016-07-04 12:58:59 +02:00
|
|
|
|
return string.Format(Messages.POOL_X, GetName(XenObject));
|
2013-06-24 13:41:48 +02:00
|
|
|
|
|
|
|
|
|
if (XenObject is Host)
|
2016-07-04 12:58:59 +02:00
|
|
|
|
return string.Format(Messages.SERVER_X, GetName(XenObject));
|
2013-06-24 13:41:48 +02:00
|
|
|
|
|
2017-09-03 04:33:29 +02:00
|
|
|
|
VM vm = XenObject as VM;
|
|
|
|
|
if (vm != null)
|
2013-06-24 13:41:48 +02:00
|
|
|
|
{
|
2017-09-03 04:33:29 +02:00
|
|
|
|
if (vm.IsControlDomainZero())
|
2016-07-04 12:58:59 +02:00
|
|
|
|
return string.Format(Messages.SERVER_X, GetName(XenObject.Connection.Resolve(vm.resident_on)));
|
2013-06-24 13:41:48 +02:00
|
|
|
|
else
|
2016-07-04 12:58:59 +02:00
|
|
|
|
return string.Format(Messages.VM_X, GetName(XenObject));
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (XenObject is SR)
|
2016-07-04 12:58:59 +02:00
|
|
|
|
return string.Format(Messages.STORAGE_REPOSITORY_X, GetName(XenObject));
|
2013-06-24 13:41:48 +02:00
|
|
|
|
|
|
|
|
|
return Messages.UNKNOWN_OBJECT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the i18n'd name for a HA restart priority according to FriendlyNames.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="priority"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static string RestartPriorityI18n(VM.HA_Restart_Priority? priority)
|
|
|
|
|
{
|
|
|
|
|
if (priority == null)
|
|
|
|
|
{
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return XenAdmin.Core.PropertyManager.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 XenAdmin.Core.PropertyManager.GetFriendlyName("Description-VM.ha_restart_priority." + priority.ToString()) ?? priority.ToString();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Builds up a dictionary of the current restart priorities for all the VMs in the given IXenConnection.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="connection">Must not be null.</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static Dictionary<VM, VM.HA_Restart_Priority> GetVmHaRestartPriorities(IXenConnection connection,bool showHiddenVMs)
|
|
|
|
|
{
|
|
|
|
|
Dictionary<VM, VM.HA_Restart_Priority> result = new Dictionary<VM, VM.HA_Restart_Priority>();
|
|
|
|
|
foreach (VM vm in connection.Cache.VMs)
|
|
|
|
|
{
|
|
|
|
|
if (vm.HaCanProtect(showHiddenVMs))
|
|
|
|
|
{
|
2017-09-05 03:15:38 +02:00
|
|
|
|
result[vm] = vm.HARestartPriority();
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Converts Dictionary<XenObject<VM>, VM.HA_Restart_Priority> -> Dictionary<XenRef<VM>, string>.
|
|
|
|
|
/// The former is useful in the GUI, the latter is suitable for sending into compute_hypothetical_max.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="settings">Must not be null.</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static Dictionary<XenRef<VM>, string> GetVmHaRestartPrioritiesForApi(Dictionary<VM, VM.HA_Restart_Priority> settings)
|
|
|
|
|
{
|
|
|
|
|
Dictionary<XenRef<VM>, string> result = new Dictionary<XenRef<VM>, 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>(vm.opaque_ref)] = VM.PriorityToString(settings[vm]);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Builds up a dictionary of the current startup options for all the VMs in the given IXenConnection.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="connection">Must not be null.</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static Dictionary<VM, VMStartupOptions> GetVmStartupOptions(IXenConnection connection, bool showHiddenVMs)
|
|
|
|
|
{
|
|
|
|
|
Dictionary<VM, VMStartupOptions> result = new Dictionary<VM, VMStartupOptions>();
|
|
|
|
|
foreach (VM vm in connection.Cache.VMs)
|
|
|
|
|
{
|
|
|
|
|
if (vm.HaCanProtect(showHiddenVMs))
|
|
|
|
|
{
|
2017-09-05 03:15:38 +02:00
|
|
|
|
result[vm] = new VMStartupOptions(vm.order, vm.start_delay, vm.HARestartPriority());
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves a IXenObject from a message. May return null if type not recognised.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="message"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static IXenObject XenObjectFromMessage(XenAPI.Message message)
|
|
|
|
|
{
|
|
|
|
|
switch (message.cls)
|
|
|
|
|
{
|
|
|
|
|
case cls.Pool:
|
|
|
|
|
Pool pool = message.Connection.Cache.Find_By_Uuid<Pool>(message.obj_uuid);
|
|
|
|
|
if (pool != null)
|
|
|
|
|
return pool;
|
|
|
|
|
break;
|
|
|
|
|
case cls.Host:
|
|
|
|
|
Host host = message.Connection.Cache.Find_By_Uuid<Host>(message.obj_uuid);
|
|
|
|
|
if (host != null)
|
|
|
|
|
return host;
|
|
|
|
|
break;
|
|
|
|
|
case cls.VM:
|
|
|
|
|
VM vm = message.Connection.Cache.Find_By_Uuid<VM>(message.obj_uuid);
|
|
|
|
|
if (vm != null)
|
|
|
|
|
return vm;
|
|
|
|
|
break;
|
|
|
|
|
case cls.SR:
|
|
|
|
|
SR sr = message.Connection.Cache.Find_By_Uuid<SR>(message.obj_uuid);
|
|
|
|
|
if (sr != null)
|
|
|
|
|
return sr;
|
|
|
|
|
break;
|
2017-01-10 07:18:18 +01:00
|
|
|
|
case cls.VMSS:
|
|
|
|
|
VMSS vmss = message.Connection.Cache.Find_By_Uuid<VMSS>(message.obj_uuid);
|
|
|
|
|
if (vmss != null)
|
|
|
|
|
return vmss;
|
2017-01-25 03:26:51 +01:00
|
|
|
|
break;
|
CP-16922: Implement GUI for enabling PVS read caching (#1179)
* CP-16922: UI changes to PvsPage to replace the single configure button with a separate enable and disable one, per the design doc
* CP-16922: Intermediate checkin - command + dialog implemented, but PVS page has some layout problems.
* CP-16922: Implement GUI for enabling PVS read caching and marking/unmarking VMs for prepopulation
Fix the broken layout by reverting MainWindow.resx (which shouldn't have changed) back to the previous commit. Also removed duplicate properties button code.
* CP-16922: PVS Page changes, to properly display all VMs (not just ones with proxy), only populating proxy columns if there is a proxy set.
* CP-16922: Use a CommandButton for the enable button, connected to the same command as the VM main menu button, and using the table selection as its selection manager.
* CP-16922: Delete the old enable button click behaviour (because it now has a proper command), was causing the dialog to show up twice
* CP-16922: Properly list VMs by hiding those that should be hidden (in addition to non-VMs, also hide the __gui__ prefix and others covered by Show function). Also fixes a bug in NewVMGroupVMsPage discovered when fixing this - it would show VMs that should be hidden if you used the search bar.
* CP-16922: Use ButtonText on the Command to set the text on the PVS enable button
* CP-16922: Add the disable PVS read-caching command, and its associated proxy destroy action. Both based off enable equivalents (main difference is no dialog for now). Not used yet but will be in main window menu + disable button
* CP-16922: Add disable PVS read-caching to the main window menu (under enable), with the disable command attached to it
* CP-16922: Add support for disabling PVS read-caching for selected hosts via the PVS tab page by converting the disable button into a CommandButton in the same way as the enable button, using its own command + selection manager (because Selection Managers only bind to one control)
* CP-16922: Design - adding the "read caching enabled" column from the design, so that we display both whether caching is enabled and whether the VM is currently cached. If it's disabled all other values are missing.
* CP-16922: Add the PVS site column per the design to show the site a VM's PVS Proxy is on if it has a proxy
* CP-16922: Add an event handler for VMs changing so that newly provisioned VMs immediately show up in the PVS tab page
* CP-16922: Support for properly remembering the selection when the VM list is refreshed. A new method returns the list of VMs for the selected rows, and we can then check if the VM for the row being added is in this list. If there was no previous selection then we still select the first row by default.
* CP-16922: Remove prepopulate, which is being removed from the design. The next XAPI version will remove it, so just pass False as prepopulate for now, and remove the option from the dialog and PVS tab page.
* CP-16922: Improve UI appearance for a missing Storage Repository. Per the design, if there is no SR show the message None (memory only) instead of an empty cell
* CP-16922: Improved sorting behaviour. All columns (except VM name, which always uses it) use the VM tag as a sorting fallback (since they all have duplicates). New default sort which sorts by caching enabled first (yes before no), and then VM tag as a tiebreaker. Support for remembering the current sort on refresh, ie when a value changes.
* CP-16922: Refactoring to use a VM extension method to get the PVSProxy (or null if none exists) for a VM. This was used in the PVS Page + commands which are now much simpler.
* CP-16922: Move where the SelectionChanged event handler is set when refreshing the PVS table, so that the command buttons are updated correctly.
* CP-16922: Improvements to PVS read-caching dialog. Set accept/cancel buttons properly + adjust tab values, so focus acts as expected
* CP-16922: Remove the images from the menu for Enable/Disable PVS read-caching, most commands in the menu don't have one
* CP-16922: Sorting out the strings for the actions + ParallelAction calls, and moving them into the resx. Also reverting wrong MainWindow.resx changes.
* CAR-2184: XAPI changes. I've made the minimal possible changes to PvsPage to ensure it compiles, but it's had a lot of feature work (CP-16922) that supersedes this, these small changes should keep that merge painless.
* CAR-2184 XAPI: Reverse the unrelated changes, only keep the PVS ones.
* CP-16922: Merge latest XAPI changes, notably changes to Pvs_site.create
* CP-16922: Change tab show/hide logic for PVS tab, show it even if there are no PVS sites.
* CP-16922: Implement GUI for enabling PVS read caching
Design changes - remove the storage repo column and replace it with status, which is populated with a friendly version of the pvs_proxy_status enum. Also populate site list using site.name_label
Signed-off-by: Callum McIntyre <callumiandavid.mcintyre@citrix.com>
* CP-16922: Implement GUI for enabling PVS read caching
Adjusting the CanExecute of Enable PVS read-caching command to require at least one PVS site to be created. Since the PVS tab can show up without any sites, we need this because we can't create a proxy without a site.
* CP-16922: Code review changes.
Tidied up messages, and ensured actions run async properly, and without exiting before their task.
* CP-16922: Code review changes.
Update dialog resx, wider combobox on dialog, update action descriptions before setting RelatedTask, add friendly message body for PVS error notifications.
2016-09-22 16:08:15 +02:00
|
|
|
|
case cls.PVS_proxy:
|
|
|
|
|
PVS_proxy proxy = message.Connection.Cache.Find_By_Uuid<PVS_proxy>(message.obj_uuid);
|
|
|
|
|
if (proxy != null)
|
|
|
|
|
return proxy;
|
2017-01-25 03:26:51 +01:00
|
|
|
|
break;
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static int Max(params int[] arr)
|
|
|
|
|
{
|
|
|
|
|
int result = int.MinValue;
|
|
|
|
|
foreach (int i in arr)
|
|
|
|
|
{
|
|
|
|
|
if (i > result)
|
|
|
|
|
result = i;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Load an xml stream and ignore comments and whitespace
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="xmlStream"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves a true of false value from an XML attribute. Returns null-bool if the attribute doesnt exist or the
|
|
|
|
|
/// value is malformed.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="Node"></param>
|
|
|
|
|
/// <param name="AttributeName"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves a true of false value from an XML attribute. Returns Default if the attribute doesnt exist or the
|
|
|
|
|
/// value is malformed.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="Node"></param>
|
|
|
|
|
/// <param name="AttributeName"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static bool GetBoolXmlAttribute(XmlNode Node, string AttributeName, bool Default)
|
|
|
|
|
{
|
|
|
|
|
bool? b = GetBoolXmlAttribute(Node, AttributeName);
|
|
|
|
|
if (!b.HasValue)
|
|
|
|
|
return Default;
|
|
|
|
|
|
|
|
|
|
return b.Value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves a float value from an XML attribute. Defaults to null-float if the attribute doesnt exist or the
|
|
|
|
|
/// value is malformed.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="Node"></param>
|
|
|
|
|
/// <param name="AttributeName"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves a float value from an XML attribute. Returns Default if the attribute doesnt exist or the
|
|
|
|
|
/// value is malformed.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="Node"></param>
|
|
|
|
|
/// <param name="AttributeName"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static float GetFloatXmlAttribute(XmlNode Node, string AttributeName, float Default)
|
|
|
|
|
{
|
|
|
|
|
float? f = GetFloatXmlAttribute(Node, AttributeName);
|
|
|
|
|
if (!f.HasValue)
|
|
|
|
|
return Default;
|
|
|
|
|
|
|
|
|
|
return f.Value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves an int value from an XML attribute. Defaults to null-int if the attribute doesnt exist or the
|
|
|
|
|
/// value is malformed.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="Node"></param>
|
|
|
|
|
/// <param name="AttributeName"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves an int value from an XML attribute. Returns Default if the attribute doesnt exist or the
|
|
|
|
|
/// value is malformed.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="Node"></param>
|
|
|
|
|
/// <param name="AttributeName"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static int GetIntXmlAttribute(XmlNode Node, string AttributeName, int Default)
|
|
|
|
|
{
|
|
|
|
|
int? i = GetIntXmlAttribute(Node, AttributeName);
|
|
|
|
|
if (!i.HasValue)
|
|
|
|
|
return Default;
|
|
|
|
|
|
|
|
|
|
return i.Value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves the string content of an XmlNode attribute or throws an I18NException if it missing.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="Node"></param>
|
|
|
|
|
/// <param name="Attribute"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 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).
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="Node"></param>
|
|
|
|
|
/// <param name="Attribute"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static T GetEnumXmlAttribute<T>(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<string> pairs = new List<string>();
|
|
|
|
|
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 object GetListOfNames(List<Host> list)
|
|
|
|
|
{
|
|
|
|
|
List<string> names = new List<string>();
|
|
|
|
|
foreach (Host obj in list)
|
|
|
|
|
{
|
2017-09-03 04:33:29 +02:00
|
|
|
|
names.Add(obj.Name());
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string.Join(", ", names.ToArray());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static List<VM> VMsRunningOn(List<Host> hosts)
|
|
|
|
|
{
|
|
|
|
|
List<VM> vms = new List<VM>();
|
|
|
|
|
foreach (Host host in hosts)
|
|
|
|
|
{
|
|
|
|
|
vms.AddRange(host.Connection.ResolveAll(host.resident_VMs));
|
|
|
|
|
}
|
|
|
|
|
return vms;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool CompareLists<T>(List<T> l1, List<T> 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<T>(List<T> l1, List<T> l2)
|
|
|
|
|
{
|
|
|
|
|
foreach (T item in l1)
|
|
|
|
|
{
|
|
|
|
|
if (l2.Contains(item))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool CustomWithNoDVD(VM template)
|
|
|
|
|
{
|
2017-09-03 04:33:29 +02:00
|
|
|
|
return template != null && !template.DefaultTemplate() && template.FindVMCDROM() == null;
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Return a string version of a list, in the form "L1, L2, L3 and L4"
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static string StringifyList<T>(List<T> 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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2017-05-05 15:31:43 +02:00
|
|
|
|
/// Does the connection support Link aggregation (LACP) bonds (i.e. on the vSwitch backend)?
|
2013-06-24 13:41:48 +02:00
|
|
|
|
/// </summary>
|
|
|
|
|
public static bool SupportsLinkAggregationBond(IXenConnection connection)
|
|
|
|
|
{
|
|
|
|
|
Host master = GetMaster(connection);
|
2017-09-03 04:33:29 +02:00
|
|
|
|
return master != null && master.vSwitchNetworkBackend();
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Number of alloowed NICs per bond
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static int BondSizeLimit(IXenConnection connection)
|
|
|
|
|
{
|
|
|
|
|
Host master = GetMaster(connection);
|
2017-05-05 15:31:43 +02:00
|
|
|
|
// For hosts on the vSwitch backend, we allow 4 NICs per bond; otherwise, 2
|
2017-09-03 04:33:29 +02:00
|
|
|
|
return master != null && master.vSwitchNetworkBackend() ? 4 : 2;
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Host GetHostAncestor(IXenObject xenObject)
|
|
|
|
|
{
|
|
|
|
|
if (xenObject == null || xenObject.Connection == null)
|
|
|
|
|
return null;
|
|
|
|
|
|
2017-09-03 04:33:29 +02:00
|
|
|
|
var h = xenObject as Host;
|
|
|
|
|
if (h != null)
|
|
|
|
|
return h;
|
|
|
|
|
|
|
|
|
|
var sr = xenObject as SR;
|
|
|
|
|
if (sr != null)
|
|
|
|
|
return sr.Home();
|
2013-06-24 13:41:48 +02:00
|
|
|
|
|
2017-09-03 04:33:29 +02:00
|
|
|
|
var vm = xenObject as VM;
|
|
|
|
|
if (vm != null)
|
2013-06-24 13:41:48 +02:00
|
|
|
|
return vm.Home();
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2013-06-28 16:50:14 +02:00
|
|
|
|
public static bool SameServerVersion(Host host, string longProductVersion)
|
2013-06-24 13:41:48 +02:00
|
|
|
|
{
|
2017-09-03 04:33:29 +02:00
|
|
|
|
return host != null && host.LongProductVersion() == longProductVersion;
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool EnabledTargetExists(Host host, IXenConnection connection)
|
|
|
|
|
{
|
|
|
|
|
if (host != null)
|
|
|
|
|
return host.enabled;
|
|
|
|
|
|
|
|
|
|
return connection.Cache.Hosts.Any(h => h.enabled);
|
|
|
|
|
}
|
2013-12-11 11:45:28 +01:00
|
|
|
|
|
2015-01-19 17:49:47 +01:00
|
|
|
|
public static bool GpuCapability(IXenConnection connection)
|
2013-12-11 11:45:28 +01:00
|
|
|
|
{
|
2015-01-21 11:04:47 +01:00
|
|
|
|
if (FeatureForbidden(connection, Host.RestrictGpu))
|
|
|
|
|
return false;
|
|
|
|
|
var pool = GetPoolOfOne(connection);
|
2017-09-03 04:33:29 +02:00
|
|
|
|
return pool != null && pool.HasGpu();
|
2013-12-11 11:45:28 +01:00
|
|
|
|
}
|
2014-05-20 13:38:03 +02:00
|
|
|
|
|
2015-01-21 11:04:47 +01:00
|
|
|
|
public static bool VGpuCapability(IXenConnection connection)
|
|
|
|
|
{
|
|
|
|
|
if (FeatureForbidden(connection, Host.RestrictVgpu))
|
|
|
|
|
return false;
|
|
|
|
|
var pool = GetPoolOfOne(connection);
|
2017-09-03 04:33:29 +02:00
|
|
|
|
return pool != null && pool.HasVGpu();
|
2015-01-21 11:04:47 +01:00
|
|
|
|
}
|
2014-05-20 13:38:03 +02:00
|
|
|
|
|
2015-01-21 11:04:47 +01:00
|
|
|
|
/// <summary>
|
2014-05-20 13:38:03 +02:00
|
|
|
|
/// Whether creation of VLAN 0 is allowed.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static bool VLAN0Allowed(IXenConnection connection)
|
|
|
|
|
{
|
|
|
|
|
Host master = GetMaster(connection);
|
|
|
|
|
// For Creedence or later on the vSwitch backend, we allow creation of VLAN 0
|
2017-09-03 04:33:29 +02:00
|
|
|
|
return master != null && CreedenceOrGreater(master) && master.vSwitchNetworkBackend();
|
2014-05-20 13:38:03 +02:00
|
|
|
|
}
|
2015-02-23 12:01:39 +01:00
|
|
|
|
|
|
|
|
|
public static bool ContainerCapability(IXenConnection connection)
|
|
|
|
|
{
|
|
|
|
|
var master = GetMaster(connection);
|
2016-10-28 13:06:44 +02:00
|
|
|
|
if (master == null)
|
|
|
|
|
return false;
|
|
|
|
|
if (ElyOrGreater(connection))
|
2017-09-03 04:33:29 +02:00
|
|
|
|
return master.AppliedUpdates().Any(update => update.Name().ToLower().StartsWith("xscontainer"));
|
|
|
|
|
return CreamOrGreater(connection) && master.SuppPacks().Any(suppPack => suppPack.Name.ToLower().StartsWith("xscontainer"));
|
2015-02-23 12:01:39 +01:00
|
|
|
|
}
|
2015-10-06 15:04:36 +02:00
|
|
|
|
|
2016-09-22 16:58:44 +02:00
|
|
|
|
public static bool PvsCacheCapability(IXenConnection connection)
|
|
|
|
|
{
|
|
|
|
|
var master = GetMaster(connection);
|
2017-09-03 04:33:29 +02:00
|
|
|
|
return master != null && master.AppliedUpdates().Any(update => update.Name().ToLower().StartsWith("pvsaccelerator"));
|
2015-02-23 12:01:39 +01:00
|
|
|
|
}
|
2015-10-06 15:04:36 +02:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// This method returns the disk space required (bytes) on the provided SR for the provided VDI.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>Disk size required in bytes.</returns>
|
|
|
|
|
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;
|
|
|
|
|
}
|
2015-11-23 15:47:28 +01:00
|
|
|
|
|
|
|
|
|
public static string UrlEncode(this string str)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrEmpty(str))
|
|
|
|
|
return str;
|
|
|
|
|
|
|
|
|
|
return System.Net.WebUtility.UrlEncode(str);
|
|
|
|
|
}
|
2017-08-08 13:25:18 +02:00
|
|
|
|
|
|
|
|
|
public static string UpdatesFriendlyName(string propertyName)
|
|
|
|
|
{
|
|
|
|
|
return Core.PropertyManager.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<string> HostAppliedPatchesList(Host host)
|
|
|
|
|
{
|
|
|
|
|
List<string> result = new List<string>();
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2013-06-24 13:41:48 +02:00
|
|
|
|
}
|
|
|
|
|
}
|