xenadmin/XenModel/Actions/CancellingAction.cs
Konstantina Chremmou 62586ef89c CP-5750: Removed ActionType; the actions should be categorised by status (in progress,
succeeded, failed; cancelled at the moment counts as failed, we may need to distinguish
in future). As a consequence the filter checkboxes from the top of the Log tab
were removed and some temporary changes were made to the drawing of action rows
(the controls will change completely in a subsequent commit).

Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com>
2013-08-09 17:20:38 +01:00

369 lines
12 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.Threading;
using XenAdmin.Network;
using XenAPI;
namespace XenAdmin.Actions
{
public class CancellingAction : ActionBase
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private Session _cancel_session = null;
private Session _session;
/// <summary>
/// Whether, the last time we checked on the server, this task could be cancelled. This is a cached
/// value, so may be stale.
/// </summary>
private volatile bool can_cancel = false;
/// <summary>
/// Whether this operation is being cancelled. This takes precedence over can_cancel.
/// </summary>
private volatile bool cancelling = false;
/// <summary>
/// Whether this operation was cancelled or not.
/// </summary>
private volatile bool cancelled = false;
public CancellingAction(string title, string description, bool suppressHistory)
: base(title, description, suppressHistory)
{
}
public CancellingAction(string title, string description, bool suppressHistory, bool completeImmediately)
: base(title, description, suppressHistory, completeImmediately)
{
}
private XenRef<Task> _relatedTask;
private readonly object connectionLock = new object();
public sealed override IXenConnection Connection
{
protected set
{
// lock any changes to the connection
// This only affect actions which run across multiple connections
lock (connectionLock)
{
base.Connection = value;
}
}
}
public Session Session
{
get { return _session; }
set { _session = value; }
}
/// <summary>
/// The XenAPI.Task object (if any) that corresponds to this action.
/// </summary>
public XenRef<Task> RelatedTask
{
get { return _relatedTask; }
set
{
//Program.AssertOffEventThread();
_relatedTask = value;
if (_relatedTask != null && _session != null)
{
Task.SetXenCenterUUID(_session, _relatedTask.opaque_ref, XenAdminConfigManager.Provider.XenCenterUUID);
Task.SetAppliesTo(_session, _relatedTask.opaque_ref, AppliesTo);
Task.SetMeddlingActionTitle(_session, _relatedTask.opaque_ref, Title);
RecomputeCanCancel();
}
}
}
public override sealed bool CanCancel
{
get { return !cancelling && can_cancel; }
protected set
{
if (can_cancel != value)
{
can_cancel = value;
OnChanged();
}
}
}
public bool Cancelling
{
get { return cancelling; }
protected set { cancelling = value; }
}
// You can't refer to property getters in order to treat them as a delegate, so this is
// a substitute.
public bool GetCancelling()
{
return cancelling;
}
public bool Cancelled
{
get { return cancelled; }
protected set { cancelled = value; }
}
/// <summary>
/// Check again whether this task may be cancelled. Must be called from off the event thread,
/// whereas CanCancel is called on it.
/// </summary>
public virtual void RecomputeCanCancel()
{
//Program.AssertOffEventThread();
try
{
XenRef<Task> task = _relatedTask;
if (task == null)
{
can_cancel = false;
return;
}
Session local_session = GetCancelSession();
if (local_session == null || string.IsNullOrEmpty(local_session.uuid))
{
can_cancel = false;
return;
}
CanCancel = Task.get_allowed_operations(local_session, task.opaque_ref).Contains(task_allowed_operations.cancel);
}
catch (Exception exn)
{
log.Error(exn, exn);
LogoutCancelSession();
can_cancel = false;
}
}
/// <summary>
/// Will return null if Connection is null.
/// </summary>
/// <returns></returns>
protected Session GetCancelSession()
{
lock (connectionLock)
{
if (Connection == null || !Connection.IsConnected)
return null;
if (_cancel_session == null)
{
if (_session == null)
{
_cancel_session = Connection.DuplicateSession(XenAdminConfigManager.Provider.ConnectionTimeout);
}
else if (_session.Url == null) // DbProxy
{
return null;
}
else
{
_cancel_session = XenAdminConfigManager.Provider.CreateActionSession(_session, Connection);
}
}
return _cancel_session;
}
}
protected void LogoutCancelSession()
{
lock (connectionLock)
{
_cancel_session = null;
}
}
/// <summary>
/// Cancels this action.
///
/// 1. Must be called on the event thread.
/// 2. Will return if Cancelling = true
/// 3. Runs RecomputeCanCancel() on a bg thread, then if CanCancel == true, sets Cancelling to true and runs Cancel_()
/// </summary>
public override sealed void Cancel()
{
//Program.AssertOnEventThread();
log.Debug("Cancel() was called. Attempting to cancel action");
// We can always cancel before the action starts running
lock (_startedRunningLock)
{
if (!_startedRunning)
{
cancelled = true;
new Thread(delegate()
{
AuditLogCancelled();
MarkCompleted(new CancelledException());
Clean();
CleanOnError();
}).Start();
return;
}
}
lock (_cancellinglock)
{
if (Cancelling)
return;
Cancelling = true;
}
Thread t = new Thread(DoCancel);
t.Name = string.Format("Cancelling task {0}", Title);
t.IsBackground = true;
t.Priority = ThreadPriority.Lowest;
t.Start();
}
private void DoCancel()
{
RecomputeCanCancel();
if (can_cancel)
{
try
{
CancelRelatedTask();
}
catch (Exception e)
{
log.DebugFormat("Exception when cancelling action {0}", this.Description);
log.Debug(e, e);
LogoutCancelSession();
Cancelling = false;
}
}
}
/// <summary>
/// Called by Cancel on a background thread, to do any heavy lifting.
/// Creates a new Session and cancels RelatedTask.
///
/// * Only called if RecomputeCanCancel sets CanCancel to true;
/// * Cancelling set to true before this call, but after call to RecomputeCanCancel and CanCancel.
/// * DO NOT CHECK CANCANCEL HERE, you will only be called with it as true, but it may change after.
///
/// </summary>
protected virtual void CancelRelatedTask()
{
PerformSilentTaskOp(delegate()
{
// Create a new session since this.Session may be in-use by the thread pool thread, and, in particular, TaskPoller
Session local_session = GetCancelSession();
XenRef<Task> r = RelatedTask;
if (r != null)
{
XenAPI.Task.cancel(local_session, r);
}
});
}
protected void PerformSilentTaskOp(Action f)
{
if (_relatedTask == null)
return;
try
{
f();
}
catch (XenAPI.Failure exn)
{
if (exn.ErrorDescription.Count > 1 &&
exn.ErrorDescription[0] == XenAPI.Failure.HANDLE_INVALID &&
exn.ErrorDescription[1] == "task")
{
log.Debug(exn, exn);
// The task has disappeared.
_relatedTask = null;
}
else
{
log.Error(exn, exn);
// Ignore, and hope that this isn't a problem.
}
}
catch (Exception exn)
{
log.Error(exn, exn);
// Ignore, and hope that this isn't a problem.
}
}
private readonly object _cancellinglock = new object();
// _startedRunningLock controls the case that two threads try and Cancel and Run an action
// at the same time: one of them has to win and prevent the other.
private object _startedRunningLock = new object();
private bool _startedRunning = false;
public bool StartedRunning
{
get { return _startedRunning; }
protected set
{
lock(_startedRunningLock)
{
_startedRunning = value;
}
}
}
/// <summary>
/// If there has been an exception this code will always execute after the action has finished, use for tidyup
/// </summary>
protected virtual void CleanOnError() { }
/// <summary>
/// This code will always execute after the action has finished, use for tidyup
/// </summary>
protected virtual void Clean() { }
}
}