xenadmin/XenModel/Actions/CancellingAction.cs
Mihaela Stoica 2ae2bdfdd8 CA-182453: XenCenter does not show an active XenMotion in another session
Added "VM.pool_migrate" to the list of tasks suitable for meddling actions.
Removed the usage of MeddlingActionTitle other_config key, because it wasn't working as intended for two reasons:
- If XenCenter starts an action that does multiple async api calls, so multiple tasks, the action's title is assigned to all tasks as MeddlingActionTitles, so the second XenCenter instance would create multiple meddling actions with the same title.
- When a second XenCenter instance tries to see if a task is suitable for a meddling action, the MeddlingActionTitle is not yet present in the task's other_config, so the task is ignored in most of the cases.

Signed-off-by: Mihaela Stoica <mihaela.stoica@citrix.com>
2017-01-18 14:25:55 +00:00

483 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.Threading;
using XenAdmin.Network;
using XenAPI;
using System.Collections.Generic;
using System.Net;
using CookComputing.XmlRpc;
using System.Reflection;
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;
protected CancellingAction(string title, string description, bool suppressHistory)
: base(title, description, suppressHistory)
{
}
protected 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; }
}
private delegate void SetXenCenterUUIDDelegate(Session session, string _task, string uuid);
private delegate void SetAppliesToDelegate(Session session, string _task, List<string> applies_to);
/// <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)
{
DoWithSessionRetry(ref _session, (SetXenCenterUUIDDelegate)Task.SetXenCenterUUID, _relatedTask.opaque_ref, XenAdminConfigManager.Provider.XenCenterUUID);
DoWithSessionRetry(ref _session, (SetAppliesToDelegate)Task.SetAppliesTo, _relatedTask.opaque_ref, AppliesTo);
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() { }
public virtual Session NewSession()
{
if (Connection == null)
return null;
return Connection.DuplicateSession();
}
/// <summary>
/// Overload for use by actions, using elevated credentials on the retry, if implemented in NewSession().
/// Try and run the delegate.
/// If it fails with a web exception or invalid session, try again.
/// Only retry 60 times.
/// </summary>
/// <param name="session"></param>
/// <param name="f"></param>
/// <param name="p"></param>
/// <returns></returns>
public object DoWithSessionRetry(ref Session session, Delegate f, params object[] p)
{
int retries = 60;
while (true)
{
try
{
object[] ps = new object[p.Length + 1];
ps[0] = session;
for (int i = 0; i < p.Length; i++)
{
ps[i + 1] = p[i];
}
try
{
return f.DynamicInvoke(ps);
}
catch (TargetInvocationException exn)
{
throw exn.InnerException;
}
}
catch (XmlRpcNullParameterException xmlExcept)
{
log.ErrorFormat("XmlRpcNullParameterException in DoWithSessionRetry, retry {0}", retries);
log.Error(xmlExcept, xmlExcept);
throw new Exception(Messages.INVALID_SESSION);
}
catch (XmlRpcIllFormedXmlException xmlRpcIllFormedXmlException)
{
log.ErrorFormat("XmlRpcIllFormedXmlException in DoWithSessionRetry, retry {0}", retries);
log.Error(xmlRpcIllFormedXmlException, xmlRpcIllFormedXmlException);
if (!Connection.ExpectDisruption || retries <= 0)
throw;
}
catch (WebException we)
{
log.ErrorFormat("WebException in DoWithSessionRetry, retry {0}", retries);
log.Error(we, we);
if (retries <= 0)
throw;
}
catch (Failure failure)
{
log.ErrorFormat("Failure in DoWithSessionRetry, retry {0}", retries);
log.Error(failure, failure);
if (retries <= 0)
throw;
if (failure.ErrorDescription.Count < 1 || failure.ErrorDescription[0] != XenAPI.Failure.SESSION_INVALID)
throw;
}
Session newSession;
try
{
// try to create a new TCP stream to use, as the other one has failed us
newSession = NewSession();
session = newSession;
}
catch (DisconnectionException e)
{
if (!Connection.ExpectDisruption)
{
//this was not expected, throw the d/c exception
throw e;
}
// We are expecting disruption on this connection. We need to wait for the hearbeat to recover.
// Though after 60 retries we will give up in the previous try catch block
}
catch
{
// do nothing
}
retries--;
Thread.Sleep(Connection.ExpectDisruption ? 500 : 100);
}
}
}
}