/* 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.Windows.Forms;
using XenAPI;
using XenAdmin.Actions;
using XenAdmin.Network;
using XenAdmin.Core;
using XenAdmin.Controls;
using XenAdmin.Dialogs;
using System.Linq;
using XenAdmin.Dialogs.WarningDialogs;
using XenCenterLib;
namespace XenAdmin.Wizards.NewSRWizard_Pages.Frontends
{
public partial class LVMoISCSI : XenTabPage
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
///
/// Non-null value indicates an SR already existing on this LUN that should be reattached.
/// Null indicates the LUN should be formatted into a new SR.
///
private SR.SRInfo _srToIntroduce;
private ISCSIPopulateLunsAction _populateLunsAction;
private ISCSIPopulateIQNsAction _populateIqnsAction;
private readonly Dictionary LunMap = new Dictionary();
///
/// Tooltip to show IQN value (as it can be very long)
///
private readonly ToolTip toolTipTargetIqn = new ToolTip
{
Active = true,
AutomaticDelay = 0,
AutoPopDelay = 50000,
InitialDelay = 50,
ReshowDelay = 50,
ShowAlways = true
};
private bool _errorExists;
private bool _buttonNextEnabled;
private const string PROVIDER = "provider";
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 CHAPUSER = "chapuser";
private const string CHAPPASSWORD = "chappassword";
public LVMoISCSI()
{
InitializeComponent();
SrType = SR.SRTypes.lvmoiscsi;
HideErrors();
}
#region XentabPage overrides
public override string PageTitle => Messages.NEWSR_PATH_ISCSI;
public override string Text => Messages.NEWSR_LOCATION;
public override string HelpID => "Location_ISCSI";
protected override void PageLoadedCore(PageLoadedDirection direction)
{
HelpersGUI.PerformIQNCheck();
if (direction == PageLoadedDirection.Forward)
{
textBoxIscsiHost.Focus();
ResetAll();
}
}
protected override void PageLeaveCore(PageLoadedDirection direction, ref bool cancel)
{
if (direction == PageLoadedDirection.Back)
return;
HideErrors();
Host coordinator = Helpers.GetCoordinator(Connection);
if (coordinator == null)
{
cancel = true;
return;
}
var currentSrType = SrType;
if (!RunProbe(coordinator, currentSrType, out var srs)) // start first probe
{
cancel = true;
return;
}
var performSecondProbe = Helpers.KolkataOrGreater(Connection) && !Helpers.FeatureForbidden(Connection, Host.CorosyncDisabled);
if (performSecondProbe && srs.Count == 0)
{
currentSrType = SrType == SR.SRTypes.gfs2 ? SR.SRTypes.lvmoiscsi : SR.SRTypes.gfs2;
if (!RunProbe(coordinator, currentSrType, out srs)) // start second probe
{
cancel = true;
return;
}
}
// Probe has been performed. Now ask the user if they want to Reattach/Format/Cancel.
// Will return false on cancel
cancel = !ExamineIscsiProbeResults(currentSrType, srs);
}
public override void PageCancelled(ref bool cancel)
{
_populateIqnsAction?.Cancel();
_populateLunsAction?.Cancel();
}
public override bool EnableNext()
{
return _buttonNextEnabled;
}
public override bool EnablePrevious()
{
if (SrWizardType.DisasterRecoveryTask && SrWizardType.SrToReattach == null)
return false;
return _populateIqnsAction == null && _populateLunsAction == null;
}
public override void PopulatePage()
{
HideErrors();
}
#endregion
private bool RunProbe(Host coordinator, SR.SRTypes srType, out List srs)
{
srs = new List();
var dconf = GetDeviceConfig(srType);
if (dconf == null)
return false;
var action = new SrProbeAction(Connection, coordinator, srType, dconf);
using (var dialog = new ActionProgressDialog(action, ProgressBarStyle.Marquee) {ShowCancel = true})
dialog.ShowDialog(this);
srs = action.SRs ?? new List();
if (action.Succeeded)
return true;
HandleFailure(action);
return false;
}
private void UpdateButtons()
{
bool portValid = ushort.TryParse(textBoxIscsiPort.Text, out _);
var iscsiHost = getIscsiHost();
bool validChap = !checkBoxUseChap.Checked || !string.IsNullOrEmpty(textBoxChapUser.Text);
buttonScanTargetHost.Enabled = !string.IsNullOrEmpty(iscsiHost) && portValid && validChap &&
_populateIqnsAction == null && _populateLunsAction == null;
_buttonNextEnabled = !string.IsNullOrEmpty(iscsiHost) && portValid && validChap &&
!string.IsNullOrEmpty(getIscsiLUN()) && !_errorExists;
OnPageUpdated();
}
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
if (comboBoxIscsiIqns.SelectedItem is ToStringWrapper wrapper)
return wrapper.item.IpAddress;
return textBoxIscsiHost.Text.Trim();
}
private ushort getIscsiPort()
{
if (comboBoxIscsiIqns.SelectedItem is ToStringWrapper wrapper)
return wrapper.item.Port;
if (ushort.TryParse(textBoxIscsiPort.Text, out var port))
return port;
return Util.DEFAULT_ISCSI_PORT;
}
private string getIscsiIQN()
{
if (comboBoxIscsiIqns.SelectedItem is ToStringWrapper wrapper)
return wrapper.item.TargetIQN;
return "";
}
private string getIscsiLUN()
{
var text = comboBoxIscsiLuns.SelectedItem as string;
return text == null || text == Messages.SELECT_TARGET_LUN ? "" : text;
}
private void ShowError(PictureBox errorIcon, Label errorLabel, string errorMessage)
{
_errorExists = true;
errorLabel.Text = errorMessage;
errorIcon.Visible = true;
errorLabel.Visible = true;
}
private void HideErrors()
{
_errorExists = false;
errorIconAtHostOrIP.Visible = false;
errorIconAtCHAPPassword.Visible = false;
errorIconAtTargetLUN.Visible = false;
errorIconBottom.Visible = false;
errorLabelAtHostOrIP.Visible = false;
errorLabelAtCHAPPassword.Visible = false;
errorLabelAtTargetLUN.Visible = false;
errorLabelBottom.Visible = false;
}
private void ResetIqns()
{
spinnerIconAtTargetIqn.StopSpinning();
try
{
comboBoxIscsiIqns.SelectedIndexChanged -= comboBoxIscsiIqns_SelectedIndexChanged;
comboBoxIscsiIqns.Items.Clear();
}
finally
{
comboBoxIscsiIqns.SelectedIndexChanged += comboBoxIscsiIqns_SelectedIndexChanged;
}
comboBoxIscsiIqns.Enabled = false;
labelTargetIqn.Enabled = false;
toolTipTargetIqn.SetToolTip(comboBoxIscsiIqns, null);
}
private void ResetLuns()
{
spinnerIconAtTargetLun.StopSpinning();
try
{
comboBoxIscsiLuns.SelectedIndexChanged -= comboBoxIscsiLuns_SelectedIndexChanged;
comboBoxIscsiLuns.Items.Clear();
}
finally
{
comboBoxIscsiLuns.SelectedIndexChanged += comboBoxIscsiLuns_SelectedIndexChanged;
}
comboBoxIscsiLuns.Enabled = false;
labelTargetLun.Enabled = false;
LunMap.Clear();
}
private void ResetAll()
{
spinnerIconAtScanTargetHostButton.StopSpinning();
iSCSITargetGroupBox.Enabled = false;
HideErrors();
ResetIqns();
ResetLuns();
UpdateButtons();
}
private void DisableAllInputControls()
{
textBoxIscsiHost.Enabled = false;
textBoxIscsiPort.Enabled = false;
checkBoxUseChap.Enabled = false;
textBoxChapUser.Enabled = false;
textBoxChapPassword.Enabled = false;
iSCSITargetGroupBox.Enabled = false;
}
private void EnableInputControls()
{
textBoxIscsiHost.Enabled = true;
textBoxIscsiPort.Enabled = true;
checkBoxUseChap.Enabled = true;
EnableChapControls();
iSCSITargetGroupBox.Enabled = true;
}
private void EnableChapControls()
{
bool enabled = checkBoxUseChap.Checked;
textBoxChapUser.Enabled = enabled;
textBoxChapPassword.Enabled = enabled;
labelCHAPuser.Enabled = enabled;
IScsiChapSecretLabel.Enabled = enabled;
}
private void HandleFailure(ActionBase action)
{
if (action == null || !(action.Exception is Failure failure) || failure.ErrorDescription.Count < 1)
return;
if (failure.ErrorDescription[0] == "SR_BACKEND_FAILURE_68")
{
ShowError(errorIconAtCHAPPassword, errorLabelAtCHAPPassword, Messages.LOGGING_IN_TO_THE_ISCSI_TARGET_FAILED);
textBoxChapUser.Focus();
}
else if (failure.ErrorDescription[0] == "SR_BACKEND_FAILURE_140")
{
ShowError(errorIconAtHostOrIP, errorLabelAtHostOrIP, Messages.INVALID_HOST);
textBoxIscsiHost.Focus();
}
else if (failure.ErrorDescription[0] == "SR_BACKEND_FAILURE_141")
{
ShowError(errorIconAtHostOrIP, errorLabelAtHostOrIP, Messages.SR_UNABLE_TO_CONNECT_TO_SCSI_TARGET);
textBoxIscsiHost.Focus();
}
else
ShowError(errorIconBottom, errorLabelBottom, failure.Message);
}
#region Event handlers
private void textBoxIscsiHost_TextChanged(object sender, EventArgs e)
{
ResetAll();
}
private void checkBoxUseChap_CheckedChanged(object sender, EventArgs e)
{
EnableChapControls();
ResetAll();
}
private void textBoxChapPassword_TextChanged(object sender, EventArgs e)
{
ResetAll();
}
private void textBoxChapUser_TextChanged(object sender, EventArgs e)
{
ResetAll();
}
private void buttonScanTargetHost_Click(object sender, EventArgs e)
{
HideErrors();
ResetIqns();
ResetLuns();
DisableAllInputControls();
spinnerIconAtScanTargetHostButton.StartSpinning();
_populateIqnsAction = SrType == SR.SRTypes.gfs2
? new Gfs2PopulateIQNsAction(Connection, getIscsiHost(), getIscsiPort(), ChapUser, ChapPassword)
: new ISCSIPopulateIQNsAction(Connection, getIscsiHost(), getIscsiPort(), ChapUser, ChapPassword);
_populateIqnsAction.Completed += PopulateIqnsAction_Completed;
_populateIqnsAction.RunAsync();
UpdateButtons();
}
private void comboBoxIscsiIqns_SelectedIndexChanged(object sender, EventArgs e)
{
HideErrors();
ResetLuns();
if (comboBoxIscsiIqns.SelectedItem is ToStringWrapper wrapper)
{
toolTipTargetIqn.SetToolTip(comboBoxIscsiIqns, wrapper.ToString());
DisableAllInputControls();
spinnerIconAtTargetIqn.StartSpinning();
_populateLunsAction = SrType == SR.SRTypes.gfs2
? new Gfs2PopulateLunsAction(Connection, getIscsiHost(), getIscsiPort(), getIscsiIQN(), ChapUser, ChapPassword)
: new ISCSIPopulateLunsAction(Connection, getIscsiHost(), getIscsiPort(), getIscsiIQN(), ChapUser, ChapPassword);
_populateLunsAction.Completed += PopulateLunsAction_Completed;
_populateLunsAction.RunAsync();
}
else
{
toolTipTargetIqn.SetToolTip(comboBoxIscsiIqns, Messages.SELECT_TARGET_IQN);
}
UpdateButtons();
}
private void comboBoxIscsiLuns_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBoxIscsiLuns.SelectedItem as string == Messages.SELECT_TARGET_LUN)
{
spinnerIconAtTargetLun.StopSpinning();
HideErrors();
}
else
{
spinnerIconAtTargetLun.ShowSuccessImage();
var isLunInUse = SrUsingLunExists(out var sr);//in this or other connected pools
if (isLunInUse)
ShowError(errorIconAtTargetLUN, errorLabelAtTargetLUN, LVMoIsciWarningDialog.GetSrInUseMessage(sr));
else
HideErrors();
}
UpdateButtons();
}
private void PopulateIqnsAction_Completed(ActionBase sender)
{
Program.Invoke(this, () => PopulateIqnsAction_Completed_(sender as ISCSIPopulateIQNsAction));
}
private void PopulateIqnsAction_Completed_(ISCSIPopulateIQNsAction action)
{
if (action == null)
return;
Program.AssertOnEventThread();
_populateIqnsAction = null;
EnableInputControls();
if (!action.Succeeded)
{
spinnerIconAtScanTargetHostButton.StopSpinning();
HandleFailure(action);
UpdateButtons();
return;
}
// If no IQNs are found do nothing; the ActionProgressDialog will have shown Messages.NEWSR_NO_IQNS_FOUND
var validIqns = action.IQNs.Where(info => !string.IsNullOrEmpty(info.TargetIQN)).ToList();
if (validIqns.Count == 0)
{
UpdateButtons();
return;
}
int width = comboBoxIscsiIqns.Width;
comboBoxIscsiIqns.Items.Add(Messages.SELECT_TARGET_IQN);
foreach (IScsiIqnInfo iqnInfo in validIqns)
{
var 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);
comboBoxIscsiIqns.SelectedItem = comboBoxIscsiIqns.Items.Count == 2
? comboBoxIscsiIqns.Items[1]
: Messages.SELECT_TARGET_IQN;
labelTargetIqn.Enabled = true;
comboBoxIscsiIqns.Enabled = true;
comboBoxIscsiIqns.Focus();
spinnerIconAtScanTargetHostButton.ShowSuccessImage();
UpdateButtons();
}
private void PopulateLunsAction_Completed(ActionBase sender)
{
Program.Invoke(this, () => PopulateLunsAction_Completed_(sender as ISCSIPopulateLunsAction));
}
private void PopulateLunsAction_Completed_(ISCSIPopulateLunsAction action)
{
if (action == null)
return;
Program.AssertOnEventThread();
_populateLunsAction = null;
EnableInputControls();
if (!action.Succeeded)
{
spinnerIconAtTargetIqn.StopSpinning();
HandleFailure(action);
UpdateButtons();
return;
}
// If no IQNs are found do nothing; the ActionProgressDialog will have shown Messages.NEWSR_NO_LUNS_FOUND
if (action.LUNs.Length == 0)
{
UpdateButtons();
return;
}
comboBoxIscsiLuns.Items.Add(Messages.SELECT_TARGET_LUN);
foreach (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);
}
//if there is only one choice, select that one by default
comboBoxIscsiLuns.SelectedItem = comboBoxIscsiLuns.Items.Count == 2
? comboBoxIscsiLuns.Items[1]
: Messages.SELECT_TARGET_LUN;
labelTargetLun.Enabled = true;
comboBoxIscsiLuns.Enabled = true;
comboBoxIscsiLuns.Focus();
spinnerIconAtTargetIqn.ShowSuccessImage();
UpdateButtons();
}
#endregion
private bool SrUsingLunExists(out SR theSr)
{
theSr = null;
if (string.IsNullOrEmpty(getIscsiLUN()))
return false;
foreach (IXenConnection connection in ConnectionsManager.XenConnectionsCopy)
{
foreach (SR sr in connection.Cache.SRs)
{
if (sr.GetSRType(false) != SR.SRTypes.lvmoiscsi && sr.GetSRType(false) != SR.SRTypes.gfs2)
continue;
if (sr.PBDs.Count < 1)
continue;
PBD pbd = connection.Resolve(sr.PBDs[0]);
if (pbd == null)
continue;
if (pbd.device_config.TryGetValue(SCSIID, out var scsiId) &&
LunMap.TryGetValue(getIscsiLUN(), out var info) &&
info.ScsiID == scsiId)
{
theSr = sr;
return true;
}
}
}
return false;
}
private bool ExamineIscsiProbeResults(SR.SRTypes currentSrType, List srs)
{
_srToIntroduce = null;
if (srs == null)
return false;
// There should be 0 or 1 SRs on the LUN
System.Diagnostics.Debug.Assert(srs.Count == 0 || srs.Count == 1);
try
{
if (!string.IsNullOrEmpty(SrWizardType.UUID))
{
// Check LUN contains correct SR
if (srs.Count == 1 && srs[0].UUID == SrWizardType.UUID)
{
_srToIntroduce = srs[0];
SrType = currentSrType; // the type of the existing SR
return true;
}
ShowError(errorIconAtTargetLUN, errorLabelAtTargetLUN, string.Format(Messages.INCORRECT_LUN_FOR_SR, SrWizardType.SrName));
return false;
}
if (srs.Count == 0)
{
if (!SrWizardType.AllowToCreateNewSr)
{
using (var dlg = new ErrorDialog(Messages.NEWSR_LUN_HAS_NO_SRS))
dlg.ShowDialog(this);
return false;
}
if (Program.RunInAutomatedTestMode)
return true;
// SR creation is allowed; ask the user if they want to proceed and format.
using (var dlog = new LVMoIsciWarningDialog(Connection, null, currentSrType, SrType))
{
dlog.ShowDialog(this);
return dlog.SelectedOption == LVMoHBAWarningDialog.UserSelectedOption.Format;
}
}
if (Program.RunInAutomatedTestMode)
return true;
// offer to attach it, or format it to create a new SR, or cancel
SR.SRInfo srInfo = srs[0];
using (var dlog = new LVMoIsciWarningDialog(Connection, srInfo, currentSrType, SrType))
{
dlog.ShowDialog(this);
switch (dlog.SelectedOption)
{
case LVMoHBAWarningDialog.UserSelectedOption.Reattach:
_srToIntroduce = srInfo;
SrType = currentSrType; // the type of the existing SR
return true;
case LVMoHBAWarningDialog.UserSelectedOption.Format:
return true;
default:
return false;
}
}
}
catch
{
// We really want to prevent the user getting to the next step if there is any kind of
// exception here, since clicking 'finish' might destroy data: require another probe.
return false;
}
}
private Dictionary GetDeviceConfig(SR.SRTypes srType)
{
var iqn = comboBoxIscsiIqns.SelectedItem as ToStringWrapper;
if (iqn == null)
return null;
var selectedLan = getIscsiLUN();
if (!LunMap.TryGetValue(selectedLan, out ISCSIInfo info))
return null;
var dconf = new Dictionary();
if (srType == SR.SRTypes.gfs2)
{
if (_srToIntroduce != null && _srToIntroduce.Configuration != null)
dconf = _srToIntroduce.Configuration;
dconf[PROVIDER] = "iscsi";
dconf[TARGET] = iqn.item.IpAddress;
dconf[PORT] = iqn.item.Port.ToString();
dconf[TARGETIQN] = getIscsiIQN();
dconf[SCSIID] = LunMap[selectedLan].ScsiID;
if (checkBoxUseChap.Checked)
{
dconf[CHAPUSER] = textBoxChapUser.Text;
dconf[CHAPPASSWORD] = textBoxChapPassword.Text;
}
return dconf;
}
// 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 (info.LunID == -1)
dconf[LUNSERIAL] = info.Serial;
else
dconf[SCSIID] = info.ScsiID;
if (checkBoxUseChap.Checked)
{
dconf[CHAPUSER] = textBoxChapUser.Text;
dconf[CHAPPASSWORD] = textBoxChapPassword.Text;
}
return dconf;
}
#region Accessors
private string ChapUser => checkBoxUseChap.Checked ? textBoxChapUser.Text : null;
private string ChapPassword => checkBoxUseChap.Checked ? textBoxChapPassword.Text : null;
public SrWizardType SrWizardType { private get; set; }
public string UUID => _srToIntroduce?.UUID;
public Dictionary DeviceConfig => GetDeviceConfig(SrType);
public string SrDescription
{
get
{
ToStringWrapper iqn = comboBoxIscsiIqns.SelectedItem as ToStringWrapper;
return iqn == null ? null : string.Format(Messages.NEWSR_ISCSI_DESCRIPTION, iqn.item.IpAddress, getIscsiIQN(), getIscsiLUN());
}
}
public SR.SRTypes SrType { get; set; }
#endregion
}
}