xenadmin/XenModel/XenAPI-Extensions/SR.cs
2023-12-30 18:58:02 +01:00

1010 lines
36 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.Linq;
using System.Xml;
using XenAdmin;
using XenAdmin.Core;
using XenAdmin.Network;
namespace XenAPI
{
public partial class SR : IComparable<SR>, IEquatable<SR>
{
/// <summary>
/// The SR types. Note that the names of these enum members correspond exactly to the SR type string as
/// recognised by the server. If you add anything here, check whether it should also be added to
/// SR.CanCreateWithXenCenter. Also add it to XenAPI/FriendlyNames.resx under
/// Label-SR.SRTypes-*. Don't change the lower-casing!
/// </summary>
public enum SRTypes
{
local, file, ext, ext4, lvmoiscsi, iso, nfs, lvm, udev, lvmofc,
lvmohba, egenera, egeneracd, dummy, unknown, shm,
iscsi, tmpfs, xfs,
ebs, rawhba,
smb, lvmofcoe, gfs2,
nutanix, nutanixiso,
}
public const long DISK_MAX_SIZE = 2 * Util.BINARY_TERA;
public const string Content_Type_ISO = "iso";
public const string SM_Config_Type_CD = "cd";
private const string XenServer_Tools_Label = "XenServer Tools";
public override string ToString()
{
return Name();
}
/// <summary>
/// A friendly name for the SR.
/// </summary>
public override string Name()
{
return I18N("name_label", name_label, true);
}
/// <summary>
/// Get the given SR's home, i.e. the host under which we are going to display it. May return null, if this SR should live
/// at the pool level.
/// </summary>
public Host Home()
{
if (shared || PBDs.Count != 1)
return null;
PBD pbd = Connection.Resolve(PBDs[0]);
if (pbd == null)
return null;
return Connection.Resolve(pbd.host);
}
public string NameWithoutHost()
{
return I18N("name_label", name_label, false);
}
/// <summary>
/// A friendly description for the SR.
/// </summary>
public override string Description()
{
return I18N("name_description", name_description, true);
}
public bool Physical()
{
SRTypes typ = GetSRType(false);
return typ == SRTypes.local || (typ == SRTypes.udev && SMConfigType() == SM_Config_Type_CD);
}
public string SMConfigType()
{
return Get(sm_config, "type");
}
public bool IsToolsSR()
{
return name_label == SR.XenServer_Tools_Label || is_tools_sr;
}
public string FriendlyTypeName()
{
var srType = GetSRType(false);
if (srType == SRTypes.unknown)
{
var sm = SM.GetByType(Connection, type);
if (sm != null &&
Version.TryParse(sm.required_api_version, out var smapiVersion) &&
smapiVersion.CompareTo(new Version(3, 0)) >= 0)
return !string.IsNullOrEmpty(sm.name_label) ? sm.name_label : type;
}
return GetFriendlyTypeName(srType);
}
/// <summary>
/// A friendly (internationalized) name for the SR type.
/// </summary>
public static string GetFriendlyTypeName(SRTypes srType)
{
return FriendlyNameManager.GetFriendlyName(string.Format("Label-SR.SRTypes-{0}", srType.ToString()));
}
/// <summary>
/// Use this instead of type
/// </summary>
public SRTypes GetSRType(bool ignoreCase)
{
try
{
return (SRTypes)Enum.Parse(typeof(SRTypes), type, ignoreCase);
}
catch
{
return SRTypes.unknown;
}
}
public SM GetSM()
{
return SM.GetByType(Connection, GetSRType(true).ToString());
}
private string I18N(string field_name, string field_value, bool with_host)
{
if (!other_config.ContainsKey("i18n-key"))
{
return field_value;
}
string i18n_key = other_config["i18n-key"];
string original_value_key = "i18n-original-value-" + field_name;
string original_value =
other_config.ContainsKey(original_value_key) ? other_config[original_value_key] : "";
if (original_value != field_value)
return field_value;
string hostname = with_host ? GetHostName() : null;
if (hostname == null)
{
string pattern = FriendlyNameManager.GetFriendlyName(string.Format("SR.{0}-{1}", field_name, i18n_key));
return pattern == null ? field_value : pattern;
}
else
{
string pattern = FriendlyNameManager.GetFriendlyName(string.Format("SR.{0}-{1}-host", field_name, i18n_key));
return pattern == null ? field_value : string.Format(pattern, hostname);
}
}
/// <summary>
/// Gets the name of the host to which this SR is attached, or null if the storage is shared
/// or unattached.
/// </summary>
private string GetHostName()
{
if (Connection == null)
return null;
Host host = GetStorageHost();
return host == null ? null : host.Name();
}
/// <summary>
/// Gets the host to which the given SR belongs, or null if the SR is shared or completely disconnected.
/// </summary>
public Host GetStorageHost()
{
if (shared || PBDs.Count != 1)
return null;
PBD pbd = Connection.Resolve(PBDs[0]);
return pbd == null ? null : Connection.Resolve(pbd.host);
}
/// <summary>
/// Iterating through the PBDs, this will return the storage host of the first PBD that is currently_attached.
/// This will return null if there are no PBDs or none of them is currently_attached
/// </summary>
public Host GetFirstAttachedStorageHost()
{
if (PBDs.Count == 0)
return null;
var currentlyAttachedPBDs = PBDs.Select(pbdref => Connection.Resolve(pbdref)).Where(p => p != null && p.currently_attached);
if (currentlyAttachedPBDs.FirstOrDefault() != null)
return currentlyAttachedPBDs.Select(p => Connection.Resolve(p.host)).Where(h => h != null).FirstOrDefault();
return null;
}
/// <summary>
/// Can create with XC, or is citrix storage link gateway. Special case alert!
/// </summary>
public bool CanCreateWithXenCenter()
{
SRTypes type = GetSRType(false);
return type == SRTypes.iso
|| type == SRTypes.lvmoiscsi
|| type == SRTypes.nfs
|| type == SRTypes.lvmohba
|| type == SRTypes.smb
|| type == SRTypes.lvmofcoe
|| type == SRTypes.gfs2;
}
public bool IsLocalSR()
{
SRTypes typ = GetSRType(false);
return typ == SRTypes.local
|| typ == SRTypes.file
|| typ == SRTypes.ext
|| typ == SRTypes.lvm
|| typ == SRTypes.udev
|| typ == SRTypes.egeneracd
|| typ == SRTypes.dummy
|| typ == SRTypes.ext4
|| typ == SRTypes.xfs;
}
/// <summary>
/// Returns true if there are any Running or Suspended VMs attached to VDIs on this SR.
/// </summary>
public bool HasRunningVMs()
{
foreach (VDI vdi in Connection.ResolveAll(VDIs))
{
foreach (VBD vbd in Connection.ResolveAll(vdi.VBDs))
{
VM vm = Connection.Resolve(vbd.VM);
if (vm == null)
continue;
// PR-1223: ignore control domain VM on metadata VDIs, so that the SR can be detached if there are no other running VMs
if (vdi.type == vdi_type.metadata && vm.is_control_domain)
continue;
if (vm.power_state == vm_power_state.Running)
return true;
}
}
return false;
}
public bool HasDriverDomain(out VM vm)
{
foreach (var pbdRef in PBDs)
{
var pbd = Connection.Resolve(pbdRef);
if (pbd != null && pbd.other_config.TryGetValue("storage_driver_domain", out string vmRef))
{
vm = Connection.Resolve(new XenRef<VM>(vmRef));
if (vm != null && !vm.IsControlDomainZero(out _))
return true;
}
}
vm = null;
return false;
}
/// <summary>
/// If host is non-null, return whether this storage can be seen from the given host.
/// If host is null, return whether the storage is shared, with a PBD for each host and at least one PBD plugged.
/// (See CA-36285 for why this is the right test when looking for SRs on which to create a new VM).
/// </summary>
public virtual bool CanBeSeenFrom(Host host)
{
if (host == null)
return shared && !IsBroken();
foreach (PBD pbd in host.Connection.ResolveAll(PBDs))
if (pbd.currently_attached && pbd.host.opaque_ref == host.opaque_ref)
return true;
return false;
}
public PBD GetPBDFor(Host host)
{
foreach (PBD pbd in host.Connection.ResolveAll(PBDs))
if (pbd.host.opaque_ref == host.opaque_ref)
return pbd;
return null;
}
/// <summary>
/// True if there is less than 0.5GB free. Always false for dummy and ebs SRs.
/// </summary>
public bool IsFull()
{
SRTypes t = GetSRType(false);
return t != SRTypes.dummy && t != SRTypes.ebs && FreeSpace() < Util.BINARY_GIGA/2;
}
public virtual long FreeSpace()
{
return physical_size - physical_utilisation;
}
/// <summary>
/// SR is detached when it has no PBDs or when all its PBDs are unplugged
/// </summary>
public bool IsDetached()
{
foreach (var pbdRef in PBDs)
{
var pbd = Connection.Resolve(pbdRef);
if (pbd != null && pbd.currently_attached)
return false;
}
return true;
}
public bool HasPBDs()
{
// CA-15188: Show SRs with no PBDs on Orlando, as pool-eject bug has been fixed.
// SRs are detached if they have no PBDs
return PBDs.Count > 0;
}
public override bool Show(bool showHiddenVMs)
{
if (name_label.StartsWith(Helpers.GuiTempObjectPrefix))
return false;
SRTypes srType = GetSRType(false);
// CA-15012 - dont show cd drives of type local on miami (if dont get destroyed properly on upgrade)
if (srType == SRTypes.local)
return false;
// Hide Memory SR
if (srType == SRTypes.tmpfs)
return false;
if (showHiddenVMs)
return true;
//CP-2458: hide SRs that were introduced by a DR_task
if (introduced_by != null && introduced_by.opaque_ref != Helper.NullOpaqueRef)
return false;
return !IsHidden();
}
public override bool IsHidden()
{
return BoolKey(other_config, HIDE_FROM_XENCENTER);
}
/// <summary>
/// The SR is broken when it has the wrong number of PBDs, or (optionally) not
/// all the PBDs are attached. For standalone host or non-shared SR there should
/// be exactly one PBD, otherwise a PBD for each host.
/// </summary>
/// <param name="checkAttached">Whether to check that all the PBDs are attached</param>
public virtual bool IsBroken(bool checkAttached = true)
{
if (PBDs.Count == 0)
return true;
if (Helpers.GetPoolOfOne(Connection) == null || !shared)
{
if (PBDs.Count != 1)
return true;
}
else
{
if (PBDs.Count != Connection.Cache.HostCount)
return true;
}
if (checkAttached)
{
foreach (var pbdRef in PBDs)
{
var pbd = Connection?.Resolve(pbdRef);
if (pbd == null || !pbd.currently_attached)
return true;
}
}
return false;
}
public static bool IsDefaultSr(SR sr)
{
Pool pool = Helpers.GetPoolOfOne(sr.Connection);
return pool != null && pool.default_SR != null && pool.default_SR.opaque_ref == sr.opaque_ref;
}
/// <summary>
/// Whether the underlying SR backend supports VDI_CREATE. Will return true even if the SR is full.
/// </summary>
public virtual bool SupportsVdiCreate()
{
// ISO SRs are deemed not to support VDI create in the GUI, even though the back end
// knows that they do. See CA-40119.
if (content_type == SR.Content_Type_ISO)
return false;
// Memory SRs should not support VDI create in the GUI
if (GetSRType(false) == SR.SRTypes.tmpfs)
return false;
SM sm = SM.GetByType(Connection, type);
return sm != null && -1 != Array.IndexOf(sm.capabilities, "VDI_CREATE");
}
/// <summary>
/// Whether the underlying SR backend supports storage migration. Will return true even if the SR is full.
/// </summary>
/// <returns></returns>
public virtual bool SupportsStorageMigration()
{
// ISO and Memory SRs should not support migration
if (content_type == SR.Content_Type_ISO || GetSRType(false) == SR.SRTypes.tmpfs)
return false;
SM sm = SM.GetByType(Connection, type);
// check if the SM has VDI_SNAPSHOT and VDI_MIRROR capabilities; the VDI_MIRROR capability has only been added in Ely (API Version 2.6)
return sm != null && Array.IndexOf(sm.capabilities, "VDI_SNAPSHOT") != -1 && (Array.IndexOf(sm.capabilities, "VDI_MIRROR") != -1 || !Helpers.ElyOrGreater(Connection));
}
public static List<SRInfo> ParseSRList(List<Probe_result> probeExtResult)
{
List<SRInfo> results = new List<SRInfo>();
foreach (var probeResult in probeExtResult.Where(p => p.sr != null))
{
string uuid = probeResult.sr.uuid;
long size = probeResult.sr.total_space;
string aggr = "";
string name_label = probeResult.sr.name_label;
string name_description = probeResult.sr.name_description;
bool pool_metadata_detected = false;
results.Add(new SRInfo(uuid, size, aggr, name_label, name_description, pool_metadata_detected, probeResult.configuration));
}
return results;
}
/// <summary>
/// Checks if the SR contains enough free space to accomodate the specified VDI size.
/// If checking a possibly thinly provisioned SR, provide a value for vdiPhysicalUtilization.
///
/// CA-359965: physical_utlization is not actually telling us how much of the VDI is actively being used.
/// Any copy of VDIs to a thinly provisioned SR could fail.
/// </summary>
/// <param name="cannotFitReason">The reason why the disks cannot fit on the SR</param>
/// <param name="vdis">The disks to check</param>
public virtual bool CanFitDisks(out string cannotFitReason, VDI[] vdis)
{
var sm = GetSM();
var vdiSizeUnlimited = sm != null && Array.IndexOf(sm.capabilities, "LARGE_VDI") != -1;
if (!vdiSizeUnlimited && vdis.Any(vdi => vdi.virtual_size > DISK_MAX_SIZE))
{
cannotFitReason = string.Format(Messages.SR_DISKSIZE_EXCEEDS_DISK_MAX_SIZE,
Util.DiskSizeString(DISK_MAX_SIZE, 0));
return false;
}
if (IsFull())
{
cannotFitReason = Messages.SRPICKER_SR_FULL;
return false;
}
var isThinlyProvisioned = sm != null && Array.IndexOf(sm.capabilities, "THIN_PROVISIONING") != -1;
var vdiPhysicalUtilization = vdis.Sum(vdi => vdi.physical_utilisation);
var vdiSize = vdis.Sum(vdi => vdi.virtual_size);
var sizeToConsider = isThinlyProvisioned ? vdiPhysicalUtilization : vdiSize;
if (sizeToConsider > physical_size)
{
cannotFitReason = string.Format(Messages.SR_PICKER_DISK_TOO_BIG,
Util.DiskSizeString(sizeToConsider, 2),
Util.DiskSizeString(physical_size, 2));
return false;
}
var freeSpace = FreeSpace();
if (sizeToConsider > freeSpace)
{
cannotFitReason = string.Format(Messages.SR_PICKER_INSUFFICIENT_SPACE,
Util.DiskSizeString(sizeToConsider, 2),
Util.DiskSizeString(freeSpace, 2));
return false;
}
cannotFitReason = string.Empty;
return true;
}
/// <summary>
/// Parses an XML list of SRs (as returned by the SR.probe() call) into a list of SRInfos.
/// </summary>
public static List<SRInfo> ParseSRListXML(string xml)
{
List<SRInfo> results = new List<SRInfo>();
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
foreach (XmlNode node in doc.GetElementsByTagName("SR"))
{
string uuid = "";
long size = 0;
string aggr = "";
string name_label = "";
string name_description = "";
bool pool_metadata_detected = false;
foreach (XmlNode info in node.ChildNodes)
{
if (info.Name.ToLowerInvariant() == "uuid")
{
uuid = info.InnerText.Trim();
}
else if (info.Name.ToLowerInvariant() == "size")
{
size = long.Parse(info.InnerText.Trim());
}
else if (info.Name.ToLowerInvariant() == "aggregate")
{
aggr = info.InnerText.Trim();
}
else if (info.Name.ToLowerInvariant() == "name_label")
{
name_label = info.InnerText.Trim();
}
else if (info.Name.ToLowerInvariant() == "name_description")
{
name_description = info.InnerText.Trim();
}
else if (info.Name.ToLowerInvariant() == "pool_metadata_detected")
{
bool.TryParse(info.InnerText.Trim(), out pool_metadata_detected);
}
}
results.Add(new SRInfo(uuid, size, aggr, name_label, name_description, pool_metadata_detected));
}
return results;
}
public static List<string> ParseSupportedVersionsListXML(string xml)
{
var supportedVersionsResult = new List<string>();
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
// If we've got this from an async task result, then it will be wrapped
// in a <value> element. Parse the contents instead.
var nodes = doc.GetElementsByTagName("value");
if (nodes.Count > 0)
{
xml = nodes[0].InnerText;
doc = new XmlDocument();
doc.LoadXml(xml);
}
foreach (XmlNode node in doc.GetElementsByTagName("SupportedVersions"))
{
foreach (XmlNode info in node.ChildNodes)
{
if (info.Name.ToLowerInvariant() == "version")
{
supportedVersionsResult.Add(info.InnerText.Trim());
}
}
}
return supportedVersionsResult;
}
public String GetScsiID()
{
foreach (PBD pbd in Connection.ResolveAll(PBDs))
{
if (!pbd.device_config.ContainsKey("SCSIid"))
continue;
return pbd.device_config["SCSIid"];
}
if (!sm_config.ContainsKey("devserial"))
return null;
String SCSIid = sm_config["devserial"];
if (SCSIid.StartsWith("scsi-"))
SCSIid = SCSIid.Remove(0, 5);
// CA-22352: SCSI IDs on the general panel for a NetApp SR have a trailing comma
SCSIid = SCSIid.TrimEnd(new char[] { ',' });
return SCSIid;
}
/// <summary>
/// New Lun Per VDI mode (cf. LunPerVDI method) using the hba encapsulating type
/// </summary>
public virtual bool HBALunPerVDI()
{
return GetSRType(true) == SRTypes.rawhba;
}
/// <summary>
/// Legacy LunPerVDI mode - for old servers that this was set up on
/// This is no longer an option (2012) for newer servers but we need to keep this
/// </summary>
public bool LunPerVDI()
{
// Look for the mapping from scsi id -> vdi uuid
// in sm-config.
foreach (String key in sm_config.Keys)
if (key.Contains("LUNperVDI") || key.StartsWith("scsi-"))
return true;
return false;
}
private const String MPATH = "mpath";
public Dictionary<VM, Dictionary<VDI, String>> GetMultiPathStatusLunPerVDI()
{
Dictionary<VM, Dictionary<VDI, String>> result =
new Dictionary<VM, Dictionary<VDI, String>>();
if (Connection == null)
return result;
foreach (PBD pbd in Connection.ResolveAll(PBDs))
{
if (!pbd.MultipathActive())
continue;
foreach (KeyValuePair<String, String> kvp in pbd.other_config)
{
if (!kvp.Key.StartsWith(MPATH))
continue;
int current;
int max;
if (!PBD.ParsePathCounts(kvp.Value, out current, out max))
continue;
String scsiIdKey = String.Format("scsi-{0}", kvp.Key.Substring(MPATH.Length + 1));
if (!sm_config.ContainsKey(scsiIdKey))
continue;
String vdiUUID = sm_config[scsiIdKey];
VDI vdi = null;
foreach (VDI candidate in Connection.ResolveAll(VDIs))
{
if (candidate.uuid != vdiUUID)
continue;
vdi = candidate;
break;
}
if (vdi == null)
continue;
foreach (VBD vbd in Connection.ResolveAll(vdi.VBDs))
{
VM vm = Connection.Resolve(vbd.VM);
if (vm == null)
continue;
if (vm.power_state != vm_power_state.Running)
continue;
if (!result.ContainsKey(vm))
result[vm] = new Dictionary<VDI, String>();
result[vm][vdi] = kvp.Value;
}
}
}
return result;
}
public Dictionary<PBD, String> GetMultiPathStatusLunPerSR()
{
Dictionary<PBD, String> result =
new Dictionary<PBD, String>();
if (Connection == null)
return result;
foreach (PBD pbd in Connection.ResolveAll(PBDs))
{
if (!pbd.MultipathActive())
continue;
String status = String.Empty;
foreach (KeyValuePair<String, String> kvp in pbd.other_config)
{
if (!kvp.Key.StartsWith(MPATH))
continue;
status = kvp.Value;
break;
}
int current;
int max;
if (!PBD.ParsePathCounts(status, out current, out max))
continue;
result[pbd] = status;
}
return result;
}
public bool MultipathAOK()
{
if (!MultipathCapable())
return true;
if (LunPerVDI())
{
Dictionary<VM, Dictionary<VDI, String>>
multipathStatus = GetMultiPathStatusLunPerVDI();
foreach (VM vm in multipathStatus.Keys)
foreach (VDI vdi in multipathStatus[vm].Keys)
if (!CheckMultipathString(multipathStatus[vm][vdi]))
return false;
}
else
{
Dictionary<PBD, String> multipathStatus =
GetMultiPathStatusLunPerSR();
foreach (PBD pbd in multipathStatus.Keys)
if (pbd.MultipathActive() && !CheckMultipathString(multipathStatus[pbd]))
return false;
}
return true;
}
public override string NameWithLocation()
{
//return only the Name for local SRs
if (Connection != null && !shared)
{
return Name();
}
return base.NameWithLocation();
}
internal override string LocationString()
{
var home = Home();
return home != null ? home.LocationString() : base.LocationString();
}
private bool CheckMultipathString(String status)
{
int current;
int max;
if (!PBD.ParsePathCounts(status, out current, out max))
return true;
return current >= max;
}
public class SRInfo : IComparable<SRInfo>, IEquatable<SRInfo>
{
public readonly string UUID;
public readonly long Size;
public readonly string Aggr;
public string Name;
public string Description;
public readonly bool PoolMetadataDetected;
public Dictionary<string, string> Configuration;
public SRInfo(string uuid, long size = 0, string aggr = "", string name = "", string description = "",
bool poolMetadataDetected = false, Dictionary<string,string> configuration = null)
{
UUID = uuid;
Size = size;
Aggr = aggr;
Name = name;
Description = description;
PoolMetadataDetected = poolMetadataDetected;
Configuration = configuration;
}
public int CompareTo(SRInfo other)
{
return (this.UUID.CompareTo(other.UUID));
}
public bool Equals(SRInfo other)
{
return this.CompareTo(other) == 0;
}
public override string ToString()
{
return UUID;
}
}
public bool MultipathCapable()
{
SM relatedSm = GetSM();
bool isAnSmCapability = relatedSm != null && relatedSm.MultipathEnabled();
bool isInSmConfig = BoolKey(sm_config, "multipathable");
return isInSmConfig || isAnSmCapability;
}
public string Target()
{
SR sr = Connection.Resolve(new XenRef<SR>(opaque_ref));
if (sr == null)
return string.Empty;
foreach (PBD pbd in sr.Connection.ResolveAll(sr.PBDs))
{
SRTypes srType = sr.GetSRType(false);
if (srType == SRTypes.lvmoiscsi && pbd.device_config.ContainsKey("target")) //iscsi
{
return pbd.device_config["target"];
}
if (srType == SRTypes.iso && pbd.device_config.ContainsKey("location")) // cifs or nfs iso
{
string target = Helpers.HostnameFromLocation(pbd.device_config["location"]); // has form //ip_address/path
if (string.IsNullOrEmpty(target))
continue;
return target;
}
if (srType == SRTypes.nfs && pbd.device_config.ContainsKey("server"))
{
return pbd.device_config["server"];
}
}
return string.Empty;
}
/// <summary>
/// The amount of memory used as compared to the available and allocated amounts as a friendly string
/// </summary>
public String SizeString()
{
return string.Format(Messages.SR_SIZE_USED,
Util.DiskSizeString(physical_utilisation),
Util.DiskSizeString(physical_size),
Util.DiskSizeString(virtual_allocation));
}
/// <summary>
/// Returns true when there is a pbd containing adapterid else false
/// </summary>
public bool CanRepairAfterUpgradeFromLegacySL()
{
if (type == "cslg")
{
var pbds = Connection.ResolveAll(PBDs);
if (pbds != null)
{
return pbds.Any(pbd => pbd.device_config.ContainsKey("adapterid"));
}
return false;
}
return true;
}
/// <summary>
/// Whether SR supports database replication.
/// </summary>
public static bool SupportsDatabaseReplication(IXenConnection connection, SR sr)
{
try
{
assert_supports_database_replication(connection.Session, sr.opaque_ref);
return true;
}
catch (Failure)
{
return false;
}
}
/// <summary>
/// Whether the underlying SR backend supports SR_TRIM
/// </summary>
public bool SupportsTrim()
{
System.Diagnostics.Trace.Assert(Connection != null, "Connection must not be null");
SM sm = SM.GetByType(Connection, type);
return sm != null && sm.features != null && sm.features.ContainsKey("SR_TRIM");
}
/// <summary>
/// Whether the underlying SR backend supports read caching.
/// </summary>
/// <returns></returns>
public bool SupportsReadCaching()
{
// for Stockholm or greater versions, check if the SM has the VDI_READ_CACHING capability
if (Helpers.StockholmOrGreater(Connection))
{
var sm = SM.GetByType(Connection, type);
return sm?.features != null && sm.features.ContainsKey("VDI_READ_CACHING");
}
// for older versions, use the SR type; read caching is available for NFS, EXT3 and SMB/CIFS SR types
var srType = GetSRType(false);
return srType == SRTypes.nfs || srType == SRTypes.ext || srType == SRTypes.smb;
}
public bool GetReadCachingEnabled()
{
// read caching is enabled when the o_direct key is not defined (or set to false) in other_config
// and is disabled if o_direct=true
return SupportsReadCaching() && !BoolKey(other_config, "o_direct");
}
public void SetReadCachingEnabled(bool value)
{
// to enable read caching, remove the o_direct key; to disable it, set o_direct=true
other_config = SetDictionaryKey(other_config, "o_direct", value ? null : bool.TrueString.ToLower());
}
#region IEquatable<SR> 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 bool Equals(SR other)
{
return base.Equals(other);
}
#endregion
}
}