/* 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.Xml; using XenAdmin.Core; using XenAdmin.Network; using XenAPI; namespace XenAdmin.Actions { /// /// Asks the server for a list of NetApp aggregates and SRs on a given filer. /// public class SrScanAction : AsyncAction { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); private readonly string hostname; private readonly string username; private readonly string password; private readonly SR.SRTypes type; private List srs; private List aggregates; private List storagePools; public SrScanAction(IXenConnection connection, string hostname, string username, string password, SR.SRTypes type) : base(connection, String.Format(Messages.ACTION_SR_SCAN_NAME, hostname), String.Format(Messages.ACTION_SR_SCAN_DESCRIPTION, SR.GetFriendlyTypeName(type), hostname), true) { this.hostname = hostname; this.username = username; this.password = password; this.type = type; } /// /// Will be null if the scan has not yet successfully returned. /// public List SRs { get { return srs; } } /// /// Will be null if the scan has not yet successfully returned. /// public List Aggregates { get { return aggregates; } } /// /// Will be null if the scan has not yet successfully returned. /// public List StoragePools { get { return storagePools; } } protected override void Run() { // Fill in dictionary params Dictionary dconf = new Dictionary(); dconf.Add("target", hostname); dconf.Add("username", username); dconf.Add("password", password); log.DebugFormat("Attempting to find SRs on {0} filer {1}.", type, hostname); RelatedTask = XenAPI.SR.async_probe(Session, Helpers.GetCoordinator(Connection).opaque_ref, dconf, type.ToString(), new Dictionary()); this.PollToCompletion(); srs = XenAPI.SR.ParseSRListXML(this.Result); log.DebugFormat("Attempting to find aggregates on {0} filer {1}.", type, hostname); try { RelatedTask = XenAPI.SR.async_create(Session, hostname, dconf, 0, Helpers.GuiTempObjectPrefix, "", type.ToString(), "", true, new Dictionary()); this.PollToCompletion(50, 100); throw new BadServerResponse(hostname); } catch (Failure exn) { if (exn.ErrorDescription.Count < 1) throw new BadServerResponse(hostname); // We expect a particular sort of failure, whose error details contain // the list of aggregates on the filer in XML. switch (exn.ErrorDescription[0]) { case "SR_BACKEND_FAILURE_123": // We want a custom error if the server returns no iqns. if (exn.ErrorDescription.Count < 4 || exn.ErrorDescription[3].Length == 0) break; aggregates = ParseAggregateXML(exn.ErrorDescription[3], hostname); break; case "SR_BACKEND_FAILURE_163": // We want a custom error if the server returns no iqns. if (exn.ErrorDescription.Count < 4 || exn.ErrorDescription[3].Length == 0) break; storagePools = ParseDellStoragePoolsXML(exn.ErrorDescription[3], hostname); break; default: throw; } if (ListIsNullOrEmpty(srs) && ListIsNullOrEmpty(aggregates) && ListIsNullOrEmpty(storagePools)) throw new NoExistingAndNowhereToCreateException(hostname); } } public static bool ListIsNullOrEmpty(List list) { return list == null || list.Count <= 0; } private static List ParseAggregateXML(String xml, String hostname) { List results = new List(); try { // Parse XML XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); foreach (XmlNode aggr in doc.GetElementsByTagName("Aggr")) { string name = ""; long size = -1; int disks = -1; string raidType = ""; bool asisCapable = false; foreach (XmlNode info in aggr.ChildNodes) { // use name for build < 7040, aggregate after this if (info.Name.ToLowerInvariant() == "name" || info.Name.ToLowerInvariant() == "aggregate") { name = info.InnerText.Trim(); } else if (info.Name.ToLowerInvariant() == "size") { size = long.Parse(info.InnerText.Trim(), System.Globalization.CultureInfo.InvariantCulture); } else if (info.Name.ToLowerInvariant() == "disks") { disks = int.Parse(info.InnerText.Trim(), System.Globalization.CultureInfo.InvariantCulture); } else if (info.Name.ToLowerInvariant() == "raidtype") { raidType = info.InnerText.Trim(); } else if (info.Name.ToLowerInvariant() == "asis_dedup") { asisCapable = bool.Parse(info.InnerText.Trim().ToLowerInvariant()); } } results.Add(new NetAppAggregate(name, size, disks, raidType, asisCapable)); } results.Sort(); } catch { throw new BadServerResponse(hostname); } return results; } private static List ParseDellStoragePoolsXML(String xml, String hostname) { List results = new List(); try { // Parse XML XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); foreach (XmlNode aggr in doc.GetElementsByTagName("StoragePool")) { String Name = ""; bool Default = false; int Members = 0; int Volumes = 0; long Capacity = 0; long Freespace = 0; foreach (XmlNode info in aggr.ChildNodes) { switch(info.Name.ToLowerInvariant()) { case "name": Name = info.InnerText.Trim(); break; case "default": bool.TryParse(info.InnerText.Trim(), out Default); break; case "members": int.TryParse(info.InnerText.Trim(), out Members); break; case "volumes": int.TryParse(info.InnerText.Trim(), out Volumes); break; case "capacity": long.TryParse(info.InnerText.Trim(), out Capacity); break; case "freespace": long.TryParse(info.InnerText.Trim(), out Freespace); break; } } results.Add(new DellStoragePool(Name, Default, Members, Volumes, Capacity, Freespace)); } results.Sort(); } catch { throw new BadServerResponse(hostname); } return results; } } public struct NetAppAggregate : IComparable { public readonly string Name; public readonly long Size; public readonly int Disks; public readonly string RaidType; public readonly bool AsisCapable; public NetAppAggregate(string name, long size, int disks, string raidType, bool asisCapable) { this.Name = name; this.Size = size; this.Disks = disks; this.RaidType = raidType; this.AsisCapable = asisCapable; } public int CompareTo(NetAppAggregate other) { return Name.CompareTo(other.Name); } } public struct DellStoragePool : IComparable { public readonly String Name; public readonly bool Default; public readonly int Members; public readonly int Volumes; public readonly long Capacity; public readonly long Freespace; public DellStoragePool(String Name, bool Default, int Members, int Volumes, long Capacity, long Freespace) { this.Name = Name; this.Default = Default; this.Members = Members; this.Volumes = Volumes; this.Capacity = Capacity; this.Freespace = Freespace; } public int CompareTo(DellStoragePool other) { return Name.CompareTo(other.Name); } } public class NoExistingAndNowhereToCreateException : Exception { private String host; public NoExistingAndNowhereToCreateException(String host) { this.host = host; } public override string Message { get { return Messages.NEWSR_NOWHERE_TO_CREATE; } } } }