mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-11-23 20:36:33 +01:00
a8efbbfba2
the action was failing to create a metadata session; the latter was due to accessing directly properties of the xmlrpc proxy which is null since we use the jsonrpc backend. Also fixed some more areas where the same might occur. Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com>
220 lines
8.5 KiB
C#
220 lines
8.5 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.Net;
|
|
using System.Net.Sockets;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
using XenAPI;
|
|
using XenAdmin.Core;
|
|
|
|
namespace XenAdmin.Network
|
|
{
|
|
/// <summary>
|
|
/// A per-connection thread that periodically calls host.get_servertime. This is used to tell us the skew between the clocks on the client
|
|
/// and the server, but more importantly, to act as a connection heartbeat.
|
|
/// xapi will return from event.next every so often even if there are no new events, to act as the connection heartbeat in the other direction.
|
|
/// </summary>
|
|
public class Heartbeat
|
|
{
|
|
private const int HeartbeatInterval = 15000;
|
|
|
|
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
private readonly IXenConnection connection;
|
|
private Session session = null;
|
|
|
|
// Once set to false, the worker thread will eventually exit.
|
|
private volatile bool _run = true;
|
|
|
|
private bool running = false;
|
|
|
|
/// <summary>
|
|
/// If true, the heartbeat has already failed once, and we are giving the server a final second chance.
|
|
/// </summary>
|
|
private bool retrying = false;
|
|
|
|
private readonly int connectionTimeout;
|
|
|
|
public Heartbeat(IXenConnection connection, int connectionTimeout)
|
|
{
|
|
this.connection = connection;
|
|
this.connectionTimeout = connectionTimeout;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Should only be called once.
|
|
/// </summary>
|
|
public void Start()
|
|
{
|
|
// We shouldn't need the running flag -- this is just a safety catch.
|
|
if (!running)
|
|
{
|
|
running = true;
|
|
Thread t = new Thread(HeartbeatThread);
|
|
t.Name = "Heartbeat for " + connection.Hostname;
|
|
t.IsBackground = true;
|
|
t.Start();
|
|
}
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
_run = false;
|
|
}
|
|
|
|
/// <param name="o">Unused</param>
|
|
private void HeartbeatThread(object o)
|
|
{
|
|
log.DebugFormat("Heartbeat thread for connection to {0} started", connection.Hostname);
|
|
|
|
try
|
|
{
|
|
while (_run)
|
|
{
|
|
DoHeartbeat();
|
|
System.Threading.Thread.Sleep(HeartbeatInterval);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
log.DebugFormat("Heartbeat thread for connection to {0} terminated", connection.Hostname);
|
|
}
|
|
}
|
|
|
|
private readonly string heartbeatConnectionGroupName = Guid.NewGuid().ToString();
|
|
|
|
private void DoHeartbeat()
|
|
{
|
|
if (!connection.IsConnected)
|
|
return;
|
|
|
|
try
|
|
{
|
|
if (session == null)
|
|
{
|
|
// Try to get a new session, but only give the server one chance (otherwise we get the default 3x timeout)
|
|
session = connection.DuplicateSession(connectionTimeout < 5000 ? 5000 : connectionTimeout);
|
|
session.ConnectionGroupName = heartbeatConnectionGroupName; // this will force the Heartbeat session onto its own set of TCP streams (see CA-108676)
|
|
}
|
|
|
|
GetServerTime();
|
|
|
|
// Now that we've successfully received a heartbeat, reset our 'second chance' for the server to timeout
|
|
if (retrying)
|
|
log.DebugFormat("Heartbeat for {0} has come back", session.Url);
|
|
retrying = false;
|
|
}
|
|
catch (TargetInvocationException exn)
|
|
{
|
|
if (exn.InnerException is SocketException ||
|
|
exn.InnerException is WebException)
|
|
{
|
|
log.Debug(exn.Message);
|
|
}
|
|
else
|
|
{
|
|
log.Error(exn);
|
|
}
|
|
HandleConnectionLoss();
|
|
}
|
|
catch (WebException exn)
|
|
{
|
|
log.Error(exn);
|
|
var webResponse = (HttpWebResponse)exn.Response;
|
|
if (webResponse != null && webResponse.StatusCode == HttpStatusCode.ProxyAuthenticationRequired) // work-around for CA-214653
|
|
{
|
|
if (session == null)
|
|
log.Debug("Heartbeat has failed due to null session; closing the main connection");
|
|
else if (session.Credentials == null)
|
|
log.DebugFormat("Heartbeat for {0} has failed due to missing credentials; closing the main connection", session.Url);
|
|
else
|
|
log.DebugFormat("Heartbeat for {0} has failed due to incorrect credentials; closing the main connection", session.Url);
|
|
|
|
connection.Interrupt();
|
|
DropSession();
|
|
}
|
|
else
|
|
{
|
|
HandleConnectionLoss();
|
|
}
|
|
}
|
|
catch (Exception exn)
|
|
{
|
|
log.Error(exn);
|
|
HandleConnectionLoss();
|
|
}
|
|
}
|
|
|
|
private void GetServerTime()
|
|
{
|
|
Host master = Helpers.GetMaster(connection);
|
|
if (master == null)
|
|
return;
|
|
DateTime t = Host.get_servertime(session, master.opaque_ref);
|
|
connection.ServerTimeOffset = DateTime.UtcNow - t;
|
|
}
|
|
|
|
private void HandleConnectionLoss()
|
|
{
|
|
// We retry once, as the server is entitled to drop persistent connections from time to time.
|
|
// After that, we assume that the server's gone away.
|
|
// Note that this doubles the effective timeout before we decide the server has died.
|
|
if (retrying && !connection.ExpectDisruption)
|
|
{
|
|
log.DebugFormat("Heartbeat for {0} has failed for the second time; closing the main connection",
|
|
session == null ? "null" : session.Url);
|
|
connection.Interrupt();
|
|
DropSession();
|
|
}
|
|
else
|
|
{
|
|
log.DebugFormat("Heartbeat for {0} has failed; retrying",
|
|
session == null ? "null" : session.Url);
|
|
retrying = true;
|
|
}
|
|
}
|
|
|
|
/// Drop the session so that we'll get a new one the next time.
|
|
/// This is used when handling exceptions.
|
|
private void DropSession()
|
|
{
|
|
Session s = session;
|
|
session = null;
|
|
// Do this on a new thread: if the master has died, then the session logout may block
|
|
// up to the timeout. Doing this on the calling thread would mean doubling the actual time elapsed
|
|
// before we decide the server is dead.
|
|
connection.Logout(s);
|
|
}
|
|
}
|
|
}
|