/* 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.Net; using System.Net.Sockets; using System.Xml; using XenAdmin.Core; using XenAdmin.Network; using XenAPI; using System.Globalization; using XenCenterLib; namespace XenAdmin.Actions { /// /// Ask the server for a list of IQNs on a particular iSCSI target. /// public class ISCSIPopulateIQNsAction : AsyncAction { protected readonly string targetHost; protected readonly UInt16 targetPort; protected readonly string chapUsername; protected readonly string chapPassword; protected IScsiIqnInfo[] _iqns; /// /// Will be null if the scan has not yet successfully returned. /// public IScsiIqnInfo[] IQNs { get { return _iqns; } } public ISCSIPopulateIQNsAction(IXenConnection connection, string targetHost, UInt16 targetPort, string chapUsername, string chapPassword) : base(connection, string.Format(Messages.ACTION_ISCSI_IQN_SCANNING, targetHost), null, true) { this.targetHost = targetHost; this.targetPort = targetPort; this.chapUsername = chapUsername; this.chapPassword = chapPassword; } public class NoIQNsFoundException : Exception { private readonly string host; public NoIQNsFoundException(string host) { this.host = host; } public override string Message { get { return String.Format(Messages.NEWSR_NO_IQNS_FOUND, host); } } } private const string UriPrefix = "http://"; private string ParseIPAddress(string address) { Uri url; if (Uri.TryCreate(string.Format("{0}{1}", UriPrefix, address), UriKind.Absolute, out url)) { IPAddress ip; if (IPAddress.TryParse(url.Host, out ip)) { if (ip.AddressFamily == AddressFamily.InterNetworkV6) return "[" + ip + "]"; return ip.ToString(); } } return address; } protected override void Run() { Pool pool = Helpers.GetPoolOfOne(Connection); if (pool == null) throw new Failure(Failure.INTERNAL_ERROR, Messages.POOL_GONE); Dictionary settings = new Dictionary(); settings["target"] = targetHost; settings["port"] = targetPort.ToString(CultureInfo.InvariantCulture); if (!string.IsNullOrEmpty(chapUsername)) { settings["chapuser"] = chapUsername; settings["chappassword"] = chapPassword; } try { // Perform a create with some missing params: should fail with the error // containing the list of SRs on the filer. RelatedTask = SR.async_create(Session, pool.master, settings, 0, Helpers.GuiTempObjectPrefix, Messages.ISCSI_SHOULD_NO_BE_CREATED, SR.SRTypes.lvmoiscsi.ToString(), "user", true, new Dictionary()); this.PollToCompletion(); // Create should always fail and never get here throw new InvalidOperationException(Messages.ISCSI_FAIL); } catch (Failure exn) { if (exn.ErrorDescription.Count < 1) throw new BadServerResponse(targetHost); // We expect an SR_BACKEND_FAILURE_96 error, with a message from // xapi, stdout, and then stderr. // stderr will be an XML-encoded description of the iSCSI IQNs. if (exn.ErrorDescription[0] != "SR_BACKEND_FAILURE_96") throw; // We want a custom error if the server returns no aggregates. if (exn.ErrorDescription.Count < 4 || exn.ErrorDescription[3].Length == 0) throw new NoIQNsFoundException(targetHost); XmlDocument doc = new XmlDocument(); List results = new List(); try { doc.LoadXml(exn.ErrorDescription[3]); foreach (XmlNode targetListNode in doc.GetElementsByTagName("iscsi-target-iqns")) { foreach (XmlNode targetNode in targetListNode.ChildNodes) { int index = -1; string address = null; UInt16 port = Util.DEFAULT_ISCSI_PORT; string targetIQN = null; foreach (XmlNode infoNode in targetNode.ChildNodes) { if (infoNode.Name.ToLowerInvariant() == "index") { index = int.Parse(infoNode.InnerText, CultureInfo.InvariantCulture); } else if (infoNode.Name.ToLowerInvariant() == "ipaddress") { string addr = infoNode.InnerText.Trim(); address = ParseIPAddress(addr); } else if (infoNode.Name.ToLowerInvariant() == "port") { port = UInt16.Parse(infoNode.InnerText, CultureInfo.InvariantCulture); } else if (infoNode.Name.ToLowerInvariant() == "targetiqn") { targetIQN = infoNode.InnerText.Trim(); } } results.Add(new IScsiIqnInfo(index, targetIQN, address, port)); } } results.Sort(); _iqns = results.ToArray(); } catch { throw new BadServerResponse(targetHost); } if (_iqns.Length < 1) throw new NoIQNsFoundException(targetHost); } } } public class Gfs2PopulateIQNsAction : ISCSIPopulateIQNsAction { public Gfs2PopulateIQNsAction(IXenConnection connection, string targetHost, UInt16 targetPort, string chapUsername, string chapPassword) : base(connection, targetHost, targetPort, chapUsername, chapPassword) { } protected override void Run() { Pool pool = Helpers.GetPoolOfOne(Connection); if (pool == null) throw new Failure(Failure.INTERNAL_ERROR, Messages.POOL_GONE); var deviceConfig = new Dictionary(); deviceConfig["provider"] = "iscsi"; deviceConfig["target"] = targetHost; deviceConfig["port"] = targetPort.ToString(CultureInfo.InvariantCulture); if (!string.IsNullOrEmpty(chapUsername)) { deviceConfig["chapuser"] = chapUsername; deviceConfig["chappassword"] = chapPassword; } var probeResults = SR.probe_ext(Session, pool.master.opaque_ref, deviceConfig, SR.SRTypes.gfs2.ToString(), new Dictionary()); var results = new List(); var index = -1; foreach (var probeResult in probeResults) { index++; var address = probeResult.configuration.ContainsKey("target") ? probeResult.configuration["target"] : ""; UInt16 port; if (!probeResult.configuration.ContainsKey("port") || !UInt16.TryParse(probeResult.configuration["port"], out port)) port = Util.DEFAULT_ISCSI_PORT; var targetIQN = probeResult.configuration.ContainsKey("targetIQN") ? probeResult.configuration["targetIQN"] : ""; results.Add(new IScsiIqnInfo(index, targetIQN, address, port)); } results.Sort(); _iqns = results.ToArray(); if (_iqns.Length < 1) throw new NoIQNsFoundException(targetHost); } } public struct IScsiIqnInfo : IComparable, IEquatable { public readonly int Index; /// /// May be null. /// public readonly string TargetIQN; /// /// May be null. /// public readonly string IpAddress; public readonly UInt16 Port; public IScsiIqnInfo(int index, string targetIQN, string ipAddress, UInt16 port) { Index = index; TargetIQN = targetIQN; IpAddress = ipAddress; Port = port; } public int CompareTo(IScsiIqnInfo other) { // Special case: * goes at the end if (TargetIQN == "*" && other.TargetIQN != "*") return 1; if (other.TargetIQN == "*" && TargetIQN != "*") return -1; // Sort by the TargetIQN (not the Index: see CA-40066) return StringUtility.NaturalCompare(TargetIQN, other.TargetIQN); } public bool Equals(IScsiIqnInfo other) { return this.Index == other.Index && this.TargetIQN == other.TargetIQN && this.IpAddress == other.IpAddress && this.Port == other.Port; } } }