xenadmin/XenAdmin/Wizards/BugToolWizardFiles/BugToolPageRetrieveData.cs
Konstantina Chremmou b66f11a1e8 CA-303195: Rewrote the logic for compiling and retrieving server status data. The changes include:
- removal of the SystemStatusAction launching the individual SingleHostStatusActions,
because the connection was set incorrectly to the last one of the selected
connections and, if an error occurred to that connection, the data collection
for all of them failed.
- proper handling of HTTP.CancelledExceptions.
- replacement of the FlickerFreeListBox by the more versatile DataGridViewEx.
- corrections to reporting progress for individual hosts as well as page totals.
- made collection of client side data more obvious.

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

468 lines
16 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.Drawing;
using System.IO;
using System.Windows.Forms;
using XenAdmin.Controls;
using XenAdmin.Dialogs;
using XenAPI;
using XenAdmin.Actions;
using XenAdmin.Controls.DataGridViewEx;
namespace XenAdmin.Wizards.BugToolWizardFiles
{
public partial class BugToolPageRetrieveData : XenTabPage
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public BugToolPageRetrieveData()
{
InitializeComponent();
}
#region XenTabPage overrides
public override string Text { get { return Messages.BUGTOOL_PAGE_RETRIEVEDATA_TEXT; } }
public override string PageTitle { get { return Messages.BUGTOOL_PAGE_RETRIEVEDATA_PAGE_TITLE; } }
public override string HelpID { get { return "CompileReport"; } }
public override bool EnableNext()
{
var allCompleted = AllActionsCompleted(out bool successExists, out _);
return allCompleted && successExists;
}
protected override void PageLoadedCore(PageLoadedDirection direction)
{
if (direction == PageLoadedDirection.Forward)
RunActions();
}
protected override void PageLeaveCore(PageLoadedDirection direction, ref bool cancel)
{
if (direction == PageLoadedDirection.Forward)
return;
var allCompleted = AllActionsCompleted(out _, out _);
if (allCompleted)
return;
using (var dlog = new ThreeButtonDialog(
new ThreeButtonDialog.Details(SystemIcons.Warning, Messages.BUGTOOL_PAGE_RETRIEVEDATA_CONFIRM_CANCEL, Messages.BUGTOOL_PAGE_RETRIEVEDATA_PAGE_TITLE),
ThreeButtonDialog.ButtonYes,
ThreeButtonDialog.ButtonNo))
{
if (dlog.ShowDialog(this) != DialogResult.Yes)
{
cancel = true;
return;
}
}
CancelActions();
}
public override void PageCancelled()
{
CancelActions();
}
#endregion
#region Properties
public List<Host> SelectedHosts { private get; set; }
public List<Capability> CapabilityList { private get; set; }
public string OutputFolder { get; private set; }
private bool AllActionsCompleted(out bool successExists, out bool failureExists)
{
successExists = false;
failureExists = false;
foreach (var row in dataGridViewEx1.Rows)
{
var srRow = (StatusReportRow)row;
if (!srRow.IsCompleted)
return false;
if (srRow.IsSuccessful)
successExists = true;
else
failureExists = true;
}
return true;
}
#endregion
private void CancelActions()
{
foreach (var r in dataGridViewEx1.Rows)
{
if (r is StatusReportRow row)
{
row.DeRegisterEvents();
DeRegisterRowEvents(row);
row.CancelAction();
}
}
}
private void RunActions()
{
try
{
labelError.Text = "";
progressBar1.Value = 0;
dataGridViewEx1.SuspendLayout();
dataGridViewEx1.Rows.Clear();
var capabilityKeys = new List<string>();
long size = 0;
foreach (Capability c in CapabilityList)
{
if (c.Key != "client-logs")
size += c.MaxSize;
capabilityKeys.Add(c.Key);
}
var rowList = new List<DataGridViewRow>();
var includeClientLogs = capabilityKeys.Contains("client-logs");
if (includeClientLogs || SelectedHosts.Count > 0)
rowList.Add(new ClientSideDataRow(SelectedHosts, includeClientLogs));
foreach (Host host in SelectedHosts)
rowList.Add(new HostStatusRow(host, size, capabilityKeys));
dataGridViewEx1.Rows.AddRange(rowList.ToArray());
}
finally
{
dataGridViewEx1.ResumeLayout();
}
OutputFolder = CreateOutputFolder();
string timeString = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
foreach (var r in dataGridViewEx1.Rows)
{
var row = (StatusReportRow)r;
RegisterRowEvents(row);
row.RunAction(OutputFolder, timeString);
}
OnPageUpdated();
}
private void Row_RowStatusChanged(StatusReportRow row)
{
UpdateTotalPercentComplete();
OnPageUpdated();
}
private void Row_RowStatusCompleted(StatusReportRow row)
{
DeRegisterRowEvents(row);
UpdateTotalPercentComplete();
var allCompleted = AllActionsCompleted(out bool successExists, out bool failureExists);
if (allCompleted)
{
if (!successExists)
labelError.Text = Messages.ACTION_SYSTEM_STATUS_FAILED;
else if (!failureExists)
labelError.Text = Messages.ACTION_SYSTEM_STATUS_SUCCESSFUL;
else
labelError.Text = Messages.ACTION_SYSTEM_STATUS_SUCCESSFUL_PARTIAL;
}
OnPageUpdated();
}
private void RegisterRowEvents(StatusReportRow row)
{
row.RowStatusChanged += Row_RowStatusChanged;
row.RowStatusCompleted += Row_RowStatusCompleted;
}
private void DeRegisterRowEvents(StatusReportRow row)
{
row.RowStatusChanged -= Row_RowStatusChanged;
row.RowStatusCompleted -= Row_RowStatusCompleted;
}
private void UpdateTotalPercentComplete()
{
int total = 0;
foreach (var r in dataGridViewEx1.Rows)
{
var row = (StatusReportRow)r;
total += row.PercentComplete;
}
var percentage = total / dataGridViewEx1.RowCount;
if (percentage < 0)
percentage = 0;
if (percentage > 100)
percentage = 100;
if (percentage > progressBar1.Value)
progressBar1.Value = percentage;
}
private static string CreateOutputFolder()
{
var folder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
if (Directory.Exists(folder))
Directory.Delete(folder);
Directory.CreateDirectory(folder);
return folder;
}
#region Nested classes
private abstract class StatusReportRow : DataGridViewRow
{
protected readonly DataGridViewExImageCell cellHostImg = new DataGridViewExImageCell();
protected readonly DataGridViewTextBoxCell cellHost = new DataGridViewTextBoxCell();
private readonly DataGridViewTextBoxCell cellStatus = new DataGridViewTextBoxCell();
private readonly DataGridViewExImageCell cellResultImg = new DataGridViewExImageCell();
public event Action<StatusReportRow> RowStatusChanged;
public event Action<StatusReportRow> RowStatusCompleted;
protected StatusReportRow()
{
Cells.AddRange(cellHostImg, cellHost, cellStatus, cellResultImg);
cellResultImg.Value = new Bitmap(1, 1);
}
public abstract StatusReportAction Action { get; }
public int PercentComplete { get; private set; }
public bool IsCompleted
{
get { return Action != null && Action.IsCompleted; }
}
public bool IsSuccessful
{
get
{
return Action != null && Action.IsCompleted && Action.Status == ReportStatus.succeeded;
}
}
public void CancelAction()
{
if (Action != null && !Action.IsCompleted)
Action.Cancel();
}
public void RunAction(string path, string time)
{
CreateAction(path, time);
Action.Changed += Action_Changed;
Action.Completed += Action_Completed;
UpdateCells(0);
Action.RunAsync();
}
public void DeRegisterEvents()
{
if (Action == null)
return;
Action.Changed -= Action_Changed;
Action.Completed -= Action_Completed;
}
private void Action_Changed(ActionBase action)
{
Program.Invoke(DataGridView, () =>
{
UpdateCells(action.PercentComplete);
if (RowStatusChanged != null)
RowStatusChanged(this);
});
}
private void Action_Completed(ActionBase action)
{
DeRegisterEvents();
Program.Invoke(DataGridView, () =>
{
UpdateCells(100);
if (RowStatusCompleted != null)
RowStatusCompleted(this);
});
}
protected abstract void CreateAction(string path, string time);
protected virtual void UpdateCells(int percentComplete)
{
var statusString = GetStatus(out Image statusImage);
cellStatus.Value = statusString;
PercentComplete = percentComplete;
if (statusImage != null)
cellResultImg.Value = statusImage;
}
protected virtual string GetStatus(out Image img)
{
img = null;
if (Action == null)
return string.Empty;
switch (Action.Status)
{
case ReportStatus.compiling:
return Messages.BUGTOOL_REPORTSTATUS_COMPILING;
case ReportStatus.succeeded:
img = Images.StaticImages._000_Tick_h32bit_16;
return Messages.COMPLETED;
case ReportStatus.failed:
img = Images.StaticImages._000_Abort_h32bit_16;
return Messages.BUGTOOL_REPORTSTATUS_FAILED;
case ReportStatus.cancelled:
img = Images.StaticImages.cancelled_action_16;
return Messages.BUGTOOL_REPORTSTATUS_CANCELLED;
case ReportStatus.queued:
return Messages.BUGTOOL_REPORTSTATUS_QUEUED;
default:
return string.Empty;
}
}
}
private class ClientSideDataRow : StatusReportRow
{
private readonly List<Host> hosts;
private readonly bool includeClientLogs;
private StatusReportClientSideAction _action;
public ClientSideDataRow(List<Host> hosts, bool includeClientLogs)
{
this.hosts = hosts;
this.includeClientLogs = includeClientLogs;
cellHostImg.Value = Images.StaticImages._000_GetServerReport_h32bit_16;
cellHost.Value = includeClientLogs ? Messages.BUGTOOL_CLIENT_LOGS_META : Messages.BUGTOOL_CLIENT_META;
}
public override StatusReportAction Action
{
get { return _action; }
}
protected override void CreateAction(string path, string time)
{
_action = new StatusReportClientSideAction(hosts, includeClientLogs, path, time);
}
}
private class HostStatusRow : StatusReportRow
{
private readonly long size;
private readonly List<string> capabilityKeys;
private readonly Host Host;
private SingleHostStatusAction _action;
public HostStatusRow(Host host, long size, List<string> capabilityKeys)
{
Host = host;
this.size = size;
this.capabilityKeys = capabilityKeys;
}
protected override void UpdateCells(int percentComplete)
{
cellHostImg.Value = Images.GetImage16For(Host);
cellHost.Value = Host.Name();
base.UpdateCells(percentComplete);
}
public override StatusReportAction Action
{
get { return _action; }
}
protected override void CreateAction(string path, string time)
{
_action = new SingleHostStatusAction(Host, size, capabilityKeys, path, time);
}
protected override string GetStatus(out Image img)
{
img = null;
if (_action == null)
return string.Empty;
switch (_action.Status)
{
case ReportStatus.downloading:
return string.Format(Messages.BUGTOOL_REPORTSTATUS_DOWNLOADING,
Util.MemorySizeStringSuitableUnits(_action.DataTransferred, false));
default:
return base.GetStatus(out img);
}
}
}
#endregion
}
}