mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-11-25 06:16:37 +01:00
5abe4e109c
Instead of showing just the name, this code is showing name with location information in title for all objects that appear in the tree view. (A new property (NameWithLocation) with a getter accessor defined in IXenObject and has been implemented as virtual in XenObject and overridden at several child classes.) Signed-off-by: Gabor Apati-Nagy <gabor.apati-nagy@citrix.com>
1093 lines
35 KiB
C#
1093 lines
35 KiB
C#
/* Copyright (c) Citrix Systems Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms,
|
|
* with or without modification, are permitted provided
|
|
* that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Xml;
|
|
using XenAdmin;
|
|
using XenAdmin.Core;
|
|
using XenAdmin.Network;
|
|
using XenAdmin.Network.StorageLink;
|
|
|
|
|
|
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, ext, lvmoiscsi, iso, nfs, lvm, netapp, udev, lvmofc,
|
|
lvmohba, egenera, egeneracd, dummy, unknown, equal, cslg, shm,
|
|
iscsi,
|
|
ebs, rawhba
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/// <returns>A friendly name for the SR.</returns>
|
|
public override string Name
|
|
{
|
|
get
|
|
{
|
|
return _name(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
|
|
{
|
|
get
|
|
{
|
|
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
|
|
{
|
|
get
|
|
{
|
|
return _name(false);
|
|
}
|
|
}
|
|
|
|
private string _name(bool with_host)
|
|
{
|
|
return I18N("name_label", name_label, with_host);
|
|
}
|
|
|
|
/// <returns>A friendly description for the SR.</returns>
|
|
public override string Description
|
|
{
|
|
get
|
|
{
|
|
return I18N("name_description", name_description, true);
|
|
}
|
|
}
|
|
|
|
public bool Physical
|
|
{
|
|
get
|
|
{
|
|
SRTypes type = GetSRType(false);
|
|
return type == SRTypes.local || (type == SRTypes.udev && SMConfigType == SM_Config_Type_CD);
|
|
}
|
|
}
|
|
|
|
public string SMConfigType
|
|
{
|
|
get { return Get(sm_config, "type"); }
|
|
}
|
|
|
|
public bool IsToolsSR
|
|
{
|
|
get
|
|
{
|
|
return name_label == SR.XenServer_Tools_Label;
|
|
}
|
|
}
|
|
|
|
public string FriendlyTypeName
|
|
{
|
|
get
|
|
{
|
|
return getFriendlyTypeName(GetSRType(false));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A friendly (internationalized) name for the SR type.
|
|
/// </summary>
|
|
public static string getFriendlyTypeName(SRTypes srType)
|
|
{
|
|
return PropertyManager.GetFriendlyName(string.Format("Label-SR.SRTypes-{0}", srType.ToString()),
|
|
"Label-SR.SRTypes-unknown");
|
|
}
|
|
|
|
/// <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 string ConfigType
|
|
{
|
|
get { return Get(sm_config, "type"); }
|
|
}
|
|
|
|
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 = PropertyManager.GetFriendlyName(string.Format("SR.{0}-{1}", field_name, i18n_key));
|
|
return pattern == null ? field_value : pattern;
|
|
}
|
|
else
|
|
{
|
|
string pattern = PropertyManager.GetFriendlyName(string.Format("SR.{0}-{1}-host", field_name, i18n_key));
|
|
return pattern == null ? field_value : string.Format(pattern, hostname);
|
|
}
|
|
}
|
|
|
|
/// <returns>The name of the host to which this SR is attached, or null if the storage is shared
|
|
/// or unattached.</returns>
|
|
private string GetHostName()
|
|
{
|
|
if (Connection == null)
|
|
return null;
|
|
Host host = GetStorageHost();
|
|
return host == null ? null : host.Name;
|
|
}
|
|
|
|
|
|
|
|
/// <returns>The host to which the given SR belongs, or null if the SR is shared or completely disconnected.</returns>
|
|
public Host GetStorageHost()
|
|
{
|
|
if (shared || PBDs.Count != 1)
|
|
return null;
|
|
|
|
PBD pbd = Connection.Resolve(PBDs[0]);
|
|
return pbd == null ? null : Connection.Resolve(pbd.host);
|
|
}
|
|
|
|
public bool IsDetachable()
|
|
{
|
|
return !IsDetached && !HasRunningVMs() && CanCreateWithXenCenter;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Can create with XC, or is citrix storage link gateway. Special case alert!
|
|
/// </summary>
|
|
public bool CanCreateWithXenCenter
|
|
{
|
|
get
|
|
{
|
|
SRTypes type = GetSRType(false);
|
|
return type == SRTypes.iso
|
|
|| type == SRTypes.lvmoiscsi
|
|
|| type == SRTypes.nfs
|
|
|| type == SRTypes.equal
|
|
|| type == SRTypes.netapp
|
|
|| type == SRTypes.lvmohba
|
|
|| type == SRTypes.cslg;
|
|
}
|
|
}
|
|
|
|
public bool IsLocalSR
|
|
{
|
|
get
|
|
{
|
|
SRTypes type = GetSRType(false);
|
|
return type == SRTypes.local
|
|
|| type == SRTypes.ext
|
|
|| type == SRTypes.lvm
|
|
|| type == SRTypes.udev
|
|
|| type == SRTypes.egeneracd
|
|
|| type == SRTypes.dummy;
|
|
}
|
|
}
|
|
|
|
public bool ShowForgetWarning
|
|
{
|
|
get
|
|
{
|
|
return GetSRType(false) != SRTypes.iso;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal helper function. True if all the PBDs for this SR are currently_attached.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private bool AllPBDsAttached()
|
|
{
|
|
return Connection.ResolveAll(this.PBDs).All(pbd => pbd.currently_attached);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal helper function. True if any of the PBDs for this SR is currently_attached.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private bool AnyPBDAttached()
|
|
{
|
|
return Connection.ResolveAll(this.PBDs).Any(pbd => pbd.currently_attached);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if there are any Running or Suspended VMs attached to VDIs on this SR.
|
|
/// </summary>
|
|
/// <param name="connection"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <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 && Connection != null && !IsBroken(false) && AnyPBDAttached();
|
|
}
|
|
|
|
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
|
|
{
|
|
get
|
|
{
|
|
SRTypes t = GetSRType(false);
|
|
return t != SRTypes.dummy && t != SRTypes.ebs && FreeSpace < XenAdmin.Util.BINARY_GIGA / 2;
|
|
}
|
|
}
|
|
|
|
public virtual long FreeSpace
|
|
{
|
|
get
|
|
{
|
|
return physical_size - physical_utilisation;
|
|
}
|
|
}
|
|
|
|
public virtual bool ShowInVDISRList(bool showHiddenVMs)
|
|
{
|
|
if (content_type == Content_Type_ISO)
|
|
return false;
|
|
return Show(showHiddenVMs);
|
|
|
|
}
|
|
|
|
public bool IsDetached
|
|
{
|
|
get
|
|
{
|
|
// SR is detached when it has no PBDs or when all its PBDs are unplugged
|
|
return !HasPBDs || !AnyPBDAttached();
|
|
}
|
|
}
|
|
|
|
public bool HasPBDs
|
|
{
|
|
get
|
|
{
|
|
// 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;
|
|
|
|
// CA-15012 - dont show cd drives of type local on miami (if dont get destroyed properly on upgrade)
|
|
if (GetSRType(false) == SRTypes.local)
|
|
return false;
|
|
|
|
if (showHiddenVMs)
|
|
return true;
|
|
|
|
//CP-2458: hide SRs that were introduced by a DR_task
|
|
if (Helpers.BostonOrGreater(Connection) && introduced_by != null && introduced_by.opaque_ref != Helper.NullOpaqueRef)
|
|
return false;
|
|
|
|
return !IsHidden;
|
|
}
|
|
|
|
public override bool IsHidden
|
|
{
|
|
get
|
|
{
|
|
return BoolKey(other_config, HIDE_FROM_XENCENTER);
|
|
}
|
|
}
|
|
|
|
public const String USE_VHD = "use_vhd";
|
|
|
|
public bool NeedsUpgrading
|
|
{
|
|
get
|
|
{
|
|
SRTypes type = GetSRType(false);
|
|
if (!(type == SRTypes.lvm ||
|
|
type == SRTypes.lvmofc ||
|
|
type == SRTypes.lvmohba ||
|
|
type == SRTypes.lvmoiscsi))
|
|
return false;
|
|
|
|
if (!Helpers.GeorgeOrGreater(Connection))
|
|
return false;
|
|
|
|
return !BoolKey(sm_config, USE_VHD);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The SR is broken when it has the wrong number of PBDs, or (optionally) not all the PBDs are attached.
|
|
/// </summary>
|
|
/// <param name="checkAttached">Whether to check that all the PBDs are attached</param>
|
|
/// <returns></returns>
|
|
public virtual bool IsBroken(bool checkAttached)
|
|
{
|
|
if (PBDs == null || PBDs.Count == 0 ||
|
|
checkAttached && !AllPBDsAttached())
|
|
{
|
|
return true;
|
|
}
|
|
Pool pool = Helpers.GetPoolOfOne(Connection);
|
|
if (pool == null || !shared)
|
|
{
|
|
if (PBDs.Count != 1)
|
|
{
|
|
// There should be exactly one PBD, since this is a non-shared SR
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (PBDs.Count != Connection.Cache.HostCount)
|
|
{
|
|
// There isn't a PBD for each host
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool IsBroken()
|
|
{
|
|
return IsBroken(true);
|
|
}
|
|
|
|
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>
|
|
/// Returns true if a new VM may be created on this SR: the SR supports VDI_CREATE, has the right number of PBDs, and is not full.
|
|
/// </summary>
|
|
/// <param name="myConnection">The IXenConnection whose cache this XenObject belongs to. May not be null.</param>
|
|
/// <returns></returns>
|
|
public bool CanCreateVmOn()
|
|
{
|
|
System.Diagnostics.Trace.Assert(Connection != null, "Connection must not be null");
|
|
|
|
return SupportsVdiCreate() && !IsBroken(false) && !IsFull;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Whether the underlying SR backend supports VDI_CREATE. Will return true even if the SR is full.
|
|
/// </summary>
|
|
/// <param name="connection"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
|
|
Host master = Helpers.GetMaster(Connection);
|
|
if (master == null)
|
|
return false;
|
|
|
|
SM sm = SM.GetByType(Connection, type);
|
|
return sm != null && -1 != Array.IndexOf(sm.capabilities, "VDI_CREATE");
|
|
}
|
|
|
|
/// <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);
|
|
|
|
// If we've got this from an async task result, then it will be wrapped
|
|
// in a <value> element. Parse the contents instead.
|
|
foreach (XmlNode node in doc.GetElementsByTagName("value"))
|
|
{
|
|
xml = node.InnerText;
|
|
doc = new XmlDocument();
|
|
doc.LoadXml(xml);
|
|
break;
|
|
}
|
|
|
|
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));
|
|
/*if (aggr != "")
|
|
results.Add(new SRInfo(uuid, size, aggr));
|
|
else
|
|
results.Add(new SRInfo(uuid, size));*/
|
|
}
|
|
return results;
|
|
}
|
|
|
|
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
|
|
{
|
|
get { 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
|
|
{
|
|
get
|
|
{
|
|
// 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
|
|
{
|
|
get
|
|
{
|
|
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
|
|
{
|
|
get
|
|
{
|
|
//return only the Name for local SRs
|
|
if (Connection != null && !shared)
|
|
{
|
|
return Name;
|
|
}
|
|
|
|
return base.NameWithLocation;
|
|
}
|
|
}
|
|
|
|
internal override string LocationString
|
|
{
|
|
get
|
|
{
|
|
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 Dictionary<String, String> GetDeviceConfig(IXenConnection connection)
|
|
{
|
|
foreach (PBD pbd in connection.ResolveAll(PBDs))
|
|
return pbd.device_config;
|
|
|
|
return null;
|
|
}
|
|
|
|
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 SRInfo(string uuid)
|
|
: this(uuid, 0, "", "", "", false)
|
|
{
|
|
}
|
|
|
|
public SRInfo(string uuid, long size)
|
|
: this(uuid, size, "", "", "", false)
|
|
{
|
|
}
|
|
|
|
public SRInfo(string uuid, long size, string aggr)
|
|
: this(uuid, size, aggr, "", "", false)
|
|
{
|
|
}
|
|
|
|
public SRInfo(string uuid, long size, string aggr, string name, string description, bool poolMetadataDetected)
|
|
{
|
|
UUID = uuid;
|
|
Size = size;
|
|
Aggr = aggr;
|
|
Name = name;
|
|
Description = description;
|
|
PoolMetadataDetected = poolMetadataDetected;
|
|
}
|
|
|
|
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 TypeCIFS
|
|
//{
|
|
// get
|
|
// {
|
|
// if (Connection == null || PBDs.Count == 0)
|
|
// return false;
|
|
// PBD pbd = Connection.Resolve<PBD>(PBDs[0]);
|
|
// if (pbd == null)
|
|
// return false;
|
|
|
|
// return (pbd.device_config.ContainsKey("options") && pbd.device_config["options"].Contains("-t cifs"))
|
|
// || (pbd.device_config.ContainsKey("type") && pbd.device_config["type"] == "cifs");
|
|
// }
|
|
//}
|
|
|
|
public bool MultipathCapable
|
|
{
|
|
get
|
|
{
|
|
return "true" == Get(sm_config, "multipathable");
|
|
}
|
|
}
|
|
|
|
public string Target
|
|
{
|
|
get
|
|
{
|
|
SR sr = Connection.Resolve(new XenRef<SR>(this.opaque_ref));
|
|
if (sr == null)
|
|
return String.Empty;
|
|
|
|
foreach (PBD pbd in sr.Connection.ResolveAll(sr.PBDs))
|
|
{
|
|
SRTypes type = sr.GetSRType(false);
|
|
if ((type == SR.SRTypes.netapp || type == SR.SRTypes.lvmoiscsi || type == SR.SRTypes.equal) && pbd.device_config.ContainsKey("target")) // netapp or iscsi
|
|
{
|
|
return pbd.device_config["target"];
|
|
}
|
|
else if (type == SR.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;
|
|
}
|
|
else if (type == SR.SRTypes.nfs && pbd.device_config.ContainsKey("server"))
|
|
{
|
|
return pbd.device_config["server"];
|
|
}
|
|
}
|
|
|
|
return String.Empty;
|
|
}
|
|
}
|
|
|
|
public SrProvisioning Provisioning
|
|
{
|
|
get
|
|
{
|
|
string allocation = Get(sm_config, "allocation");
|
|
return string.IsNullOrEmpty(allocation) || allocation == "thick"
|
|
? SrProvisioning.Thick
|
|
: SrProvisioning.Thin;
|
|
}
|
|
}
|
|
|
|
public Icons GetIcon
|
|
{
|
|
get
|
|
{
|
|
if (!HasPBDs || IsHidden)
|
|
{
|
|
return Icons.StorageDisabled;
|
|
}
|
|
else if (IsDetached || IsBroken() || !MultipathAOK)
|
|
{
|
|
return Icons.StorageBroken;
|
|
}
|
|
else if (NeedsUpgrading)
|
|
{
|
|
return Icons.StorageNeedsUpgrading;
|
|
}
|
|
else if (SR.IsDefaultSr(this))
|
|
{
|
|
return Icons.StorageDefault;
|
|
}
|
|
else
|
|
{
|
|
return Icons.Storage;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The amount of memory used as compared to the available and allocated amounts as a friendly string
|
|
/// </summary>
|
|
public String SizeString
|
|
{
|
|
get
|
|
{
|
|
return string.Format(Messages.SR_SIZE_USED,
|
|
Util.DiskSizeString(physical_utilisation),
|
|
Util.DiskSizeString(physical_size),
|
|
Util.DiskSizeString(virtual_allocation));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A friendly string indicating whether the sr is detached/broken/multipath failing/needs upgrade/ok
|
|
/// </summary>
|
|
public String StatusString
|
|
{
|
|
get
|
|
{
|
|
if (!HasPBDs)
|
|
return Messages.DETACHED;
|
|
|
|
if (IsDetached || IsBroken())
|
|
return Messages.GENERAL_SR_STATE_BROKEN;
|
|
|
|
if (!MultipathAOK)
|
|
return Messages.GENERAL_MULTIPATH_FAILURE;
|
|
|
|
if (NeedsUpgrading)
|
|
return Messages.UPGRADE_SR_WARNING;
|
|
|
|
return Messages.GENERAL_SR_STATE_OK;
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Returns true when there is a pbd containing adapterid else false
|
|
/// </summary>
|
|
public bool CanRepairAfterUpgradeFromLegacySL
|
|
{
|
|
get
|
|
{
|
|
if (type == "cslg")
|
|
{
|
|
var pbds = Connection.ResolveAll(PBDs);
|
|
if (pbds != null)
|
|
{
|
|
return pbds.Any(pbd => pbd.device_config.ContainsKey("adapterid"));
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
public StorageLinkRepository StorageLinkRepository(IEnumerable<StorageLinkConnection> connections)
|
|
{
|
|
if (type == "cslg")
|
|
{
|
|
foreach (StorageLinkConnection slCon in connections)
|
|
{
|
|
foreach (StorageLinkRepository r in slCon.Cache.StorageRespositories)
|
|
{
|
|
if (r.opaque_ref == uuid)
|
|
{
|
|
return r;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether SR supports database replication.
|
|
/// </summary>
|
|
public bool SupportsDatabaseReplication()
|
|
{
|
|
return SupportsDatabaseReplication(Connection, this);
|
|
}
|
|
|
|
|
|
/// <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>
|
|
/// Is an iSL type or legacy iSl adpater type
|
|
/// </summary>
|
|
/// <param name="sr"></param>
|
|
/// <returns></returns>
|
|
public static bool IsIslOrIslLegacy(SR sr)
|
|
{
|
|
SRTypes currentType = sr.GetSRType(true);
|
|
return currentType == SRTypes.cslg || currentType == SRTypes.equal || currentType == SRTypes.netapp;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the underlying SR backend supports SR_TRIM
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public bool SupportsTrim
|
|
{
|
|
get
|
|
{
|
|
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");
|
|
}
|
|
}
|
|
|
|
#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
|
|
}
|
|
|
|
public enum SrProvisioning
|
|
{
|
|
Thick,
|
|
Thin
|
|
}
|
|
}
|