xenadmin/XenAdmin/Wizards/DRWizards/DRFailoverWizardStoragePage.cs
Konstantina Chremmou 749518e94c CA-303195: Provide the user the option to reconsider wizard cancellation
(either when cancelling the wizard itself or exiting the application).
Implemented in the first instance for compiling the server status report.

Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com>
2019-01-15 17:12:09 +00:00

667 lines
24 KiB
C#

/* 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.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using XenAdmin.Actions;
using XenAdmin.Core;
using XenAdmin.Dialogs;
using XenAdmin.Wizards.NewSRWizard_Pages;
using XenAPI;
using XenAdmin.Controls;
using XenAdmin.Actions.DR;
namespace XenAdmin.Wizards.DRWizards
{
public partial class DRFailoverWizardStoragePage : XenTabPage
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public event Action<string> NewDrTaskIntroduced;
private List<SR> _availableSRs = new List<SR>();
public DRFailoverWizardStoragePage()
{
InitializeComponent();
}
#region XenTabPage overrides
public override string Text
{
get { return Messages.DR_WIZARD_STORAGEPAGE_TEXT; }
}
public override string PageTitle
{
get { return Messages.DR_WIZARD_STORAGEPAGE_TITLE; }
}
public override string HelpID
{
get
{
switch (WizardType)
{
case DRWizardType.Failback:
return "Failback_Storage";
case DRWizardType.Dryrun:
return "Dryrun_Storage";
default:
return "Failover_Storage";
}
}
}
public override bool EnableNext()
{
if (_worker.IsBusy)
return false;
buttonClearAll.Enabled = SelectedSRsUuids.Count > 0;
buttonSelectAll.Enabled = SelectedSRsUuids.Count < dataGridViewSRs.Rows.OfType<SrRow>().Count();
return SelectedSRsUuids.Count > 0;
}
public override bool EnablePrevious()
{
return !_worker.IsBusy;
}
private readonly ConnectionLostDialogLauncher cldl = new ConnectionLostDialogLauncher();
protected override void PageLeaveCore(PageLoadedDirection direction, ref bool cancel)
{
_worker.CancelAsync();
if (direction == PageLoadedDirection.Forward)
{
IntroduceSRs();
LoadMetadata();
}
}
protected override void PageLoadedCore(PageLoadedDirection direction)
{
if (direction == PageLoadedDirection.Forward)
SetupLabels();
}
public override void PopulatePage()
{
dataGridViewSRs.Rows.Clear();
SelectedSRsUuids.Clear();
_availableSRs.Clear();
OnPageUpdated();
spinnerIcon1.StartSpinning();
_worker.RunWorkerAsync();
}
public override void PageCancelled(ref bool cancel)
{
_worker.CancelAsync();
}
#endregion
#region Initial page setup
private void _worker_DoWork(object sender, DoWorkEventArgs e)
{
Pool currentPool = Helpers.GetPoolOfOne(Connection);
if (currentPool == null)
return;
var srs = new List<SR>(Connection.Cache.SRs);
for (int i = 0; i < srs.Count; i++)
{
if (_worker.CancellationPending)
{
e.Cancel = true;
return;
}
var sr = srs[i];
SR checkedSr = SR.SupportsDatabaseReplication(sr.Connection, sr) ? sr : null;
int percentage = (i + 1)*100/srs.Count;
_worker.ReportProgress(percentage, checkedSr);
}
}
private void _worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
SR sr = e.UserState as SR;
if (sr != null)
_availableSRs.Add(sr);
}
private void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
spinnerIcon1.StopSpinning();
try
{
dataGridViewSRs.SuspendLayout();
foreach (SR sr in _availableSRs)
{
var vdis = sr.Connection.ResolveAll(sr.VDIs);
bool poolMetadataDetected = vdis.Any(vdi => vdi.type == vdi_type.metadata);
SrRow row;
if (!FindRowByUuid(sr.uuid, out row))
{
row = new SrRow(sr, poolMetadataDetected, SelectedSRsUuids.Contains(sr.uuid));
dataGridViewSRs.Rows.Add(row);
}
}
// add new SRs
foreach (var scannedDevice in ScannedDevices.Values)
{
foreach (var srInfo in scannedDevice.SRList)
{
SrRow row;
if (!FindRowByUuid(srInfo.UUID, out row))
{
row = new SrRow(srInfo, scannedDevice.Type, srInfo.PoolMetadataDetected,
SelectedSRsUuids.Contains(srInfo.UUID));
dataGridViewSRs.Rows.Add(row);
}
}
}
if (dataGridViewSRs.Rows.Count > 0)
SortRows();
}
finally
{
dataGridViewSRs.ResumeLayout();
}
OnPageUpdated();
}
private void SetupLabels()
{
switch (WizardType)
{
case DRWizardType.Failback:
labelText.Text = Messages.DR_WIZARD_STORAGEPAGE_DESCRIPTION_FAILBACK;
break;
default:
labelText.Text = Messages.DR_WIZARD_STORAGEPAGE_DESCRIPTION_FAILOVER;
break;
}
}
#endregion
#region Accessors
public List<string> SelectedSRsUuids = new List<String>();
internal List<string> GetSelectedSRsNames()
{
return (from SrRow row in dataGridViewSRs.Rows where IsRowChecked(row) select row.SrName).ToList();
}
public DRWizardType WizardType { private get; set; }
#endregion
#region Scan for SRs
private Dictionary<string, ScannedDeviceInfo> ScannedDevices = new Dictionary<string, ScannedDeviceInfo>();
private const String LUNSERIAL = "LUNSerial";
private const String SCSIID = "SCSIid";
private const String METADATA = "metadata";
private void ScanForSRs(SR.SRTypes type)
{
var srs = new List<SR.SRInfo>();
switch (type)
{
case SR.SRTypes.lvmohba:
var devices = FiberChannelScan();
if (devices != null && devices.Count > 0)
{
foreach (FibreChannelDevice device in devices)
{
string deviceId = string.IsNullOrEmpty(device.SCSIid) ? device.Path : device.SCSIid;
var metadataSrs = ScanDeviceForSRs(SR.SRTypes.lvmohba, deviceId,
new Dictionary<string, string> {{SCSIID, device.SCSIid}});
if (metadataSrs != null && metadataSrs.Count > 0)
srs.AddRange(metadataSrs);
}
}
AddScanResultsToDataGridView(srs, SR.SRTypes.lvmohba);
break;
case SR.SRTypes.lvmoiscsi:
using (var dialog = new IscsiDeviceConfigDialog(Connection))
{
if (dialog.ShowDialog(this) == DialogResult.OK)
{
Dictionary<String, String> dconf = dialog.DeviceConfig;
string deviceId = string.IsNullOrEmpty(dconf[SCSIID]) ? dconf[LUNSERIAL] : dconf[SCSIID];
var metadataSrs = ScanDeviceForSRs(SR.SRTypes.lvmoiscsi, deviceId, dconf);
if (metadataSrs != null && metadataSrs.Count > 0)
srs.AddRange(metadataSrs);
}
else
{
// if the user cancels the dialog there is no need to show the "no SRs found" pop-up
return;
}
}
AddScanResultsToDataGridView(srs, SR.SRTypes.lvmoiscsi);
break;
}
if (srs.Count == 0)
using (var dlg = new ThreeButtonDialog(
new ThreeButtonDialog.Details(SystemIcons.Information,
Messages.DR_WIZARD_STORAGEPAGE_SCAN_RESULT_NONE,
Messages.XENCENTER)))
{
dlg.ShowDialog(this);
}
}
private List<FibreChannelDevice> FiberChannelScan()
{
Host master = Helpers.GetMaster(Connection);
if (master == null)
return null;
var action = new FibreChannelProbeAction(master);
using (var dialog = new ActionProgressDialog(action, ProgressBarStyle.Marquee))
dialog.ShowDialog(this); //Will block until dialog closes, action completed
return action.Succeeded ? action.FibreChannelDevices : null;
}
private List<SR.SRInfo> ScanDeviceForSRs(SR.SRTypes type, string deviceId, Dictionary<string, string> dconf)
{
Host master = Helpers.GetMaster(Connection);
if (master == null || dconf == null)
return null;
// Start probe
SrProbeAction srProbeAction = new SrProbeAction(Connection, master, type, dconf,
new Dictionary<string, string> {{METADATA, "true"}});
using (var dlg = new ActionProgressDialog(srProbeAction, ProgressBarStyle.Marquee))
dlg.ShowDialog(this);
if (!srProbeAction.Succeeded)
return null;
try
{
var metadataSrs = SR.ParseSRListXML(srProbeAction.Result);
if (ScannedDevices.ContainsKey(deviceId))
{
//update SR list
ScannedDevices[deviceId].SRList.Clear();
ScannedDevices[deviceId].SRList.AddRange(metadataSrs);
}
else
{
ScannedDevices.Add(deviceId, new ScannedDeviceInfo(type, dconf, metadataSrs));
}
return metadataSrs;
}
catch
{
return null;
}
}
private void AddScanResultsToDataGridView(List<SR.SRInfo> metadataSrs, SR.SRTypes type)
{
if (metadataSrs == null || metadataSrs.Count == 0)
return;
foreach (SR.SRInfo srInfo in metadataSrs)
{
SrRow row;
if (!FindRowByUuid(srInfo.UUID, out row))
{
row = new SrRow(srInfo, type, srInfo.PoolMetadataDetected, srInfo.PoolMetadataDetected);
dataGridViewSRs.Rows.Add(row);
ToggleRowChecked(row);
}
}
SortRows();
}
#endregion
#region Introduce SRs
private void IntroduceSRs()
{
Pool pool = Helpers.GetPoolOfOne(Connection);
if (pool == null)
{
log.Error("New SR Wizard: Pool has disappeared");
return;
}
Host master = Connection.Resolve(pool.master);
if (master == null)
{
log.Error("New SR Wizard: Master has disappeared");
return;
}
// Start DR task
foreach (var kvp in SelectedNewDevices)
{
var newDevice = kvp.Value;
DrTaskCreateAction action = new DrTaskCreateAction(Connection, newDevice);
using (var dialog = new ActionProgressDialog(action, ProgressBarStyle.Marquee))
{
dialog.ShowCancel = true;
dialog.ShowDialog(this);
}
if(!cldl.IsStillConnected(Connection))
return;
if (action.Succeeded)
{
ScannedDevices[kvp.Key].SRList.Clear();
if (NewDrTaskIntroduced != null)
NewDrTaskIntroduced(action.Result);
}
else
{
Exception exn = action.Exception;
log.Warn(exn, exn);
Failure failure = exn as Failure;
if (failure != null && failure.ErrorDescription[0] == "SR_BACKEND_FAILURE_140")
{
MessageBox.Show(this, failure.Message);
}
break;
}
}
}
private Dictionary<string, ScannedDeviceInfo> SelectedNewDevices
{
get
{
Dictionary<string, ScannedDeviceInfo> result = new Dictionary<string, ScannedDeviceInfo>();
if (SelectedSRsUuids.Count > 0)
{
foreach (var scannedDevice in ScannedDevices)
{
List<SR.SRInfo> lst = new List<SR.SRInfo>();
foreach (var srInfo in scannedDevice.Value.SRList)
{
if (SelectedSRsUuids.Contains(srInfo.UUID))
lst.Add(srInfo);
}
if (lst.Count > 0)
{
result.Add(scannedDevice.Key, new ScannedDeviceInfo(scannedDevice.Value.Type, scannedDevice.Value.DeviceConfig, lst));
}
}
}
return result;
}
}
#endregion
#region Load Pool Metadata
private Dictionary<XenRef<VDI>, PoolMetadata> allPoolMetadata = new Dictionary<XenRef<VDI>, PoolMetadata>();
public Dictionary<XenRef<VDI>, PoolMetadata> AllPoolMetadata { get { return allPoolMetadata; } }
private void LoadMetadata()
{
allPoolMetadata.Clear();
GetMetadataVDIsAction action = new GetMetadataVDIsAction(Connection, SelectedSRsUuids);
if (!cldl.IsStillConnected(Connection))
return;
using (var dialog = new ActionProgressDialog(action, ProgressBarStyle.Marquee))
dialog.ShowDialog(this);
if (!cldl.IsStillConnected(Connection))
return;
if (action.Succeeded && action.VDIs != null)
{
foreach (VDI vdi in action.VDIs)
{
var inUse = Connection.ResolveAll(vdi.VBDs).Any(vbd => vbd.currently_attached);
if (!inUse)
LoadPoolMetadata(vdi);
else
log.DebugFormat("This metadata VDI is in use: '{0}' (UUID='{1}', SR='{2})'; will not attempt to load metadata from it", vdi.Name(),
vdi.uuid, Connection.Resolve(vdi.SR).Name());
}
}
}
private void LoadPoolMetadata(VDI vdi)
{
Session metadataSession = null;
try
{
VdiLoadMetadataAction action = new VdiLoadMetadataAction(Connection, vdi);
using (var dialog = new ActionProgressDialog(action, ProgressBarStyle.Marquee))
dialog.ShowDialog(this); //Will block until dialog closes, action completed
if (action.Succeeded && action.MetadataSession != null)
{
metadataSession = action.MetadataSession;
XenRef<VDI> vdiRef = new XenRef<VDI>(vdi);
if (action.PoolMetadata != null && !allPoolMetadata.ContainsKey(vdiRef))
{
allPoolMetadata.Add(vdiRef, action.PoolMetadata);
}
}
}
finally
{
if (metadataSession != null)
metadataSession.logout();
}
}
#endregion
#region Control event handlers
private void FindSrsButton_Click(object sender, EventArgs e)
{
FindSrsOptionsMenuStrip.Show(this,
new Point(FindSrsButton.Left, FindSrsButton.Bottom));
}
private void fcToolStripMenuItem_Click(object sender, EventArgs e)
{
ScanForSRs(SR.SRTypes.lvmohba);
}
private void iscsiToolStripMenuItem_Click(object sender, EventArgs e)
{
ScanForSRs(SR.SRTypes.lvmoiscsi);
}
private void dataGridViewSRs_CellClick(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex != 0 || e.RowIndex < 0 || e.RowIndex > dataGridViewSRs.RowCount - 1)
return;
dataGridViewSRs.Rows[e.RowIndex].Cells[0].Value = !(bool)dataGridViewSRs.Rows[e.RowIndex].Cells[0].Value;
}
private void dataGridViewSRs_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex != 0 || e.RowIndex < 0 || e.RowIndex > dataGridViewSRs.RowCount - 1)
return;
ToggleRowChecked(dataGridViewSRs.Rows[e.RowIndex] as SrRow);
}
#endregion
#region SrRow class and DataGridView helper methods
private class SrRow : DataGridViewRow
{
private readonly SR Sr;
private readonly SR.SRInfo SrInfo;
public SrRow(SR sr, bool poolMetadataDetected, bool selected)
{
Sr = sr;
var cellTick = new DataGridViewCheckBoxCell { Value = selected };
var cellName = new DataGridViewTextBoxCell { Value = sr.Name() };
var cellDesc = new DataGridViewTextBoxCell { Value = sr.Description() };
var cellType = new DataGridViewTextBoxCell { Value = sr.FriendlyTypeName() };
var cellMetadata = new DataGridViewTextBoxCell { Value = poolMetadataDetected.ToStringI18n() };
Cells.AddRange(cellTick, cellName, cellDesc, cellType, cellMetadata);
}
public SrRow(SR.SRInfo srInfo, SR.SRTypes type, bool poolMetadataDetected, bool selected)
{
SrInfo = srInfo;
var cellTick = new DataGridViewCheckBoxCell { Value = selected };
var cellName = new DataGridViewTextBoxCell { Value = srInfo.Name };
var cellDesc = new DataGridViewTextBoxCell { Value = srInfo.Description };
var cellType = new DataGridViewTextBoxCell { Value = SR.getFriendlyTypeName(type) };
var cellMetadata = new DataGridViewTextBoxCell { Value = poolMetadataDetected.ToStringI18n() };
Cells.AddRange(cellTick, cellName, cellDesc, cellType, cellMetadata);
}
public string SrUuid
{
get
{
if (Sr != null)
return Sr.uuid;
return SrInfo != null ? SrInfo.UUID : string.Empty;
}
}
public string SrName
{
get
{
if (Sr != null)
return Sr.Name();
return SrInfo != null ? SrInfo.Name : string.Empty;
}
}
}
private bool IsRowChecked(SrRow row)
{
if (row == null)
return false;
var cell = row.Cells[0] as DataGridViewCheckBoxCell;
return cell == null ? false : (bool)cell.Value;
}
private void ToggleRowChecked(SrRow row)
{
if (row == null)
return;
if (IsRowChecked(row))
SelectedSRsUuids.Add(row.SrUuid);
else
SelectedSRsUuids.Remove(row.SrUuid);
OnPageUpdated();
}
private bool FindRowByUuid(string uuid, out SrRow row)
{
row = null;
foreach (var srRow in dataGridViewSRs.Rows.Cast<SrRow>().Where(srRow => srRow.SrUuid == uuid))
{
row = srRow;
return true;
}
return false;
}
private void buttonSelectAll_Click(object sender, EventArgs e)
{
SelectAllRows(true);
}
private void buttonClearAll_Click(object sender, EventArgs e)
{
SelectAllRows(false);
}
private void SelectAllRows(bool selected)
{
foreach (var row in dataGridViewSRs.Rows.OfType<SrRow>())
{
var cell = row.Cells[0] as DataGridViewCheckBoxCell;
if (cell != null && (bool)cell.Value != selected)
cell.Value = selected;
}
}
private void SortRows()
{
dataGridViewSRs.Sort(columnMetadata, ListSortDirection.Descending);
dataGridViewSRs.Rows[0].Selected = true;
}
#endregion
}
}