xenadmin/XenAdmin/Dialogs/IscsiDeviceConfigDialog.cs

540 lines
20 KiB
C#
Raw Normal View History

/* 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<String, ISCSIInfo> LunMap = new Dictionary<String, ISCSIInfo>();
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);
}
/// <summary>
/// Called when any of the iSCSI filer params change: resets the IQNs/LUNs.
/// Must be called on the event thread.
/// </summary>
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<IScsiIqnInfo> wrapper = comboBoxIscsiIqns.SelectedItem as ToStringWrapper<IScsiIqnInfo>;
if (wrapper != null)
return wrapper.item.IpAddress;
return textBoxIscsiHost.Text.Trim();
}
private UInt16 getIscsiPort()
{
ToStringWrapper<IScsiIqnInfo> wrapper = comboBoxIscsiIqns.SelectedItem as ToStringWrapper<IScsiIqnInfo>;
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<IScsiIqnInfo> wrapper = comboBoxIscsiIqns.SelectedItem as ToStringWrapper<IScsiIqnInfo>;
if (wrapper == null)
return "";
return wrapper.item.TargetIQN;
}
private String getIscsiLUN()
{
return comboBoxIscsiLuns.Text;
}
private void comboBoxIscsiIqns_SelectedIndexChanged(object sender, EventArgs e)
{
ToStringWrapper<IScsiIqnInfo> wrapper = comboBoxIscsiIqns.SelectedItem as ToStringWrapper<IScsiIqnInfo>;
// 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();
}
/// <summary>
/// Check the current config of the iSCSI sr in the wizard is unique across
/// all active connections.
/// </summary>
/// <returns>SR that uses this config if not unique, null if unique</returns>
private SR UniquenessCheck(List<IXenConnection> 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;
}
/// <summary>
/// Check currently LUN against miami host
/// </summary>
/// <param name="connection"></param>
/// <param name="pbd"></param>
/// <returns></returns>
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<String, String> DeviceConfig
{
get
{
Dictionary<String, String> dconf = new Dictionary<String, String>();
ToStringWrapper<IScsiIqnInfo> iqn = comboBoxIscsiIqns.SelectedItem as ToStringWrapper<IScsiIqnInfo>;
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<IScsiIqnInfo>(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;
using (var 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();
}
}
}