mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-11-23 20:36:33 +01:00
1653 lines
59 KiB
C#
1653 lines
59 KiB
C#
/* Copyright (c) Cloud Software Group, Inc.
|
|
*
|
|
* Redistribution and use in source and binary forms,
|
|
* with or without modification, are permitted provided
|
|
* that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using XenAdmin;
|
|
using XenAdmin.Core;
|
|
using System.Diagnostics;
|
|
using System.Web.Script.Serialization;
|
|
|
|
|
|
namespace XenAPI
|
|
{
|
|
public partial class Host : IComparable<Host>, IEquatable<Host>
|
|
{
|
|
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod()?.DeclaringType);
|
|
|
|
public enum Edition
|
|
{
|
|
Free,
|
|
PerSocket, //Added in Clearwater (PR-1589)
|
|
XenDesktop, //Added in Clearwater (PR-1589) and is new form of "EnterpriseXD"
|
|
StandardPerSocket, // Added in Creedence (standard-per-socket)
|
|
Desktop, // Added in Creedence (desktop)
|
|
Standard, // Added in Dundee/Violet (standard)
|
|
EnterprisePerSocket, // Added in Creedence (enterprise-per-socket)
|
|
EnterprisePerUser, // Added in Creedence (enterprise-per-user)
|
|
DesktopPlus, // Added in Creedence (desktop-plus)
|
|
DesktopCloud, // Added in Jura (desktop-cloud)
|
|
Premium // Added in Indigo (premium)
|
|
}
|
|
|
|
public const string LicenseServerWebConsolePort = "8082";
|
|
|
|
public override string Name()
|
|
{
|
|
return name_label;
|
|
}
|
|
|
|
public static Edition GetEdition(string editionText)
|
|
{
|
|
switch (editionText)
|
|
{
|
|
case "xendesktop":
|
|
return Edition.XenDesktop;
|
|
case "per-socket":
|
|
return Edition.PerSocket;
|
|
case "enterprise-per-socket":
|
|
case "premium-per-socket":
|
|
return Edition.EnterprisePerSocket;
|
|
case "enterprise-per-user":
|
|
case "premium-per-user":
|
|
return Edition.EnterprisePerUser;
|
|
case "standard-per-socket":
|
|
return Edition.StandardPerSocket;
|
|
case "desktop":
|
|
return Edition.Desktop;
|
|
case "desktop-plus":
|
|
return Edition.DesktopPlus;
|
|
case "desktop-cloud":
|
|
return Edition.DesktopCloud;
|
|
case "premium":
|
|
return Edition.Premium;
|
|
case "standard":
|
|
return Edition.Standard;
|
|
case "basic":
|
|
default:
|
|
return Edition.Free;
|
|
}
|
|
}
|
|
|
|
public bool CanSeeNetwork(XenAPI.Network network)
|
|
{
|
|
System.Diagnostics.Trace.Assert(network != null);
|
|
|
|
// Special case for local networks.
|
|
if (network.PIFs.Count == 0)
|
|
return true;
|
|
|
|
foreach (var pifRef in network.PIFs)
|
|
{
|
|
PIF pif = network.Connection.Resolve(pifRef);
|
|
if (pif != null && pif.host != null && pif.host.opaque_ref == opaque_ref)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public string GetEditionText(Edition edition)
|
|
{
|
|
switch (edition)
|
|
{
|
|
case Edition.XenDesktop:
|
|
return "xendesktop";
|
|
case Edition.PerSocket:
|
|
return "per-socket";
|
|
case Edition.EnterprisePerSocket:
|
|
return Helpers.NaplesOrGreater(this) ? "premium-per-socket" : "enterprise-per-socket";
|
|
case Edition.EnterprisePerUser:
|
|
return Helpers.NaplesOrGreater(this) ? "premium-per-user" : "enterprise-per-user";
|
|
case Edition.StandardPerSocket:
|
|
return "standard-per-socket";
|
|
case Edition.Desktop:
|
|
return "desktop";
|
|
case Edition.DesktopPlus:
|
|
return "desktop-plus";
|
|
case Edition.DesktopCloud:
|
|
return "desktop-cloud";
|
|
case Edition.Premium:
|
|
return "premium";
|
|
case Edition.Standard:
|
|
return "standard";
|
|
default:
|
|
// CP-43000: For some hosts "trial" works, too. However, "express" is valid from Naples onwards
|
|
return Helpers.NaplesOrGreater(this) ? "express" : "free";
|
|
}
|
|
}
|
|
|
|
public string GetIscsiIqn()
|
|
{
|
|
if (Helpers.KolkataOrGreater(this))
|
|
{
|
|
return iscsi_iqn;
|
|
}
|
|
return Get(other_config, "iscsi_iqn") ?? "";
|
|
}
|
|
|
|
public void SetIscsiIqn(string value)
|
|
{
|
|
if (Helpers.KolkataOrGreater(this))
|
|
{
|
|
iscsi_iqn = value;
|
|
}
|
|
else
|
|
{
|
|
other_config = SetDictionaryKey(other_config, "iscsi_iqn", value);
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return this.name_label;
|
|
}
|
|
|
|
public override string Description()
|
|
{
|
|
// i18n: CA-30372, CA-207273
|
|
if (name_description == "Default install of XenServer" || name_description == "Default install")
|
|
return string.Format(Messages.DEFAULT_INSTALL_OF_XENSERVER,
|
|
software_version.ContainsKey("product_brand")
|
|
? software_version["product_brand"]
|
|
: BrandManager.ProductBrand);
|
|
|
|
return name_description ?? "";
|
|
}
|
|
|
|
/// <summary>
|
|
/// The expiry date of this host's license in UTC.
|
|
/// Defaults to 2030-01-01 if not found.
|
|
/// </summary>
|
|
public virtual DateTime LicenseExpiryUTC()
|
|
{
|
|
if (license_params != null && license_params.ContainsKey("expiry") &&
|
|
Util.TryParseIso8601DateTime(license_params["expiry"], out var result))
|
|
return result;
|
|
return new DateTime(2030, 1, 1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The CSS expiry date of this host's license.
|
|
/// The time component is always set to midnight.
|
|
/// Returns null if the value doesn't exist.
|
|
/// </summary>
|
|
public virtual DateTime? LicenseCssExpiry()
|
|
{
|
|
if(license_params != null &&
|
|
license_params.TryGetValue("css_expiry", out var cssExpiryValue) &&
|
|
!string.IsNullOrEmpty(cssExpiryValue) &&
|
|
Util.TryParseNonIso8601DateTime(cssExpiryValue, out var result))
|
|
{
|
|
// css_expiry is not a datetime object
|
|
return result.Date;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static bool RestrictRBAC(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_rbac");
|
|
}
|
|
|
|
public static bool RestrictDMC(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_dmc");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Added for Clearwater
|
|
/// </summary>
|
|
/// <param name="h"></param>
|
|
/// <returns></returns>
|
|
public static bool RestrictHotfixApply(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_hotfix_apply");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restrict Automated Updates
|
|
/// </summary>
|
|
/// <param name="h">host</param>
|
|
/// <returns></returns>
|
|
public static bool RestrictBatchHotfixApply(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_batch_hotfix_apply");
|
|
}
|
|
|
|
public static bool RestrictCheckpoint(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_checkpoint");
|
|
}
|
|
|
|
public static bool RestrictCifs(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_cifs");
|
|
}
|
|
|
|
public static bool RestrictVendorDevice(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_pci_device_for_auto_update");
|
|
}
|
|
|
|
public static bool RestrictWLB(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_wlb");
|
|
}
|
|
|
|
public static bool RestrictVSwitchController(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_vswitch_controller");
|
|
}
|
|
|
|
public static bool RestrictSriovNetwork(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_network_sriov");
|
|
}
|
|
|
|
public static bool RestrictPooling(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_pooling");
|
|
}
|
|
|
|
public static bool RestrictVMSnapshotSchedule(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_vmss");
|
|
}
|
|
|
|
public static bool RestrictVMAppliances(Host h)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public static bool RestrictDR(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_dr");
|
|
}
|
|
|
|
public static bool RestrictConversion(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_xcm");
|
|
}
|
|
|
|
public static bool RestrictCrossPoolMigrate(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_storage_xen_motion");
|
|
}
|
|
|
|
public static bool RestrictChangedBlockTracking(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_cbt");
|
|
}
|
|
|
|
public virtual bool IsFreeLicense()
|
|
{
|
|
return edition == "free" || edition == "express" || edition == "trial";
|
|
}
|
|
|
|
public virtual bool IsExpired()
|
|
{
|
|
if (Connection != null && Connection.CacheIsPopulated)
|
|
return LicenseExpiryUTC() < DateTime.UtcNow - Connection.ServerTimeOffset;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// True if host qualifies for showing an upselling message based on its license and version.
|
|
/// <br />
|
|
/// Used to decide whether or not to show the upselling message from trial or express edition.
|
|
/// <br />
|
|
/// See CP-43000 for more info.
|
|
/// </summary>
|
|
public virtual bool CanShowTrialEditionUpsell()
|
|
{
|
|
return Helpers.NileOrGreater(this) && IsFreeLicense() && !IsInPreviewRelease();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return true if the is_preview_release value in host.software_version is present and set to true.
|
|
/// </summary>
|
|
public virtual bool IsInPreviewRelease()
|
|
{
|
|
return software_version.TryGetValue("is_preview_release", out var isPreviewReleaseString) &&
|
|
bool.TryParse(isPreviewReleaseString, out var isPreviewRelease) && isPreviewRelease;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the CSS license has expired, regardless of what edition is shown.
|
|
/// <br />
|
|
/// Do not rely on this method for enforcing restrictions as the user can circumvent this method
|
|
/// by updating the system date.
|
|
/// </summary>
|
|
public virtual bool CssLicenseHasExpired()
|
|
{
|
|
var cssExpiry = LicenseCssExpiry();
|
|
|
|
if (cssExpiry.HasValue)
|
|
{
|
|
// User can circumvent this by changing system date
|
|
return DateTime.UtcNow < cssExpiry;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool RestrictHA(Host h)
|
|
{
|
|
return !BoolKey(h.license_params, "enable_xha");
|
|
}
|
|
|
|
public static bool RestrictPoolSecretRotation(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_pool_secret_rotation");
|
|
}
|
|
|
|
public static bool RestrictCertificateVerification(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_certificate_verification");
|
|
}
|
|
|
|
public static bool RestrictAlerts(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_email_alerting");
|
|
}
|
|
|
|
public static bool RestrictStorageChoices(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_netapp");
|
|
}
|
|
|
|
public static bool RestrictPerformanceGraphs(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_historical_performance");
|
|
}
|
|
|
|
public static bool RestrictCpuMasking(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_cpu_masking");
|
|
}
|
|
|
|
public static bool RestrictGpu(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_gpu");
|
|
}
|
|
|
|
public static bool RestrictUsbPassthrough(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_usb_passthrough");
|
|
}
|
|
|
|
public static bool RestrictVgpu(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_vgpu");
|
|
}
|
|
|
|
public static bool RestrictManagementOnVLAN(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_management_on_vlan");
|
|
}
|
|
|
|
public static bool RestrictIntegratedGpuPassthrough(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_integrated_gpu_passthrough");
|
|
}
|
|
|
|
public static bool RestrictExportResourceData(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_export_resource_data");
|
|
}
|
|
|
|
public static bool RestrictIntraPoolMigrate(Host h)
|
|
{
|
|
return BoolKey(h.license_params, "restrict_xen_motion");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Active directory is restricted only if the "restrict_ad" key exists and it is true
|
|
/// </summary>
|
|
public static bool RestrictAD(Host h)
|
|
{
|
|
return BoolKey(h.license_params, "restrict_ad");
|
|
}
|
|
|
|
public static bool RestrictReadCaching(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_read_caching");
|
|
}
|
|
|
|
public static bool RestrictHealthCheck(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_health_check");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Vss feature is restricted only if the "restrict_vss" key exists and it is true
|
|
/// </summary>
|
|
public static bool RestrictVss(Host h)
|
|
{
|
|
return BoolKey(h.license_params, "restrict_vss");
|
|
}
|
|
|
|
public static bool RestrictPoolSize(Host h)
|
|
{
|
|
return BoolKey(h.license_params, "restrict_pool_size");
|
|
}
|
|
|
|
public static bool RestrictPvsCache(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_pvs_proxy");
|
|
}
|
|
|
|
/// <summary>
|
|
/// For Dundee and greater hosts: the feature is restricted only if the "restrict_ssl_legacy_switch" key exists and it is true
|
|
/// For pre-Dundee hosts: the feature is restricted if the "restrict_ssl_legacy_switch" key is absent or it is true
|
|
/// </summary>
|
|
public static bool RestrictSslLegacySwitch(Host h)
|
|
{
|
|
return BoolKey(h.license_params, "restrict_ssl_legacy_switch");
|
|
}
|
|
|
|
public static bool RestrictLivePatching(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_live_patching");
|
|
}
|
|
|
|
public static bool RestrictIGMPSnooping(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_igmp_snooping");
|
|
}
|
|
|
|
public static bool RestrictVcpuHotplug(Host h)
|
|
{
|
|
if (Helpers.ElyOrGreater(h.Connection))
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_set_vcpus_number_live");
|
|
}
|
|
// Pre-Ely hosts:
|
|
// allowed on Premium edition only
|
|
var hostEdition = GetEdition(h.edition);
|
|
if (hostEdition == Edition.Premium)
|
|
{
|
|
return h.LicenseExpiryUTC() < DateTime.UtcNow - h.Connection.ServerTimeOffset; // restrict if the license has expired
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The feature is restricted if the "restrict_rpu" key exists and it is true
|
|
/// or if the key is absent and the host is unlicensed
|
|
/// </summary>
|
|
public static bool RestrictRpu(Host h)
|
|
{
|
|
return h.license_params.ContainsKey("restrict_rpu")
|
|
? BoolKey(h.license_params, "restrict_rpu")
|
|
: h.IsExpired(); // restrict if the license has expired
|
|
}
|
|
|
|
public static bool RestrictCorosync(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_corosync");
|
|
}
|
|
|
|
public static bool RestrictVtpm(Host h)
|
|
{
|
|
return BoolKeyPreferTrue(h.license_params, "restrict_vtpm");
|
|
}
|
|
|
|
#region Experimental Features
|
|
|
|
public static bool CorosyncDisabled(Host h)
|
|
{
|
|
return RestrictCorosync(h) && FeatureDisabled(h, "corosync");
|
|
}
|
|
|
|
public static bool SriovNetworkDisabled(Host h)
|
|
{
|
|
return RestrictSriovNetwork(h) && FeatureDisabled(h, "network_sriov");
|
|
}
|
|
|
|
public static bool UefiBootDisabled(Host h)
|
|
{
|
|
return FeatureDisabled(h, "guefi");
|
|
}
|
|
|
|
public static bool UefiSecureBootDisabled(Host h)
|
|
{
|
|
return FeatureDisabled(h, "guefi-secureboot");
|
|
}
|
|
|
|
public static bool FeatureDisabled(Host h, string featureName)
|
|
{
|
|
foreach (var feature in h.Connection.ResolveAll(h.features))
|
|
{
|
|
if (feature.name_label.Equals(featureName, StringComparison.OrdinalIgnoreCase))
|
|
return !feature.enabled;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
public bool HasPBDTo(SR sr)
|
|
{
|
|
foreach (XenRef<PBD> pbd in PBDs)
|
|
{
|
|
PBD thePBD = sr.Connection.Resolve<PBD>(pbd);
|
|
if (thePBD != null && thePBD.SR.opaque_ref == sr.opaque_ref)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public PBD GetPBDTo(SR sr)
|
|
{
|
|
foreach (XenRef<PBD> pbd in PBDs)
|
|
{
|
|
PBD thePBD = sr.Connection.Resolve<PBD>(pbd);
|
|
if (thePBD != null && thePBD.SR.opaque_ref == sr.opaque_ref)
|
|
{
|
|
return thePBD;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Constants for other-config from CP-329
|
|
public const String MULTIPATH = "multipathing";
|
|
public const String MULTIPATH_HANDLE = "multipathhandle";
|
|
public const String DMP = "dmp";
|
|
|
|
public bool MultipathEnabled()
|
|
{
|
|
if (Helpers.KolkataOrGreater(this))
|
|
{
|
|
return multipathing;
|
|
}
|
|
return BoolKey(other_config, MULTIPATH);
|
|
}
|
|
|
|
public String MultipathHandle()
|
|
{
|
|
return Get(other_config, MULTIPATH_HANDLE);
|
|
}
|
|
|
|
public override int CompareTo(Host other)
|
|
{
|
|
// CA-20865 Sort in the following order:
|
|
// * Coordinators first
|
|
// * Then connected supporters
|
|
// * Then disconnected servers
|
|
// Within each group, in NaturalCompare order
|
|
|
|
bool thisConnected = (Connection.IsConnected && Helpers.GetCoordinator(Connection) != null);
|
|
bool otherConnected = (other.Connection.IsConnected && Helpers.GetCoordinator(other.Connection) != null);
|
|
|
|
if (thisConnected && !otherConnected)
|
|
return -1;
|
|
else if (!thisConnected && otherConnected)
|
|
return 1;
|
|
else if (thisConnected)
|
|
{
|
|
bool thisIsCoordinator = IsCoordinator();
|
|
bool otherIsCoordinator = other.IsCoordinator();
|
|
|
|
if (thisIsCoordinator && !otherIsCoordinator)
|
|
return -1;
|
|
else if (!thisIsCoordinator && otherIsCoordinator)
|
|
return 1;
|
|
}
|
|
|
|
return base.CompareTo(other);
|
|
}
|
|
|
|
public virtual bool IsCoordinator()
|
|
{
|
|
Pool pool = Helpers.GetPoolOfOne(Connection);
|
|
if (pool == null)
|
|
return false;
|
|
|
|
Host coordinator = Connection.Resolve<Host>(pool.master);
|
|
return coordinator != null && coordinator.uuid == this.uuid;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return this host's product version triplet (e.g. 5.6.100), or null if it can't be found.
|
|
/// </summary>
|
|
public virtual string ProductVersion()
|
|
{
|
|
return Get(software_version, "product_version");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return this host's marketing version number (e.g. 5.6 Feature Pack 1),
|
|
/// or ProductVersion (which can still be null) if it can't be found, including pre-Cowley hosts.
|
|
/// </summary>
|
|
public string ProductVersionText()
|
|
{
|
|
string s = Get(software_version, "product_version_text");
|
|
return string.IsNullOrEmpty(s) ? ProductVersion() : s;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return this host's marketing version number in short form (e.g. 5.6 FP1),
|
|
/// or ProductVersion (which can still be null) if it can't be found, including pre-Cowley hosts.
|
|
/// </summary>
|
|
public string ProductVersionTextShort()
|
|
{
|
|
string s = Get(software_version, "product_version_text_short");
|
|
return string.IsNullOrEmpty(s) ? ProductVersion() : s;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return this host's XCP version triplet (e.g. 1.0.50), or null if it can't be found,
|
|
/// including all pre-Tampa hosts.
|
|
/// </summary>
|
|
public virtual string PlatformVersion()
|
|
{
|
|
return Get(software_version, "platform_version");
|
|
}
|
|
|
|
public string GetXapiVersion()
|
|
{
|
|
return Get(software_version, "xapi_build") ?? Get(software_version, "xapi");
|
|
}
|
|
|
|
/// <summary>
|
|
/// For legacy build numbers only (used to be integers + one char at the end)
|
|
/// From Falcon, this property is not used.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Return the build number of this host, or -1 if none can be found. This will often be
|
|
/// 0 or -1 for developer builds, so comparisons should generally treat those numbers as if
|
|
/// they were brand new.
|
|
/// </remarks>
|
|
internal int BuildNumber()
|
|
{
|
|
Debug.Assert(!Helpers.ElyOrGreater(this));
|
|
|
|
string bn = BuildNumberRaw();
|
|
if (bn == null)
|
|
return -1;
|
|
while (bn.Length > 0 && !char.IsDigit(bn[bn.Length - 1]))
|
|
{
|
|
bn = bn.Substring(0, bn.Length - 1);
|
|
}
|
|
int result;
|
|
if (int.TryParse(bn, out result))
|
|
return result;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the exact build_number of this host
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// null if not found
|
|
/// </remarks>
|
|
public virtual string BuildNumberRaw()
|
|
{
|
|
return Get(software_version, "build_number");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return this host's product version and build number (e.g. 5.6.100.72258), or null if product version can't be found.
|
|
/// </summary>
|
|
public virtual string LongProductVersion()
|
|
{
|
|
string productVersion = ProductVersion();
|
|
return productVersion != null ? string.Format("{0}.{1}", productVersion, Helpers.ElyOrGreater(this) ? BuildNumberRaw() : BuildNumber().ToString()) : null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the product_brand of this host, or null if none can be found.
|
|
/// </summary>
|
|
public string ProductBrand()
|
|
{
|
|
return Get(software_version, "product_brand");
|
|
}
|
|
|
|
/// <summary>
|
|
/// The remote syslog target. May return null if not set on the server.
|
|
/// </summary>
|
|
public string GetSysLogDestination()
|
|
{
|
|
return logging != null && logging.ContainsKey("syslog_destination") ? logging["syslog_destination"] : null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set to null to unset
|
|
/// </summary>
|
|
public void SetSysLogDestination(string value)
|
|
{
|
|
logging = SetDictionaryKey(logging, "syslog_destination", value);
|
|
}
|
|
|
|
public virtual List<Pool_patch> AppliedPatches()
|
|
{
|
|
List<Pool_patch> patches = new List<Pool_patch>();
|
|
|
|
foreach (Host_patch hostPatch in Connection.ResolveAll(this.patches))
|
|
{
|
|
Pool_patch patch = Connection.Resolve(hostPatch.pool_patch);
|
|
if (patch != null)
|
|
patches.Add(patch);
|
|
}
|
|
|
|
return patches;
|
|
}
|
|
|
|
public virtual List<Pool_update> AppliedUpdates()
|
|
{
|
|
var updates = new List<Pool_update>();
|
|
|
|
foreach (var hostUpdate in Connection.ResolveAll(this.updates))
|
|
{
|
|
if (hostUpdate != null)
|
|
updates.Add(hostUpdate);
|
|
}
|
|
|
|
return updates;
|
|
}
|
|
|
|
public bool LinuxPackPresent()
|
|
{
|
|
return software_version.ContainsKey("xs:linux");
|
|
}
|
|
|
|
public bool HasCrashDumps()
|
|
{
|
|
return crashdumps != null && crashdumps.Count > 0;
|
|
}
|
|
|
|
public bool IsLive()
|
|
{
|
|
if (Connection == null)
|
|
return false;
|
|
|
|
Host_metrics hm = Connection.Resolve(metrics);
|
|
return hm != null && hm.live;
|
|
}
|
|
|
|
public const string MAINTENANCE_MODE = "MAINTENANCE_MODE";
|
|
|
|
public bool MaintenanceMode()
|
|
{
|
|
return BoolKey(other_config, MAINTENANCE_MODE);
|
|
}
|
|
|
|
private const string BOOT_TIME = "boot_time";
|
|
|
|
public double BootTime()
|
|
{
|
|
if (other_config == null)
|
|
return 0.0;
|
|
|
|
if (!other_config.ContainsKey(BOOT_TIME))
|
|
return 0.0;
|
|
|
|
double bootTime;
|
|
|
|
if (!double.TryParse(other_config[BOOT_TIME], NumberStyles.Number,
|
|
CultureInfo.InvariantCulture, out bootTime))
|
|
return 0.0;
|
|
|
|
return bootTime;
|
|
}
|
|
|
|
public static double BootTime(Session session, string hostOpaqueRef)
|
|
{
|
|
var host = get_record(session, hostOpaqueRef);
|
|
return host.BootTime();
|
|
}
|
|
|
|
public PrettyTimeSpan Uptime()
|
|
{
|
|
double bootTime = BootTime();
|
|
if (bootTime == 0.0)
|
|
return null;
|
|
return new PrettyTimeSpan(DateTime.UtcNow - Util.FromUnixTime(bootTime) - Connection.ServerTimeOffset);
|
|
}
|
|
|
|
private const string AGENT_START_TIME = "agent_start_time";
|
|
|
|
public double AgentStartTime()
|
|
{
|
|
if (other_config == null)
|
|
return 0.0;
|
|
|
|
if (!other_config.ContainsKey(AGENT_START_TIME))
|
|
return 0.0;
|
|
|
|
double agentStartTime;
|
|
|
|
if (!double.TryParse(other_config[AGENT_START_TIME], System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture, out agentStartTime))
|
|
return 0.0;
|
|
|
|
return agentStartTime;
|
|
}
|
|
|
|
public static double AgentStartTime(Session session, string hostOpaqueRef)
|
|
{
|
|
var host = get_record(session, hostOpaqueRef);
|
|
return host.AgentStartTime();
|
|
}
|
|
|
|
public PrettyTimeSpan AgentUptime()
|
|
{
|
|
double startTime = AgentStartTime();
|
|
if (startTime == 0.0)
|
|
return null;
|
|
return new PrettyTimeSpan(DateTime.UtcNow - Util.FromUnixTime(startTime) - Connection.ServerTimeOffset);
|
|
}
|
|
|
|
// Get the path counts from the Multipath Boot From SAN feature (see PR-1034 and CP-1696).
|
|
// Returns true if the Host.other_config contains the multipathed and mpath-boot keys,
|
|
// and the mpath-boot key is parseable. In this case, current and max will contain the result;
|
|
// otherwise they will contain zero.
|
|
public bool GetBootPathCounts(out int current, out int max)
|
|
{
|
|
current = max = 0;
|
|
return (BoolKey(other_config, "multipathed") &&
|
|
other_config.ContainsKey("mpath-boot") &&
|
|
PBD.ParsePathCounts(other_config["mpath-boot"], out current, out max));
|
|
}
|
|
|
|
public bool HasRunningVMs()
|
|
{
|
|
// 2 not 1, because the Control Domain doesn't count
|
|
return resident_VMs != null && resident_VMs.Count >= 2;
|
|
}
|
|
|
|
public List<XenRef<VM>> GetRunningPvVMs()
|
|
{
|
|
var vms = from XenRef<VM> vmref in resident_VMs
|
|
let vm = Connection.Resolve(vmref)
|
|
where vm != null && vm.IsRealVm() && !vm.IsHVM()
|
|
select vmref;
|
|
|
|
return vms.ToList();
|
|
}
|
|
|
|
public List<XenRef<VM>> GetRunningHvmVMs()
|
|
{
|
|
var vms = from XenRef<VM> vmref in resident_VMs
|
|
let vm = Connection.Resolve(vmref)
|
|
where vm != null && vm.IsRealVm() && vm.IsHVM()
|
|
select vmref;
|
|
|
|
return vms.ToList();
|
|
}
|
|
|
|
|
|
public List<XenRef<VM>> GetRunningVMs()
|
|
{
|
|
var vms = from XenRef<VM> vmref in resident_VMs
|
|
let vm = Connection.Resolve(vmref)
|
|
where vm != null && vm.IsRealVm()
|
|
select vmref;
|
|
|
|
return vms.ToList();
|
|
}
|
|
|
|
#region Save Evacuated VMs for later
|
|
|
|
public const String MAINTENANCE_MODE_EVACUATED_VMS_MIGRATED = "MAINTENANCE_MODE_EVACUATED_VMS_MIGRATED";
|
|
public const String MAINTENANCE_MODE_EVACUATED_VMS_SUSPENDED = "MAINTENANCE_MODE_EVACUATED_VMS_SUSPENDED";
|
|
public const String MAINTENANCE_MODE_EVACUATED_VMS_HALTED = "MAINTENANCE_MODE_EVACUATED_VMS_HALTED";
|
|
|
|
/// <summary>
|
|
/// Save the list of VMs on this host, so we can try and put them back when finished.
|
|
/// This may get run multiple times, after which some vms will have been suspended / shutdown.
|
|
/// </summary>
|
|
/// <param name="session">Pass in the session you want to use for the other config writing</param>
|
|
public void SaveEvacuatedVMs(Session session)
|
|
{
|
|
//Program.AssertOffEventThread();
|
|
|
|
XenRef<Host> opaque_ref = get_by_uuid(session, uuid);
|
|
|
|
List<VM> migratedVMs = GetVMs(MAINTENANCE_MODE_EVACUATED_VMS_MIGRATED);
|
|
List<VM> suspendedVMs = GetVMs(MAINTENANCE_MODE_EVACUATED_VMS_SUSPENDED);
|
|
List<VM> haltedVMs = GetVMs(MAINTENANCE_MODE_EVACUATED_VMS_HALTED);
|
|
|
|
List<VM> allVMs = new List<VM>();
|
|
allVMs.AddRange(migratedVMs);
|
|
allVMs.AddRange(suspendedVMs);
|
|
allVMs.AddRange(haltedVMs);
|
|
|
|
// First time round there will be no saved VMs,
|
|
// (or less saved VMs than currently resident)
|
|
// so just save them all as migrated
|
|
// don't forget the control domain
|
|
|
|
if (allVMs.Count < resident_VMs.Count - 1)
|
|
{
|
|
SaveVMList(session, opaque_ref, MAINTENANCE_MODE_EVACUATED_VMS_MIGRATED,
|
|
Connection.ResolveAll(resident_VMs));
|
|
return;
|
|
}
|
|
|
|
// We've been round once, so just make sure all the vms are in the correct list
|
|
// and then save the lists again
|
|
|
|
migratedVMs.Clear();
|
|
suspendedVMs.Clear();
|
|
haltedVMs.Clear();
|
|
|
|
foreach (VM vm in allVMs)
|
|
{
|
|
switch (vm.power_state)
|
|
{
|
|
case vm_power_state.Halted:
|
|
haltedVMs.Add(vm);
|
|
break;
|
|
|
|
case vm_power_state.Running:
|
|
migratedVMs.Add(vm);
|
|
break;
|
|
|
|
case vm_power_state.Suspended:
|
|
suspendedVMs.Add(vm);
|
|
break;
|
|
}
|
|
}
|
|
|
|
SaveVMList(session, opaque_ref, MAINTENANCE_MODE_EVACUATED_VMS_MIGRATED, migratedVMs);
|
|
SaveVMList(session, opaque_ref, MAINTENANCE_MODE_EVACUATED_VMS_HALTED, haltedVMs);
|
|
SaveVMList(session, opaque_ref, MAINTENANCE_MODE_EVACUATED_VMS_SUSPENDED, suspendedVMs);
|
|
}
|
|
|
|
private static void SaveVMList(Session session, String serverOpaqueRef, String key, List<VM> vms)
|
|
{
|
|
//Program.AssertOffEventThread();
|
|
|
|
List<String> vmUUIDs = new List<String>();
|
|
foreach (VM vm in vms)
|
|
{
|
|
if (vm.is_control_domain)
|
|
continue;
|
|
|
|
vmUUIDs.Add(vm.uuid);
|
|
}
|
|
|
|
Host.remove_from_other_config(session, serverOpaqueRef, key);
|
|
Host.add_to_other_config(session, serverOpaqueRef, key, String.Join(",", vmUUIDs.ToArray()));
|
|
}
|
|
|
|
private List<VM> GetVMs(String key)
|
|
{
|
|
List<VM> vms = new List<VM>();
|
|
|
|
if (other_config == null || !other_config.ContainsKey(key))
|
|
return vms;
|
|
|
|
String vmUUIDs = other_config[key];
|
|
if (String.IsNullOrEmpty(vmUUIDs))
|
|
return vms;
|
|
|
|
foreach (String vmUUID in vmUUIDs.Split(new char[] { ',' }))
|
|
foreach (VM vm in Connection.Cache.VMs)
|
|
if (vm.uuid == vmUUID)
|
|
{
|
|
if (!vms.Contains(vm))
|
|
vms.Add(vm);
|
|
|
|
break;
|
|
}
|
|
|
|
return vms;
|
|
}
|
|
|
|
public void ClearEvacuatedVMs(Session session)
|
|
{
|
|
var hostRef = get_by_uuid(session, uuid);
|
|
ClearEvacuatedVMs(session, hostRef);
|
|
}
|
|
|
|
public static void ClearEvacuatedVMs(Session session, XenRef<Host> hostRef)
|
|
{
|
|
remove_from_other_config(session, hostRef, MAINTENANCE_MODE_EVACUATED_VMS_MIGRATED);
|
|
remove_from_other_config(session, hostRef, MAINTENANCE_MODE_EVACUATED_VMS_HALTED);
|
|
remove_from_other_config(session, hostRef, MAINTENANCE_MODE_EVACUATED_VMS_SUSPENDED);
|
|
}
|
|
|
|
public List<VM> GetMigratedEvacuatedVMs()
|
|
{
|
|
return GetEvacuatedVMs(MAINTENANCE_MODE_EVACUATED_VMS_MIGRATED, vm_power_state.Running);
|
|
}
|
|
|
|
public List<VM> GetSuspendedEvacuatedVMs()
|
|
{
|
|
return GetEvacuatedVMs(MAINTENANCE_MODE_EVACUATED_VMS_SUSPENDED, vm_power_state.Suspended);
|
|
}
|
|
|
|
public List<VM> GetHaltedEvacuatedVMs()
|
|
{
|
|
return GetEvacuatedVMs(MAINTENANCE_MODE_EVACUATED_VMS_HALTED, vm_power_state.Halted);
|
|
}
|
|
|
|
private List<VM> GetEvacuatedVMs(String key, vm_power_state expectedPowerState)
|
|
{
|
|
List<VM> vms = GetVMs(key);
|
|
foreach (VM vm in vms.ToArray())
|
|
if (vm.power_state != expectedPowerState)
|
|
vms.Remove(vm);
|
|
|
|
return vms;
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Will return null if cannot find connection or any control domain in list of vms
|
|
/// </summary>
|
|
public VM ControlDomainZero()
|
|
{
|
|
if (Connection == null)
|
|
return null;
|
|
|
|
if (!Helper.IsNullOrEmptyOpaqueRef(control_domain))
|
|
return Connection.Resolve(control_domain);
|
|
|
|
var vms = Connection.ResolveAll(resident_VMs);
|
|
return vms.FirstOrDefault(vm => vm.is_control_domain && vm.domid == 0);
|
|
}
|
|
|
|
public IEnumerable<VM> OtherControlDomains()
|
|
{
|
|
if (Connection == null)
|
|
return null;
|
|
|
|
var vms = Connection.ResolveAll(resident_VMs);
|
|
|
|
if (!Helper.IsNullOrEmptyOpaqueRef(control_domain))
|
|
return vms.Where(v => v.is_control_domain && v.opaque_ref != control_domain);
|
|
|
|
return vms.Where(v => v.is_control_domain && v.domid != 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Interpret a value from the software_version dictionary as a int, or 0 if we couldn't parse it.
|
|
/// </summary>
|
|
private int GetSVAsInt(string key)
|
|
{
|
|
string s = Get(software_version, key);
|
|
if (s == null)
|
|
return 0;
|
|
return (int)Helper.GetAPIVersion(s);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The xencenter_min as a int, or 0. if we couldn't parse it.
|
|
/// </summary>
|
|
public int XenCenterMin()
|
|
{
|
|
return GetSVAsInt("xencenter_min");
|
|
}
|
|
|
|
/// <summary>
|
|
/// The xencenter_max as a int, or 0 if we couldn't parse it.
|
|
/// </summary>
|
|
public int XenCenterMax()
|
|
{
|
|
return GetSVAsInt("xencenter_max");
|
|
}
|
|
|
|
public string GetDatabaseSchema()
|
|
{
|
|
return Get(software_version, "db_schema");
|
|
}
|
|
|
|
/// <summary>
|
|
/// The amount of memory free on the host. For George and earlier hosts, we use to use
|
|
/// the obvious Host_metrics.memory_free. Since Midnight Ride, however, we use
|
|
/// the same calculation as xapi, adding the used memory and the virtualisation overheads
|
|
/// on each of the VMs. This is a more conservative estimate (i.e., it reports less memory
|
|
/// free), but it's the one we need to make the memory go down to zero when ballooning
|
|
/// takes place.
|
|
/// </summary>
|
|
public long memory_free_calc()
|
|
{
|
|
Host_metrics host_metrics = Connection.Resolve(this.metrics);
|
|
if (host_metrics == null)
|
|
return 0;
|
|
|
|
long used = memory_overhead;
|
|
foreach (VM vm in Connection.ResolveAll(resident_VMs))
|
|
{
|
|
used += vm.memory_overhead;
|
|
VM_metrics vm_metrics = vm.Connection.Resolve(vm.metrics);
|
|
if (vm_metrics != null)
|
|
used += vm_metrics.memory_actual;
|
|
}
|
|
|
|
// This hack is needed because of bug CA-32509. xapi uses a deliberately generous
|
|
// estimate of VM.memory_overhead: but the low-level squeezer code doesn't (and can't)
|
|
// know about the same calculation, and so uses some of this memory_overhead for the
|
|
// VM's memory_actual. This causes up to 1MB of double-counting per VM.
|
|
return ((host_metrics.memory_total > used) ? (host_metrics.memory_total - used) : 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The total of all the dynamic_minimum memories of all resident VMs other than the control domain.
|
|
/// For non-ballonable VMs, we use the static_maximum instead, because the dynamic_minimum has no effect.
|
|
/// </summary>
|
|
public long tot_dyn_min()
|
|
{
|
|
long ans = 0;
|
|
foreach (VM vm in Connection.ResolveAll(resident_VMs))
|
|
{
|
|
if (!vm.is_control_domain)
|
|
ans += vm.SupportsBallooning() ? vm.memory_dynamic_min : vm.memory_static_max;
|
|
}
|
|
return ans;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The total of all the dynamic_maximum memories of all resident VMs other than the control domain.
|
|
/// For non-ballonable VMs, we use the static_maximum instead, because the dynamic_maximum has no effect.
|
|
/// </summary>
|
|
public long tot_dyn_max()
|
|
{
|
|
long ans = 0;
|
|
foreach (VM vm in Connection.ResolveAll(resident_VMs))
|
|
{
|
|
if (!vm.is_control_domain)
|
|
ans += vm.SupportsBallooning() ? vm.memory_dynamic_max : vm.memory_static_max;
|
|
}
|
|
return ans;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The amount of available memory on the host. This is not the same as the amount of free memory, because
|
|
/// it includes the memory that could be freed by reducing balloonable VMs to their dynamic_minimum memory.
|
|
/// </summary>
|
|
public long memory_available_calc()
|
|
{
|
|
Host_metrics host_metrics = Connection.Resolve(this.metrics);
|
|
if (host_metrics == null)
|
|
return 0;
|
|
|
|
long avail = host_metrics.memory_total - tot_dyn_min() - xen_memory_calc();
|
|
if (avail < 0)
|
|
avail = 0; // I don't think this can happen, but I'm nervous about CA-32509: play it safe
|
|
return avail;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The amount of memory used by Xen, including the control domain plus host and VM overheads.
|
|
/// Used to calculate this as total - free - tot_vm_mem, but that caused xen_mem to jump around
|
|
/// during VM startup/shutdown because some changes happen before others.
|
|
/// </summary>
|
|
public long xen_memory_calc()
|
|
{
|
|
long xen_mem = memory_overhead;
|
|
foreach (VM vm in Connection.ResolveAll(resident_VMs))
|
|
{
|
|
xen_mem += vm.memory_overhead;
|
|
if (vm.is_control_domain)
|
|
{
|
|
VM_metrics vmMetrics = vm.Connection.Resolve(vm.metrics);
|
|
if (vmMetrics != null)
|
|
xen_mem += vmMetrics.memory_actual;
|
|
}
|
|
}
|
|
return xen_mem;
|
|
}
|
|
|
|
public long dom0_memory()
|
|
{
|
|
long dom0_mem = 0;
|
|
VM vm = ControlDomainZero();
|
|
if (vm != null)
|
|
{
|
|
VM_metrics vmMetrics = vm.Connection.Resolve(vm.metrics);
|
|
dom0_mem = vmMetrics != null ? vmMetrics.memory_actual : vm.memory_dynamic_min;
|
|
}
|
|
return dom0_mem;
|
|
}
|
|
|
|
public long dom0_memory_extra()
|
|
{
|
|
VM vm = ControlDomainZero();
|
|
return vm != null ? vm.memory_static_max - vm.memory_static_min : 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Friendly string showing memory usage on the host
|
|
/// </summary>
|
|
public string HostMemoryString()
|
|
{
|
|
Host_metrics m = Connection.Resolve(metrics);
|
|
if (m == null)
|
|
return Messages.GENERAL_UNKNOWN;
|
|
|
|
long ServerMBAvail = memory_available_calc();
|
|
long ServerMBTotal = m.memory_total;
|
|
|
|
return string.Format(Messages.GENERAL_MEMORY_SERVER_FREE,
|
|
Util.MemorySizeStringSuitableUnits(ServerMBAvail, true),
|
|
Util.MemorySizeStringSuitableUnits(ServerMBTotal, true));
|
|
}
|
|
|
|
/// <summary>
|
|
/// A friendly string for the XenMemory on this host
|
|
/// </summary>
|
|
public string XenMemoryString()
|
|
{
|
|
if (Connection.Resolve(metrics) == null)
|
|
return Messages.GENERAL_UNKNOWN;
|
|
|
|
return Util.MemorySizeStringSuitableUnits(xen_memory_calc(), true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A friendly string of the resident VM's memory usage, with each entry separated by a line break
|
|
/// </summary>
|
|
public string ResidentVMMemoryUsageString()
|
|
{
|
|
Host_metrics m = Connection.Resolve(metrics);
|
|
|
|
if (m == null)
|
|
return Messages.GENERAL_UNKNOWN;
|
|
else
|
|
{
|
|
List<string> lines = new List<string>();
|
|
|
|
foreach (VM vm in Connection.ResolveAll(resident_VMs))
|
|
{
|
|
if (vm.is_control_domain)
|
|
continue;
|
|
|
|
VM_metrics VMMetrics = Connection.Resolve(vm.metrics);
|
|
if (VMMetrics == null)
|
|
continue;
|
|
|
|
string message = string.Format(Messages.GENERAL_MEMORY_VM_USED, vm.Name(),
|
|
Util.MemorySizeStringSuitableUnits(VMMetrics.memory_actual, true));
|
|
|
|
lines.Add(message);
|
|
}
|
|
|
|
return string.Join("\n", lines.ToArray());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wait about two minutes for all the PBDs on this host to become plugged:
|
|
/// if they do not, try and plug them. (Refs: CA-41219, CA-41305, CA-66496).
|
|
/// </summary>
|
|
public void CheckAndPlugPBDs()
|
|
{
|
|
bool allPBDsReady = false;
|
|
int timeout = 120;
|
|
log.DebugFormat("Waiting for PBDs on host {0} to become plugged", Name());
|
|
|
|
while (timeout > 0)
|
|
{
|
|
if (enabled) // if the Host is not yet enabled, pbd.currently_attached may not be accurate: see CA-66496.
|
|
{
|
|
allPBDsReady = true;
|
|
foreach (var pbdRef in PBDs)
|
|
{
|
|
var pbd = Connection.Resolve(pbdRef);
|
|
|
|
if (pbd == null || pbd.currently_attached)
|
|
continue;
|
|
|
|
if (Helpers.StockholmOrGreater(this)) //CA-350406
|
|
{
|
|
var sr = Connection.Resolve(pbd.SR);
|
|
if (sr != null && sr.is_tools_sr)
|
|
continue;
|
|
}
|
|
|
|
allPBDsReady = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (allPBDsReady)
|
|
return;
|
|
|
|
Thread.Sleep(1000);
|
|
timeout--;
|
|
}
|
|
|
|
foreach (var pbdRef in PBDs)
|
|
{
|
|
var pbd = Connection.Resolve(pbdRef);
|
|
if (pbd == null || pbd.currently_attached)
|
|
continue;
|
|
|
|
if (Helpers.StockholmOrGreater(this))
|
|
{
|
|
var sr = Connection.Resolve(pbd.SR);
|
|
if (sr != null && sr.is_tools_sr)
|
|
continue;
|
|
}
|
|
|
|
Session session = Connection.DuplicateSession();
|
|
|
|
// If we still haven't plugged, then try and plug it - this will probably
|
|
// fail, but at least we'll get a better error message.
|
|
|
|
try
|
|
{
|
|
log.DebugFormat("Plugging PBD {0} on host {1}", pbd.Name(), Name());
|
|
PBD.plug(session, pbd.opaque_ref);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
log.Debug(string.Format("Error plugging PBD {0} on host {1}", pbd.Name(), Name()), e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the host is running the vSwitch network stack
|
|
/// </summary>
|
|
public bool vSwitchNetworkBackend()
|
|
{
|
|
return software_version.ContainsKey("network_backend") &&
|
|
software_version["network_backend"] == "openvswitch";
|
|
}
|
|
|
|
/// <summary>
|
|
/// The number of CPU sockets the host has
|
|
/// Return 0 if a problem is found
|
|
/// </summary>
|
|
public virtual int CpuSockets()
|
|
{
|
|
const string key = "socket_count";
|
|
const int defaultSockets = 0;
|
|
|
|
if (cpu_info == null || !cpu_info.ContainsKey(key))
|
|
return defaultSockets;
|
|
|
|
int sockets;
|
|
bool parsed = int.TryParse(cpu_info[key], out sockets);
|
|
if (!parsed)
|
|
return defaultSockets;
|
|
|
|
return sockets;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The number of cpus the host has
|
|
/// Return 0 if a problem is found
|
|
/// </summary>
|
|
public int CpuCount()
|
|
{
|
|
const string key = "cpu_count";
|
|
const int defaultCpuCount = 0;
|
|
|
|
if (cpu_info == null || !cpu_info.ContainsKey(key))
|
|
return defaultCpuCount;
|
|
|
|
int cpuCount;
|
|
bool parsed = int.TryParse(cpu_info[key], out cpuCount);
|
|
if (!parsed)
|
|
return defaultCpuCount;
|
|
|
|
return cpuCount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The number of cores per socket the host has
|
|
/// Return 0 if a problem is found
|
|
/// </summary>
|
|
public int CoresPerSocket()
|
|
{
|
|
var sockets = CpuSockets();
|
|
var cpuCount = CpuCount();
|
|
if (sockets > 0 && cpuCount > 0)
|
|
return (cpuCount/sockets);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Grace is either upgrade or regular
|
|
/// </summary>
|
|
public virtual bool InGrace()
|
|
{
|
|
return license_params.ContainsKey("grace");
|
|
}
|
|
|
|
internal override string LocationString()
|
|
{
|
|
//for standalone hosts we do not show redundant location info
|
|
return Helpers.GetPool(Connection) == null ? string.Empty : base.LocationString();
|
|
}
|
|
|
|
public bool EnterpriseFeaturesEnabled()
|
|
{
|
|
var hostEdition = GetEdition(edition);
|
|
return EligibleForSupport() && (hostEdition == Edition.EnterprisePerSocket || hostEdition == Edition.EnterprisePerUser
|
|
|| hostEdition == Edition.PerSocket);
|
|
}
|
|
|
|
public bool DesktopPlusFeaturesEnabled()
|
|
{
|
|
return GetEdition(edition) == Edition.DesktopPlus;
|
|
}
|
|
|
|
public bool DesktopFeaturesEnabled()
|
|
{
|
|
return GetEdition(edition) == Edition.Desktop;
|
|
}
|
|
|
|
public bool DesktopCloudFeaturesEnabled()
|
|
{
|
|
return GetEdition(edition) == Edition.DesktopCloud;
|
|
}
|
|
|
|
public bool PremiumFeaturesEnabled()
|
|
{
|
|
return GetEdition(edition) == Edition.Premium;
|
|
}
|
|
|
|
public bool StandardFeaturesEnabled()
|
|
{
|
|
return GetEdition(edition) == Edition.Standard;
|
|
}
|
|
|
|
public bool FreeFeaturesEnabled()
|
|
{
|
|
return GetEdition(edition) == Edition.Free;
|
|
}
|
|
|
|
public bool EligibleForSupport()
|
|
{
|
|
return GetEdition(edition) != Edition.Free;
|
|
}
|
|
|
|
#region Supplemental Packs
|
|
|
|
// From http://scale.uk.xensource.com/confluence/display/engp/Supplemental+Pack+product+design+notes#SupplementalPackproductdesignnotes-XenAPI:
|
|
// The supplemental packs that are installed on a host are listed in the host's Host.software_version field in the data model.
|
|
// The keys of the entries have the form "<originator>:<name>", the value is "<description>, version <version>", appended by
|
|
// ", build <build>" if the build number is present in the XML file, and further appended by ", homogeneous" if the
|
|
// enforce-homogeneity attribute is present and set to true.
|
|
//
|
|
// Examples:
|
|
// xs:main: Base Pack, version 5.5.900, build 19689c
|
|
// xs:linux: Linux Pack, version 5.5.900, build 19689c, homogeneous
|
|
|
|
public class SuppPack
|
|
{
|
|
private string originator, name, description, version, build;
|
|
private bool homogeneous;
|
|
|
|
public string Originator { get { return originator; } }
|
|
public string Name { get { return name; } }
|
|
public string Description { get { return description; } }
|
|
public string Version { get { return version; } }
|
|
public string Build { get { return build; } }
|
|
public bool Homogeneous { get { return homogeneous; } }
|
|
|
|
public string OriginatorAndName { get { return originator + ":" + name; } }
|
|
|
|
private bool parsed = false;
|
|
public bool IsValid { get { return parsed; } }
|
|
|
|
public string LongDescription { get { return string.Format(Messages.SUPP_PACK_DESCRIPTION, description, version); } }
|
|
|
|
/// <summary>
|
|
/// Try to parse the supp pack information from one key of software_version
|
|
/// </summary>
|
|
public SuppPack(string key, string value)
|
|
{
|
|
// Parse the key
|
|
string[] splitKey = key.Split(':');
|
|
if (splitKey.Length != 2)
|
|
return;
|
|
originator = splitKey[0];
|
|
name = splitKey[1];
|
|
|
|
// Parse the value. The description may contain arbitrary text, so we have to be a bit subtle:
|
|
// we first search from the end to find where the description ends.
|
|
int x = value.LastIndexOf(", version ");
|
|
if (x <= 0)
|
|
return;
|
|
|
|
description = value.Substring(0, x);
|
|
string val = value.Substring(x + 10);
|
|
|
|
string[] delims = new string[] {", "};
|
|
string[] splitValue = val.Split(delims, StringSplitOptions.None);
|
|
if (splitValue.Length == 0 || splitValue.Length > 3)
|
|
return;
|
|
|
|
version = splitValue[0];
|
|
|
|
if (splitValue.Length >= 2)
|
|
{
|
|
if (!splitValue[1].StartsWith("build "))
|
|
return;
|
|
build = splitValue[1].Substring(6);
|
|
}
|
|
|
|
if (splitValue.Length >= 3)
|
|
{
|
|
if (splitValue[2] != "homogeneous")
|
|
return;
|
|
homogeneous = true;
|
|
}
|
|
else
|
|
homogeneous = false;
|
|
|
|
parsed = true;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Return a list of the supplemental packs
|
|
/// </summary>
|
|
public List<SuppPack> SuppPacks()
|
|
{
|
|
List<SuppPack> packs = new List<SuppPack>();
|
|
if (software_version == null)
|
|
return packs;
|
|
foreach (string key in software_version.Keys)
|
|
{
|
|
SuppPack pack = new SuppPack(key, software_version[key]);
|
|
if (pack.IsValid)
|
|
packs.Add(pack);
|
|
}
|
|
return packs;
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// The PGPU that is the system display device or null
|
|
/// </summary>
|
|
public PGPU SystemDisplayDevice()
|
|
{
|
|
var pGpus = Connection.ResolveAll(PGPUs);
|
|
return pGpus.FirstOrDefault(pGpu => pGpu.is_system_display_device);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is the host allowed to enable/disable integrated GPU passthrough or is the feature unavailable/restricted?
|
|
/// </summary>
|
|
public bool CanEnableDisableIntegratedGpu()
|
|
{
|
|
return Helpers.GpuCapability(Connection) &&
|
|
!Helpers.FeatureForbidden(Connection, RestrictIntegratedGpuPassthrough);
|
|
}
|
|
|
|
public static bool TryGetUpgradeVersion(Host host, Dictionary<string, string> installMethodConfig,
|
|
out string platformVersion, out string productVersion)
|
|
{
|
|
platformVersion = productVersion = null;
|
|
|
|
try
|
|
{
|
|
var result = call_plugin(host.Connection.Session, host.opaque_ref,
|
|
"prepare_host_upgrade.py", "getVersion", installMethodConfig);
|
|
var serializer = new JavaScriptSerializer();
|
|
var version = (Dictionary<string, object>)serializer.DeserializeObject(result);
|
|
platformVersion = version.ContainsKey("platform-version") ? (string)version["platform-version"] : null;
|
|
productVersion = version.ContainsKey("product-version") ? (string)version["product-version"] : null;
|
|
return platformVersion != null || productVersion != null;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
log.WarnFormat("Plugin call prepare_host_upgrade.getVersion on {0} failed with {1}", host.Name(), exception.Message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#region IEquatable<Host> Members
|
|
|
|
/// <summary>
|
|
/// Indicates whether the current object is equal to the specified object. This calls the implementation from XenObject.
|
|
/// This implementation is required for ToStringWrapper.
|
|
/// </summary>
|
|
public virtual bool Equals(Host other)
|
|
{
|
|
return base.Equals(other);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|