/* 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, IEquatable { 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 ?? ""; } /// /// The expiry date of this host's license in UTC. /// Defaults to 2030-01-01 if not found. /// 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); } /// /// 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. /// 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"); } /// /// Added for Clearwater /// /// /// public static bool RestrictHotfixApply(Host h) { return BoolKeyPreferTrue(h.license_params, "restrict_hotfix_apply"); } /// /// Restrict Automated Updates /// /// host /// 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; } /// /// True if host qualifies for showing an upselling message based on its license and version. ///
/// Used to decide whether or not to show the upselling message from trial or express edition. ///
/// See CP-43000 for more info. ///
public virtual bool CanShowTrialEditionUpsell() { return Helpers.NileOrGreater(this) && IsFreeLicense() && !IsInPreviewRelease(); } /// /// Return true if the is_preview_release value in host.software_version is present and set to true. /// public virtual bool IsInPreviewRelease() { return software_version.TryGetValue("is_preview_release", out var isPreviewReleaseString) && bool.TryParse(isPreviewReleaseString, out var isPreviewRelease) && isPreviewRelease; } /// /// Returns true if the CSS license has expired, regardless of what edition is shown. ///
/// Do not rely on this method for enforcing restrictions as the user can circumvent this method /// by updating the system date. ///
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"); } /// /// Active directory is restricted only if the "restrict_ad" key exists and it is true /// 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"); } /// /// Vss feature is restricted only if the "restrict_vss" key exists and it is true /// 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"); } /// /// 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 /// 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; } /// /// 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 /// 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 in PBDs) { PBD thePBD = sr.Connection.Resolve(pbd); if (thePBD != null && thePBD.SR.opaque_ref == sr.opaque_ref) { return true; } } return false; } public PBD GetPBDTo(SR sr) { foreach (XenRef pbd in PBDs) { PBD thePBD = sr.Connection.Resolve(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(pool.master); return coordinator != null && coordinator.uuid == this.uuid; } /// /// Return this host's product version triplet (e.g. 5.6.100), or null if it can't be found. /// public virtual string ProductVersion() { return Get(software_version, "product_version"); } /// /// 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. /// public string ProductVersionText() { string s = Get(software_version, "product_version_text"); return string.IsNullOrEmpty(s) ? ProductVersion() : s; } /// /// 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. /// public string ProductVersionTextShort() { string s = Get(software_version, "product_version_text_short"); return string.IsNullOrEmpty(s) ? ProductVersion() : s; } /// /// 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. /// public virtual string PlatformVersion() { return Get(software_version, "platform_version"); } public string GetXapiVersion() { return Get(software_version, "xapi_build") ?? Get(software_version, "xapi"); } /// /// For legacy build numbers only (used to be integers + one char at the end) /// From Falcon, this property is not used. /// /// /// 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. /// 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; } /// /// Return the exact build_number of this host /// /// /// null if not found /// public virtual string BuildNumberRaw() { return Get(software_version, "build_number"); } /// /// Return this host's product version and build number (e.g. 5.6.100.72258), or null if product version can't be found. /// public virtual string LongProductVersion() { string productVersion = ProductVersion(); return productVersion != null ? string.Format("{0}.{1}", productVersion, Helpers.ElyOrGreater(this) ? BuildNumberRaw() : BuildNumber().ToString()) : null; } /// /// Return the product_brand of this host, or null if none can be found. /// public string ProductBrand() { return Get(software_version, "product_brand"); } /// /// The remote syslog target. May return null if not set on the server. /// public string GetSysLogDestination() { return logging != null && logging.ContainsKey("syslog_destination") ? logging["syslog_destination"] : null; } /// /// Set to null to unset /// public void SetSysLogDestination(string value) { logging = SetDictionaryKey(logging, "syslog_destination", value); } public virtual List AppliedPatches() { List patches = new List(); 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 AppliedUpdates() { var updates = new List(); 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> GetRunningPvVMs() { var vms = from XenRef vmref in resident_VMs let vm = Connection.Resolve(vmref) where vm != null && vm.IsRealVm() && !vm.IsHVM() select vmref; return vms.ToList(); } public List> GetRunningHvmVMs() { var vms = from XenRef vmref in resident_VMs let vm = Connection.Resolve(vmref) where vm != null && vm.IsRealVm() && vm.IsHVM() select vmref; return vms.ToList(); } public List> GetRunningVMs() { var vms = from XenRef 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"; /// /// 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. /// /// Pass in the session you want to use for the other config writing public void SaveEvacuatedVMs(Session session) { //Program.AssertOffEventThread(); XenRef opaque_ref = get_by_uuid(session, uuid); List migratedVMs = GetVMs(MAINTENANCE_MODE_EVACUATED_VMS_MIGRATED); List suspendedVMs = GetVMs(MAINTENANCE_MODE_EVACUATED_VMS_SUSPENDED); List haltedVMs = GetVMs(MAINTENANCE_MODE_EVACUATED_VMS_HALTED); List allVMs = new List(); 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 vms) { //Program.AssertOffEventThread(); List vmUUIDs = new List(); 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 GetVMs(String key) { List vms = new List(); 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 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 GetMigratedEvacuatedVMs() { return GetEvacuatedVMs(MAINTENANCE_MODE_EVACUATED_VMS_MIGRATED, vm_power_state.Running); } public List GetSuspendedEvacuatedVMs() { return GetEvacuatedVMs(MAINTENANCE_MODE_EVACUATED_VMS_SUSPENDED, vm_power_state.Suspended); } public List GetHaltedEvacuatedVMs() { return GetEvacuatedVMs(MAINTENANCE_MODE_EVACUATED_VMS_HALTED, vm_power_state.Halted); } private List GetEvacuatedVMs(String key, vm_power_state expectedPowerState) { List vms = GetVMs(key); foreach (VM vm in vms.ToArray()) if (vm.power_state != expectedPowerState) vms.Remove(vm); return vms; } #endregion /// /// Will return null if cannot find connection or any control domain in list of vms /// 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 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); } /// /// Interpret a value from the software_version dictionary as a int, or 0 if we couldn't parse it. /// private int GetSVAsInt(string key) { string s = Get(software_version, key); if (s == null) return 0; return (int)Helper.GetAPIVersion(s); } /// /// The xencenter_min as a int, or 0. if we couldn't parse it. /// public int XenCenterMin() { return GetSVAsInt("xencenter_min"); } /// /// The xencenter_max as a int, or 0 if we couldn't parse it. /// public int XenCenterMax() { return GetSVAsInt("xencenter_max"); } public string GetDatabaseSchema() { return Get(software_version, "db_schema"); } /// /// 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. /// 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); } /// /// 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. /// 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; } /// /// 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. /// 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; } /// /// 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. /// 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; } /// /// 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. /// 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; } /// /// Friendly string showing memory usage on the host /// 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)); } /// /// A friendly string for the XenMemory on this host /// public string XenMemoryString() { if (Connection.Resolve(metrics) == null) return Messages.GENERAL_UNKNOWN; return Util.MemorySizeStringSuitableUnits(xen_memory_calc(), true); } /// /// A friendly string of the resident VM's memory usage, with each entry separated by a line break /// public string ResidentVMMemoryUsageString() { Host_metrics m = Connection.Resolve(metrics); if (m == null) return Messages.GENERAL_UNKNOWN; else { List lines = new List(); 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()); } } /// /// 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). /// 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); } } } /// /// Whether the host is running the vSwitch network stack /// public bool vSwitchNetworkBackend() { return software_version.ContainsKey("network_backend") && software_version["network_backend"] == "openvswitch"; } /// /// The number of CPU sockets the host has /// Return 0 if a problem is found /// 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; } /// /// The number of cpus the host has /// Return 0 if a problem is found /// 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; } /// /// The number of cores per socket the host has /// Return 0 if a problem is found /// public int CoresPerSocket() { var sockets = CpuSockets(); var cpuCount = CpuCount(); if (sockets > 0 && cpuCount > 0) return (cpuCount/sockets); return 0; } /// /// Grace is either upgrade or regular /// 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 ":", the value is ", version ", appended by // ", 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); } } /// /// Try to parse the supp pack information from one key of software_version /// 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; } } /// /// Return a list of the supplemental packs /// public List SuppPacks() { List packs = new List(); 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 /// /// The PGPU that is the system display device or null /// public PGPU SystemDisplayDevice() { var pGpus = Connection.ResolveAll(PGPUs); return pGpus.FirstOrDefault(pGpu => pGpu.is_system_display_device); } /// /// Is the host allowed to enable/disable integrated GPU passthrough or is the feature unavailable/restricted? /// public bool CanEnableDisableIntegratedGpu() { return Helpers.GpuCapability(Connection) && !Helpers.FeatureForbidden(Connection, RestrictIntegratedGpuPassthrough); } public static bool TryGetUpgradeVersion(Host host, Dictionary 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)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 Members /// /// Indicates whether the current object is equal to the specified object. This calls the implementation from XenObject. /// This implementation is required for ToStringWrapper. /// public virtual bool Equals(Host other) { return base.Equals(other); } #endregion } }