/* 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.Threading; using System.Windows.Forms; using XenAdmin.Actions; using XenAdmin.Core; using XenAdmin.Network; using XenAPI; namespace XenAdmin.Dialogs { public partial class IscsiDeviceConfigDialog : XenDialogBase { private ISCSIPopulateLunsAction IscsiPopulateLunsAction; private ISCSIPopulateIQNsAction IscsiPopulateIqnsAction; private bool LunInUse = false; private readonly Dictionary LunMap = new Dictionary(); private readonly ToolTip TargetIqnToolTip = new ToolTip(); private readonly ToolTip TargetLunToolTip = new ToolTip(); public IscsiDeviceConfigDialog() { InitializeComponent(); } public IscsiDeviceConfigDialog(IXenConnection connection) : base(connection) { InitializeComponent(); labelIscsiInvalidHost.Visible = false; // IQN's can be very long, so we will show the value as a mouse over tooltip. // Initialize the tooltip here. TargetIqnToolTip.Active = true; TargetIqnToolTip.AutomaticDelay = 0; TargetIqnToolTip.AutoPopDelay = 50000; TargetIqnToolTip.InitialDelay = 50; TargetIqnToolTip.ReshowDelay = 50; TargetIqnToolTip.ShowAlways = true; // Initialize LUN's tooltip here. TargetLunToolTip.Active = true; TargetLunToolTip.AutomaticDelay = 0; TargetLunToolTip.AutoPopDelay = 50000; TargetLunToolTip.InitialDelay = 50; TargetLunToolTip.ReshowDelay = 50; TargetLunToolTip.ShowAlways = true; } private void textBoxIscsiHost_TextChanged(object sender, EventArgs e) { labelIscsiInvalidHost.Visible = false; IScsiParams_TextChanged(null, null); } private void IscsiUseChapCheckBox_CheckedChanged(object sender, System.EventArgs e) { groupBoxChap.Enabled = IscsiUseChapCheckBox.Checked; foreach (Control c in groupBoxChap.Controls) { c.Enabled = IscsiUseChapCheckBox.Checked; } lunInUseLabel.Text = ""; ChapSettings_Changed(null, null); } /// /// Called when any of the iSCSI filer params change: resets the IQNs/LUNs. /// Must be called on the event thread. /// private void IScsiParams_TextChanged(object sender, EventArgs e) { Program.AssertOnEventThread(); // User has changed filer hostname/username/password - clear IQN/LUN boxes comboBoxIscsiIqns.Items.Clear(); comboBoxIscsiIqns.Enabled = false; // Cancel pending IQN/LUN scans if (IscsiPopulateIqnsAction != null) { IscsiPopulateIqnsAction.Cancel(); } ChapSettings_Changed(null, null); } private void ChapSettings_Changed(object sender, EventArgs e) { comboBoxIscsiLuns.Items.Clear(); comboBoxIscsiLuns.Text = ""; comboBoxIscsiLuns.Enabled = false; if (IscsiPopulateLunsAction != null) { IscsiPopulateLunsAction.Cancel(); } UpdateButtons(); } private void UpdateButtons() { UInt16 i; bool portValid = UInt16.TryParse(textBoxIscsiPort.Text, out i); buttonIscsiPopulateIQNs.Enabled = !String.IsNullOrEmpty(getIscsiHost()) && portValid; buttonIscsiPopulateLUNs.Enabled = !String.IsNullOrEmpty(getIscsiHost()) && !String.IsNullOrEmpty(getIscsiIQN()) && portValid; buttonOk.Enabled = !String.IsNullOrEmpty(getIscsiLUN()) && !LunInUse; } private String getIscsiHost() { // If the user has selected an IQN, use the host from that IQN (due to multi-homing, // this may differ from the host they first entered). Otherwise use the host // they first entered, ToStringWrapper wrapper = comboBoxIscsiIqns.SelectedItem as ToStringWrapper; if (wrapper != null) return wrapper.item.IpAddress; return textBoxIscsiHost.Text.Trim(); } private UInt16 getIscsiPort() { ToStringWrapper wrapper = comboBoxIscsiIqns.SelectedItem as ToStringWrapper; if (wrapper != null) return wrapper.item.Port; // No combobox item was selected UInt16 port; if (UInt16.TryParse(textBoxIscsiPort.Text, out port)) { return port; } return Util.DEFAULT_ISCSI_PORT; } private String getIscsiIQN() { ToStringWrapper wrapper = comboBoxIscsiIqns.SelectedItem as ToStringWrapper; if (wrapper == null) return ""; return wrapper.item.TargetIQN; } private String getIscsiLUN() { return comboBoxIscsiLuns.Text; } private void comboBoxIscsiIqns_SelectedIndexChanged(object sender, EventArgs e) { ToStringWrapper wrapper = comboBoxIscsiIqns.SelectedItem as ToStringWrapper; // Keep the IScsiTargetIqnComboBox tooltip in sync with the selected item if (wrapper != null) { TargetIqnToolTip.SetToolTip(comboBoxIscsiIqns, wrapper.ToString()); } else { TargetIqnToolTip.SetToolTip(comboBoxIscsiIqns, ""); } // Clear the LUN map and ComboBox because the user has changed the IQN. The user // must re-discover the LUNs for the new IQN. ClearLunMapAndCombo(); } private void comboBoxIscsiLuns_SelectedIndexChanged(object sender, EventArgs e) { var selectedLun = comboBoxIscsiLuns.SelectedItem as string; // Keep the tooltip in sync with the selected item if (selectedLun != null) { TargetLunToolTip.SetToolTip(comboBoxIscsiLuns, selectedLun); } else { TargetLunToolTip.SetToolTip(comboBoxIscsiLuns, ""); } try { SR sr = UniquenessCheck(ConnectionsManager.XenConnectionsCopy); // LUN is not in use if sr != null LunInUse = sr != null; if (sr == null) { lunInUseLabel.Text = ""; return; } Pool pool = Helpers.GetPool(sr.Connection); if (pool != null) { lunInUseLabel.Text = String.Format(Messages.NEWSR_LUN_IN_USE_ON_POOL, sr.Name, pool.Name); return; } Host master = Helpers.GetMaster(sr.Connection); if (master != null) { lunInUseLabel.Text = String.Format(Messages.NEWSR_LUN_IN_USE_ON_SERVER, sr.Name, master.Name); return; } lunInUseLabel.Text = Messages.NEWSR_LUN_IN_USE; } finally { UpdateButtons(); } } private void ClearLunMapAndCombo() { // Clear LUNs as they may no longer be valid comboBoxIscsiLuns.Items.Clear(); comboBoxIscsiLuns.Text = ""; comboBoxIscsiLuns.Enabled = false; LunMap.Clear(); LunInUse = false; lunInUseLabel.Text = ""; UpdateButtons(); } /// /// Check the current config of the iSCSI sr in the wizard is unique across /// all active connections. /// /// SR that uses this config if not unique, null if unique private SR UniquenessCheck(List connections) { // Check currently selected lun is unique amongst other connected hosts. foreach (IXenConnection connection in connections) { foreach (SR sr in connection.Cache.SRs) { if (sr.GetSRType(false) != SR.SRTypes.lvmoiscsi) continue; if (sr.PBDs.Count < 1) continue; PBD pbd = connection.Resolve(sr.PBDs[0]); if (pbd == null) continue; if (UniquenessCheckMiami(connection, pbd)) return sr; } } return null; } /// /// Check currently LUN against miami host /// /// /// /// private bool UniquenessCheckMiami(IXenConnection connection, PBD pbd) { if (!pbd.device_config.ContainsKey(SCSIID)) return false; String scsiID = pbd.device_config[SCSIID]; String myLUN = getIscsiLUN(); if (!LunMap.ContainsKey(myLUN)) return false; ISCSIInfo info = LunMap[myLUN]; return info.ScsiID == scsiID; } private const String TARGET = "target"; private const String PORT = "port"; private const String TARGETIQN = "targetIQN"; private const String LUNSERIAL = "LUNSerial"; private const String SCSIID = "SCSIid"; private const String LUNID = "LUNid"; private const String CHAPUSER = "chapuser"; private const String CHAPPASSWORD = "chappassword"; public Dictionary DeviceConfig { get { Dictionary dconf = new Dictionary(); ToStringWrapper iqn = comboBoxIscsiIqns.SelectedItem as ToStringWrapper; if (iqn == null) return null; // Reset target IP address to home address specified in IQN scan. // Allows multi-homing - see CA-11607 dconf[TARGET] = iqn.item.IpAddress; dconf[PORT] = iqn.item.Port.ToString(); dconf[TARGETIQN] = getIscsiIQN(); if (!LunMap.ContainsKey(getIscsiLUN())) return null; ISCSIInfo info = LunMap[getIscsiLUN()]; if (info.LunID == -1) { dconf[LUNSERIAL] = info.Serial; } else { dconf[SCSIID] = info.ScsiID; } if (IscsiUseChapCheckBox.Checked) { dconf[CHAPUSER] = IScsiChapUserTextBox.Text; dconf[CHAPPASSWORD] = IScsiChapSecretTextBox.Text; } return dconf; } } private void buttonIscsiPopulateIQNs_Click(object sender, EventArgs e) { buttonIscsiPopulateIQNs.Enabled = false; // For this button to be enabled, we must be Miami or newer comboBoxIscsiIqns.Items.Clear(); // Clear LUNs as they may no longer be valid ClearLunMapAndCombo(); // Disable the Discover LUNs button because we have no IQNs buttonIscsiPopulateLUNs.Enabled = false; // Cancel any LUN scan in progress, as it is no longer meaningful if (IscsiPopulateLunsAction != null) { IscsiPopulateLunsAction.Cancel(); } if (IscsiUseChapCheckBox.Checked) { IscsiPopulateIqnsAction = new ISCSIPopulateIQNsAction(connection, getIscsiHost(), getIscsiPort(), IScsiChapUserTextBox.Text, IScsiChapSecretTextBox.Text); } else { IscsiPopulateIqnsAction = new ISCSIPopulateIQNsAction(connection, getIscsiHost(), getIscsiPort(), null, null); } IscsiPopulateIqnsAction.Completed += IscsiPopulateIqnsAction_Completed; Dialogs.ActionProgressDialog dialog = new Dialogs.ActionProgressDialog( IscsiPopulateIqnsAction, ProgressBarStyle.Marquee); dialog.ShowCancel = true; dialog.ShowDialog(this); } private void IscsiPopulateIqnsAction_Completed(ActionBase sender) { Program.Invoke(this, (System.Threading.WaitCallback)IscsiPopulateIqnsAction_Completed_, sender); } private void IscsiPopulateIqnsAction_Completed_(object o) { Program.AssertOnEventThread(); ISCSIPopulateIQNsAction action = (ISCSIPopulateIQNsAction)o; if (action.Succeeded) { if (action.IQNs.Length == 0) { // Do nothing: ActionProgressDialog will show Messages.NEWSR_NO_IQNS_FOUND } else { int width = comboBoxIscsiIqns.Width; foreach (Actions.IScsiIqnInfo iqnInfo in action.IQNs) { if (!String.IsNullOrEmpty(iqnInfo.TargetIQN)) { String toString = String.Format("{0} ({1}:{2})", iqnInfo.TargetIQN, iqnInfo.IpAddress, iqnInfo.Port); comboBoxIscsiIqns.Items.Add(new ToStringWrapper(iqnInfo, toString)); width = Math.Max(width, Drawing.MeasureText(toString, comboBoxIscsiIqns.Font).Width); } } // Set the combo box dropdown width to accommodate the widest item (within reason) comboBoxIscsiIqns.DropDownWidth = Math.Min(width, Int16.MaxValue); if (comboBoxIscsiIqns.Items.Count > 0) { comboBoxIscsiIqns.SelectedItem = comboBoxIscsiIqns.Items[0]; comboBoxIscsiIqns.Enabled = true; buttonIscsiPopulateLUNs.Enabled = true; } } } else { Failure failure = action.Exception as Failure; if (failure != null && failure.ErrorDescription[0] == "SR_BACKEND_FAILURE_140") { labelIscsiInvalidHost.Visible = true; textBoxIscsiHost.Focus(); } } buttonIscsiPopulateIQNs.Enabled = true; } private void buttonIscsiPopulateLUNs_Click(object sender, EventArgs e) { buttonIscsiPopulateLUNs.Enabled = false; comboBoxIscsiLuns.Items.Clear(); LunMap.Clear(); if (IscsiUseChapCheckBox.Checked) { IscsiPopulateLunsAction = new Actions.ISCSIPopulateLunsAction(connection, getIscsiHost(), getIscsiPort(), getIscsiIQN(), IScsiChapUserTextBox.Text, IScsiChapSecretTextBox.Text); } else { IscsiPopulateLunsAction = new Actions.ISCSIPopulateLunsAction(connection, getIscsiHost(), getIscsiPort(), getIscsiIQN(), null, null); } IscsiPopulateLunsAction.Completed += IscsiPopulateLunsAction_Completed; ActionProgressDialog dialog = new ActionProgressDialog(IscsiPopulateLunsAction, ProgressBarStyle.Marquee); dialog.ShowCancel = true; dialog.ShowDialog(this); } private void IscsiPopulateLunsAction_Completed(ActionBase sender) { Program.Invoke(this, (WaitCallback)IscsiPopulateLunsAction_Completed_, sender); } private void IscsiPopulateLunsAction_Completed_(object o) { Program.AssertOnEventThread(); ISCSIPopulateLunsAction action = (ISCSIPopulateLunsAction)o; buttonIscsiPopulateLUNs.Enabled = true; if (!action.Succeeded) { Failure failure = action.Exception as Failure; if (failure != null && failure.ErrorDescription[0] == "SR_BACKEND_FAILURE_140") { labelIscsiInvalidHost.Visible = true; textBoxIscsiHost.Focus(); } return; } if (action.LUNs.Length == 0) { // Do nothing: ActionProgressDialog will show Messages.NEWSR_NO_LUNS_FOUND } else { int width = comboBoxIscsiLuns.Width; foreach (Actions.ISCSIInfo i in action.LUNs) { String label = "LUN"; if (i.LunID != -1) label += String.Format(" {0}", i.LunID); if (i.Serial != "") label += String.Format(": {0}", i.Serial); if (i.Size >= 0) label += String.Format(": {0}", Util.DiskSizeString(i.Size)); if (i.Vendor != "") label += String.Format(" ({0})", i.Vendor); comboBoxIscsiLuns.Items.Add(label); LunMap.Add(label, i); width = Math.Max(width, Drawing.MeasureText(label, comboBoxIscsiLuns.Font).Width); } comboBoxIscsiLuns.SelectedItem = comboBoxIscsiLuns.Items[0]; comboBoxIscsiLuns.Enabled = true; // Set the combo box dropdown width to accommodate the widest item (within reason) comboBoxIscsiLuns.DropDownWidth = Math.Min(width, Int16.MaxValue); } UpdateButtons(); } } }