2023-01-24 15:29:31 +01:00
/ * Copyright ( c ) Cloud Software Group , Inc .
2013-06-24 13:41:48 +02:00
*
* 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.Globalization ;
using System.Net ;
using System.Reflection ;
using System.Threading ;
using XenAdmin.Actions ;
using XenAdmin.Core ;
using XenAPI ;
2017-11-17 02:04:45 +01:00
using XenCenterLib ;
2013-06-24 13:41:48 +02:00
using System.Diagnostics ;
2023-03-22 23:10:52 +01:00
using System.IO ;
2021-10-04 16:09:42 +02:00
using System.Linq ;
2023-03-22 23:10:52 +01:00
using System.Xml ;
2013-06-24 13:41:48 +02:00
using System.Xml.Serialization ;
2017-11-17 02:04:45 +01:00
2013-06-24 13:41:48 +02:00
namespace XenAdmin.Network
2022-10-20 14:53:14 +02:00
{
2013-06-24 13:41:48 +02:00
[DebuggerDisplay("IXenConnection :{HostnameWithPort}")]
2022-10-20 14:53:14 +02:00
public class XenConnection : IXenConnection , IXmlSerializable
2013-06-24 13:41:48 +02:00
{
2023-03-10 14:28:23 +01:00
private static readonly log4net . ILog log = log4net . LogManager . GetLogger ( MethodBase . GetCurrentMethod ( ) . DeclaringType ) ;
2013-06-24 13:41:48 +02:00
public string Hostname { get ; set ; }
public int Port { get ; set ; }
public string Username { get ; set ; }
public string Password { get ; set ; }
public string FriendlyName { get ; set ; }
/// <summary>
/// The last known name for the pool, in the form "'Pool friendly name' (hostname or IP)".
/// </summary>
public string LastConnectionFullName = "" ;
/// <summary>
/// Whether this connection is saved in a disconnected state (i.e. don't reconnect on session restore).
/// </summary>
public bool SaveDisconnected { get ; set ; }
/// <summary>
/// Indicates whether this connection was created in the Add Server dialog.
/// </summary>
public bool fromDialog = false ;
2023-03-22 23:10:52 +01:00
private volatile bool _expectedDisruption ;
private volatile bool _suppressErrors ;
private volatile bool _preventResettingPasswordPrompt ;
private volatile bool _coordinatorMayChange ;
2013-06-24 13:41:48 +02:00
/// <summary>
/// Whether we're expecting network disruption, say because we're reconfiguring the network at this time. In that case, we ignore
/// keepalive failures, and expect task polling to be disrupted.
/// </summary>
2023-03-22 23:10:52 +01:00
public bool ExpectDisruption
{
get = > _expectedDisruption ;
set = > _expectedDisruption = value ;
}
2013-06-24 13:41:48 +02:00
/// <summary>
/// If we are 'expecting' this connection's Password property to contain the correct password
/// (i.e. false if the user has just entered the password, true if it was restored from the saved session).
/// </summary>
public bool ExpectPasswordIsCorrect { get ; set ; }
/// <summary>
2019-10-27 16:50:20 +01:00
/// Used by the patch wizard, suppress any errors coming from reconnect attempts
2013-06-24 13:41:48 +02:00
/// </summary>
2019-10-27 16:50:20 +01:00
public bool SuppressErrors
{
get = > _suppressErrors ;
set = > _suppressErrors = value ;
}
2013-06-24 13:41:48 +02:00
2022-10-20 14:52:12 +02:00
/// <summary>
/// The password prompting function (<see cref="_promptForNewPassword"/>) is set to null when the connection is closed.
/// Set this value to true in order to prevent that from happening.
/// n.b.: remember to set the value back to false once it's not needed anymore
/// </summary>
public bool PreventResettingPasswordPrompt
{
get = > _preventResettingPasswordPrompt ;
set = > _preventResettingPasswordPrompt = value ;
}
2013-06-24 13:41:48 +02:00
/// <summary>
2021-08-31 12:31:16 +02:00
/// Indicates whether we are expecting the pool coordinator to change soon (e.g. when explicitly designating a new coordinator).
2013-06-24 13:41:48 +02:00
/// </summary>
2023-03-22 23:10:52 +01:00
public bool CoordinatorMayChange
{
get = > _coordinatorMayChange ;
set = > _coordinatorMayChange = value ;
}
2013-06-24 13:41:48 +02:00
/// <summary>
/// Set when we detect that Event.next() has become blocked and we need to reset the connection. See CA-33145.
/// </summary>
private bool EventNextBlocked = false ;
/// <summary>
/// A cache of the pool's opaque_ref, last time this connection was connected. This will be
/// Helper.NullOpaqueRef if it's never been connected.
/// </summary>
private string PoolOpaqueRef = Helper . NullOpaqueRef ;
/// <summary>
/// Set after a successful connection attempt, before the cache is populated.
/// </summary>
2021-08-31 12:31:16 +02:00
private string CoordinatorIPAddress = "" ;
2013-06-24 13:41:48 +02:00
/// <summary>
2023-03-22 23:10:52 +01:00
/// The lock that must be taken around connectTask and heartbeat.
2013-06-24 13:41:48 +02:00
/// </summary>
private readonly object connectTaskLock = new object ( ) ;
private ConnectTask connectTask = null ;
2023-03-22 23:10:52 +01:00
/// <summary>
/// This is the metrics monitor. Has to be accessed within the connectTaskLock.
/// </summary>
private Heartbeat heartbeat ;
2013-06-24 13:41:48 +02:00
/// <summary>
2021-08-31 12:31:16 +02:00
/// Whether we are trying to automatically connect to the new coordinator. Set in HandleConnectionLost.
2013-06-24 13:41:48 +02:00
/// Note: I think we are not using this correctly -- see CA-37864 for details -- but I'm not going
/// to fix it unless it gives rise to a reported bug, because I can't test the fix.
/// </summary>
2021-08-31 12:31:16 +02:00
private volatile bool FindingNewCoordinator = false ;
2013-06-24 13:41:48 +02:00
/// <summary>
2021-08-31 12:31:16 +02:00
/// The time at which we started looking for the new coordinator.
2013-06-24 13:41:48 +02:00
/// </summary>
2021-08-31 12:31:16 +02:00
private DateTime FindingNewCoordinatorStartedAt = DateTime . MinValue ;
2013-06-24 13:41:48 +02:00
/// <summary>
/// Timeout before we consider that Event.next() has got blocked: see CA-33145
/// </summary>
private const int EVENT_NEXT_TIMEOUT = 120 * 1000 ; // 2 minutes
2021-08-31 12:31:16 +02:00
private string LastCoordinatorHostname = "" ;
2013-06-24 13:41:48 +02:00
public readonly object PoolMembersLock = new object ( ) ;
private List < string > _poolMembers = new List < string > ( ) ;
public List < string > PoolMembers { get { return _poolMembers ; } set { _poolMembers = value ; } }
private int PoolMemberIndex = 0 ;
private System . Threading . Timer ReconnectionTimer = null ;
private ActionBase ConnectAction ;
private DateTime m_startTime = DateTime . MinValue ;
private int m_lastDebug ;
2023-03-22 23:10:52 +01:00
private TimeSpan _serverTimeOffset = TimeSpan . Zero ;
2013-06-24 13:41:48 +02:00
private object ServerTimeOffsetLock = new object ( ) ;
/// <summary>
/// The offset between the clock at the client and the clock at the server. server time + ServerTimeOffset = client time.
/// This does not take the local timezone into account -- all calculations should be in UTC.
/// For Orlando and greater, this value is set by XenMetricsMonitor, calling Host.get_servertime on a heartbeat.
/// </summary>
public TimeSpan ServerTimeOffset
{
get
{
lock ( ServerTimeOffsetLock )
{
2023-03-22 23:10:52 +01:00
return _serverTimeOffset ;
2013-06-24 13:41:48 +02:00
}
}
set
{
lock ( ServerTimeOffsetLock )
{
2023-03-22 23:10:52 +01:00
TimeSpan diff = _serverTimeOffset - value ;
2013-06-24 13:41:48 +02:00
if ( diff . TotalSeconds < - 1 | | 1 < diff . TotalSeconds )
{
var now = DateTime . UtcNow ;
var debugMsg = string . Format ( "Time offset for {0} is now {1}. It's now {2} UTC here, and {3} UTC on the server." ,
Hostname , value ,
now . ToString ( "o" , CultureInfo . InvariantCulture ) ,
( now . Subtract ( value ) ) . ToString ( "o" , CultureInfo . InvariantCulture ) ) ;
2022-10-20 14:53:14 +02:00
if ( m_startTime . Ticks = = 0 ) //log it the first time it is detected
2013-06-24 13:41:48 +02:00
{
m_startTime = now ;
2016-02-12 12:29:19 +01:00
log . Info ( debugMsg ) ;
2013-06-24 13:41:48 +02:00
}
//then log every 5mins
2022-10-20 14:53:14 +02:00
int currDebug = ( int ) ( ( now - m_startTime ) . TotalSeconds ) / 300 ;
2013-06-24 13:41:48 +02:00
if ( currDebug > m_lastDebug )
{
m_lastDebug = currDebug ;
2016-02-12 12:29:19 +01:00
log . InfoFormat ( debugMsg ) ;
2013-06-24 13:41:48 +02:00
}
}
2023-03-22 23:10:52 +01:00
_serverTimeOffset = value ;
2013-06-24 13:41:48 +02:00
}
}
}
public string HostnameWithPort
{
get
{
return Port = = ConnectionsManager . DEFAULT_XEN_PORT ? Hostname : Hostname + ':' + Port . ToString ( ) ;
}
}
public string UriScheme
{
get
{
return Port = = 8080 | | Port = = 80 ? Uri . UriSchemeHttp : Uri . UriSchemeHttps ;
}
}
public string Name
{
get
{
string result = Helpers . GetName ( Helpers . GetPoolOfOne ( this ) ) ;
return ! string . IsNullOrEmpty ( result )
? result
: ! string . IsNullOrEmpty ( FriendlyName ) ? FriendlyName : Hostname ;
}
}
/// <summary>
/// The cache of XenAPI objects for this connection.
/// </summary>
2023-03-22 23:10:52 +01:00
public ICache Cache { get ; } = new Cache ( ) ;
2013-06-24 13:41:48 +02:00
private readonly LockFreeQueue < ObjectChange > eventQueue = new LockFreeQueue < ObjectChange > ( ) ;
private readonly System . Threading . Timer cacheUpdateTimer ;
/// <summary>
/// Whether the cache for this connection has been populated.
/// </summary>
public bool CacheIsPopulated
{
2023-03-22 23:10:52 +01:00
get = > _cacheIsPopulated ;
private set
{
_cacheIsPopulated = value ;
if ( _cacheIsPopulated )
CachePopulated ? . Invoke ( this ) ;
}
2013-06-24 13:41:48 +02:00
}
2023-03-22 23:10:52 +01:00
private bool _cacheIsPopulated ;
2013-06-24 13:41:48 +02:00
private bool cacheUpdaterRunning = false ;
private bool updatesWaiting = false ;
/// <summary>
/// Initializes a new instance of the <see cref="XenConnection"/> class.
/// </summary>
public XenConnection ( )
{
Port = ConnectionsManager . DEFAULT_XEN_PORT ;
Username = "root" ;
SaveDisconnected = false ;
2023-03-10 14:28:23 +01:00
ExpectPasswordIsCorrect = true ;
cacheUpdateTimer = new Timer ( cacheUpdater ) ;
2013-06-24 13:41:48 +02:00
}
2023-03-22 23:10:52 +01:00
#region Events
2013-06-24 13:41:48 +02:00
/// <summary>
/// Fired just before the cache is cleared (i.e. the cache is still populated).
/// </summary>
2019-10-27 16:52:15 +01:00
public event Action < IXenConnection > ClearingCache ;
public event Action < IXenConnection > CachePopulated ;
2013-06-24 13:41:48 +02:00
public event EventHandler < ConnectionResultEventArgs > ConnectionResult ;
2019-10-27 16:52:15 +01:00
public event Action < IXenConnection > ConnectionStateChanged ;
public event Action < IXenConnection > ConnectionLost ;
public event Action < IXenConnection > ConnectionClosed ;
public event Action < IXenConnection > ConnectionReconnecting ;
public event Action < IXenConnection > BeforeConnectionEnd ;
public event Action < IXenConnection , string > ConnectionMessageChanged ;
public event Action < IXenConnection , bool > BeforeMajorChange ;
public event Action < IXenConnection , bool > AfterMajorChange ;
2013-06-24 13:41:48 +02:00
/// <summary>
/// Fired on the UI thread, once per batch of events in CacheUpdater.
/// </summary>
public event EventHandler < EventArgs > XenObjectsUpdated ;
2019-08-22 11:47:43 +02:00
2023-03-22 23:10:52 +01:00
#endregion
public void LoadFromDatabaseFile ( string databaseFile )
{
IsSimulatedConnection = true ;
var foundConn = ConnectionsManager . XenConnections . Cast < XenConnection > ( ) . FirstOrDefault ( c = >
c . Hostname = = databaseFile & & c . CoordinatorIPAddress = = CoordinatorIPAddress ) ;
if ( foundConn ! = null & & foundConn . IsConnected )
throw new ConnectionExists ( this ) ;
var document = new XmlDocument ( ) ;
using ( var sr = new StreamReader ( databaseFile ) )
document . LoadXml ( sr . ReadToEnd ( ) ) ;
var db = new Db ( document ) ;
var events = db . Tables . SelectMany ( t = > t . Rows ) . Select ( r = > r . ObjectChange ) . ToList ( ) ;
2023-07-10 13:58:41 +02:00
var session = new Session ( this , Path . GetFileName ( Hostname ) , Port ) ;
2023-03-22 23:10:52 +01:00
connectTask = new ConnectTask ( Hostname , Port ) { Connected = true , Session = session } ;
OnBeforeMajorChange ( false ) ;
Cache . Clear ( ) ;
CacheIsPopulated = false ;
OnAfterMajorChange ( false ) ;
OnBeforeMajorChange ( false ) ;
Cache . UpdateFrom ( this , events ) ;
OnAfterMajorChange ( false ) ;
CacheIsPopulated = true ;
var pool = Cache . Pools [ 0 ] ;
CoordinatorIPAddress = Cache . Hosts . FirstOrDefault ( h = > h . opaque_ref = = pool . master ) ? . address ;
HandleSuccessfulConnection ( connectTask , pool ) ;
MarkConnectActionComplete ( ) ;
OnXenObjectsUpdated ( ) ;
}
2019-08-22 11:47:43 +02:00
public NetworkCredential NetworkCredential { get ; set ; }
2023-03-22 23:10:52 +01:00
public bool IsSimulatedConnection { get ; private set ; }
2013-06-24 13:41:48 +02:00
public bool IsConnected
{
get
{
// sneaky, but avoids a lock
ConnectTask t = connectTask ;
return t ! = null & & t . Connected ;
}
}
public bool InProgress
{
get { return connectTask ! = null ; }
}
/// <summary>
/// Gets the Session passed to ConnectWorkerThread and used to update the cache in XenObjectDownloader.
/// May return null.
/// </summary>
public Session Session
{
get
{
ConnectTask t = connectTask ;
return t = = null ? null : t . Session ;
}
}
/// <summary>
/// Create a duplicate of the Session that this connection is currently using.
/// This will use a separate TCP stream, but the same authentication credentials.
/// </summary>
/// <returns></returns>
public Session DuplicateSession ( )
{
return DuplicateSession ( Session . STANDARD_TIMEOUT ) ;
}
public Session DuplicateSession ( int timeout )
{
Session s = Session ;
if ( s = = null )
throw new DisconnectionException ( ) ;
2023-07-10 13:58:41 +02:00
return new Session ( s , this ) { Timeout = timeout } ;
2013-06-24 13:41:48 +02:00
}
/// <summary>
2013-08-19 17:59:41 +02:00
/// For retrieving an extra session using different credentials to those stored in the connection. Used for the sudo
/// function in actions. Does not prompt for new credentials if the authentication fails.
/// Does not change connection's Username and Password.
2013-06-24 13:41:48 +02:00
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <returns></returns>
public Session ElevatedSession ( string username , string password )
{
return GetNewSession ( Hostname , Port , username , password , true ) ;
}
/// <summary>
2013-08-19 17:59:41 +02:00
/// For retrieving a new session.
2013-06-24 13:41:48 +02:00
/// </summary>
/// <param name="hostname"></param>
/// <param name="port"></param>
/// <param name="username"></param>
/// <param name="password"></param>
/// <param name="isElevated"></param>
/// <returns>null if the server password has changed, the user has been prompted for the new password,
/// but the user has clicked cancel on the dialog.</returns>
private Session GetNewSession ( string hostname , int port , string username , string password , bool isElevated )
{
const int DELAY = 250 ; // unit = ms
int attempt = 0 ;
while ( true )
{
attempt + + ;
2013-08-19 17:59:41 +02:00
string uname = isElevated ? username : Username ;
string pwd = isElevated ? password : Password ; // Keep the password that we're using for this iteration, as it may
2013-06-24 13:41:48 +02:00
// be changed by another thread handling an authentication failure.
2013-08-19 17:59:41 +02:00
// For elevated session we use the elevated username and password passed into this function,
// as the connection's Username and Password are not updated.
2013-06-24 13:41:48 +02:00
2023-07-10 13:58:41 +02:00
Session session = new Session ( this , hostname , port ) ;
2013-06-24 13:41:48 +02:00
if ( isElevated )
session . IsElevatedSession = true ;
2023-07-10 13:58:41 +02:00
2013-06-24 13:41:48 +02:00
try
{
2023-03-22 16:41:03 +01:00
session . login_with_password ( uname , pwd , Helper . APIVersionString ( API_Version . LATEST ) , Session . UserAgent ) ;
2019-08-22 11:47:43 +02:00
NetworkCredential = new NetworkCredential ( uname , pwd ) ;
2013-06-24 13:41:48 +02:00
return session ;
}
catch ( Failure f )
{
if ( connectTask = = null | | connectTask . Cancelled ) // the user has clicked cancel on the connection to server dialog
{
attempt = DEFAULT_MAX_SESSION_LOGIN_ATTEMPTS ; // make sure we throw rather than try again
throw new CancelledException ( ) ; // make the dialog pop up again
}
else if ( f . ErrorDescription . Count > 0 )
{
switch ( f . ErrorDescription [ 0 ] )
{
case Failure . SESSION_AUTHENTICATION_FAILED :
if ( isElevated )
throw ;
if ( PromptForNewPassword ( pwd ) )
attempt = 0 ;
else
{
//user cannot provide correct credentials, we d/c now to save the confusion of having the server available
//but unusable.
EndConnect ( ) ;
throw new CancelledException ( ) ;
}
break ;
case Failure . HOST_IS_SLAVE :
2022-10-20 14:53:14 +02:00
// we know it is a supporter so there there is no need to try and connect again, we need to connect to the coordinator
2013-06-24 13:41:48 +02:00
case Failure . RBAC_PERMISSION_DENIED :
2022-10-20 14:53:14 +02:00
// No point retrying this, the user needs the read only role at least to log in
2013-06-24 13:41:48 +02:00
case Failure . HOST_UNKNOWN_TO_MASTER :
// Will never succeed, CA-74718
throw ;
default :
if ( isElevated )
{
if ( attempt > = DEFAULT_MAX_SESSION_LOGIN_ATTEMPTS )
throw ;
else
break ;
}
else
break ;
}
}
else
{
if ( attempt > = DEFAULT_MAX_SESSION_LOGIN_ATTEMPTS )
throw ;
}
}
catch ( WebException e )
{
if ( e . Status = = WebExceptionStatus . TrustFailure )
throw new CancelledException ( ) ;
if ( e . Status = = WebExceptionStatus . NameResolutionFailure | | e . Status = = WebExceptionStatus . ProtocolError | | attempt > = DEFAULT_MAX_SESSION_LOGIN_ATTEMPTS )
throw ;
}
catch ( UriFormatException )
{
// No point trying to connect more than once to a duff URI
throw ;
}
catch ( Exception )
{
if ( attempt > = DEFAULT_MAX_SESSION_LOGIN_ATTEMPTS )
throw ;
}
Thread . Sleep ( DELAY ) ;
}
}
// This CompareTo() mimics the sort order we get from XenSearch, with
// pools first, then hosts, then disconnected connections. It is used
// in some dialogs where we don't do a proper search: see CA-57131 &
// CA-60517 for examples.
public int CompareTo ( IXenConnection other )
{
if ( this = = other )
return 0 ;
if ( other = = null )
return - 1 ;
Pool thisPool = Helpers . GetPool ( this ) ;
Pool otherPool = Helpers . GetPool ( other ) ;
int thisClass = ( this . IsConnected ?
( thisPool = = null ? 2 : 1 ) : 3 ) ;
int otherClass = ( other . IsConnected ?
( otherPool = = null ? 2 : 1 ) : 3 ) ;
if ( thisClass ! = otherClass )
return thisClass - otherClass ;
int result = StringUtility . NaturalCompare ( Name , other . Name ) ;
if ( result ! = 0 )
return result ;
Pool p1 = Helpers . GetPoolOfOne ( this ) ;
Pool p2 = Helpers . GetPoolOfOne ( other ) ;
if ( p1 = = null | | p2 = = null )
return 0 ; // shouldn't happen once connected, but let's be safe
return p1 . opaque_ref . CompareTo ( p2 . opaque_ref ) ;
}
2013-11-05 15:40:16 +01:00
private void SetPoolAndHostInAction ( ActionBase action , Pool pool , string poolopaqueref )
2013-06-24 13:41:48 +02:00
{
if ( pool ! = null & & ! string . IsNullOrEmpty ( poolopaqueref ) )
{
Pool p = new Pool ( ) ;
p . Connection = this ;
p . opaque_ref = poolopaqueref ;
p . UpdateFrom ( pool ) ;
Host h = new Host ( ) ;
h . Connection = this ;
h . opaque_ref = pool . master . opaque_ref ;
2013-11-05 15:40:16 +01:00
h . name_label = Hostname ;
2013-06-24 13:41:48 +02:00
2013-11-05 15:40:16 +01:00
action . Pool = p ;
action . Host = h ;
2013-06-24 13:41:48 +02:00
}
else
{
// Match the hack in XenSearch/GroupAlg.cs. We are creating fake Host objects to
// represent the disconnected server, with the opaque_ref set to the connection's HostnameWithPort.
// We need to do the same here, so that Actions for disconnected hosts (like failed connections)
// are attached to the disconnected server correctly.
Host host = new Host ( ) ;
host . Connection = this ;
host . opaque_ref = HostnameWithPort ;
2013-11-05 15:40:16 +01:00
action . Host = host ;
2013-06-24 13:41:48 +02:00
}
}
private Func < IXenConnection , string , bool > _promptForNewPassword ;
/// <summary>
///
/// </summary>
2021-08-31 12:31:16 +02:00
/// <param name="initiateCoordinatorSearch">If true, if connection to the coordinator fails we will start trying to connect to
/// each remembered supporter in turn.</param>
2013-06-24 13:41:48 +02:00
/// <param name="promptForNewPassword">A function that prompts the user for the changed password for a server.</param>
2021-08-31 12:31:16 +02:00
public void BeginConnect ( bool initiateCoordinatorSearch , Func < IXenConnection , string , bool > promptForNewPassword )
2013-06-24 13:41:48 +02:00
{
_promptForNewPassword = promptForNewPassword ;
//InvokeHelper.Synchronizer is used for synchronizing the cache update. Must not be null at this point. It can be initialized through InvokeHelper.Initialize()
Trace . Assert ( InvokeHelper . Synchronizer ! = null ) ;
2022-10-20 14:53:14 +02:00
2013-06-24 13:41:48 +02:00
InvokeHelper . AssertOnEventThread ( ) ;
2021-08-31 12:31:16 +02:00
if ( initiateCoordinatorSearch )
2013-06-24 13:41:48 +02:00
{
2021-08-31 12:31:16 +02:00
FindingNewCoordinator = true ;
FindingNewCoordinatorStartedAt = DateTime . Now ;
2013-06-24 13:41:48 +02:00
}
2021-08-31 12:31:16 +02:00
CoordinatorMayChange = false ;
2013-06-24 13:41:48 +02:00
if ( ! HandlePromptForNewPassword ( ) )
return ;
lock ( connectTaskLock )
{
2023-03-22 23:10:52 +01:00
//if connectTask != null a connection is already in progress
2013-06-24 13:41:48 +02:00
if ( connectTask = = null )
{
ClearEventQueue ( ) ;
OnBeforeMajorChange ( false ) ;
Cache . Clear ( ) ;
OnAfterMajorChange ( false ) ;
connectTask = new ConnectTask ( Hostname , Port ) ;
2023-03-22 23:10:52 +01:00
heartbeat ? . Stop ( ) ;
heartbeat = null ;
2013-06-24 13:41:48 +02:00
heartbeat = new Heartbeat ( this , XenAdminConfigManager . Provider . ConnectionTimeout ) ;
2023-03-22 23:10:52 +01:00
2013-06-24 13:41:48 +02:00
Thread t = new Thread ( ConnectWorkerThread ) ;
t . Name = "Connection to " + Hostname ;
t . IsBackground = true ;
t . Start ( connectTask ) ;
}
}
}
private static object WaitForMonitor = new object ( ) ;
private static int WaitForEventRegistered = 0 ;
private static object WaitForEventRegisteredLock = new object ( ) ;
public void WaitFor ( Func < bool > predicate , Func < bool > cancelling )
{
lock ( WaitForEventRegisteredLock )
{
if ( WaitForEventRegistered = = 0 )
XenObjectsUpdated + = WakeWaitFor ;
WaitForEventRegistered + + ;
}
try
{
2020-05-04 01:32:53 +02:00
for ( int i = 0 ; i < 120 ; i + + )
2013-06-24 13:41:48 +02:00
{
lock ( WaitForMonitor )
{
if ( predicate ( ) | | ( cancelling ! = null & & cancelling ( ) ) )
return ;
System . Threading . Monitor . Wait ( WaitForMonitor , 500 ) ;
}
}
}
finally
{
lock ( WaitForEventRegisteredLock )
{
WaitForEventRegistered - - ;
if ( WaitForEventRegistered = = 0 )
XenObjectsUpdated - = WakeWaitFor ;
}
}
}
private void WakeWaitFor ( object sender , EventArgs e )
{
lock ( WaitForMonitor )
{
System . Threading . Monitor . PulseAll ( WaitForMonitor ) ;
}
}
/// <summary>
/// Equivalent to WaitForCache(xenref, null).
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="xenref"></param>
/// <returns></returns>
public T WaitForCache < T > ( XenRef < T > xenref ) where T : XenObject < T >
{
return WaitForCache ( xenref , null ) ;
}
/// <summary>
/// Wait for the given object to arrive in our cache. Returns the XenObject, or null if it does
/// not appear within the timeout (1 minute).
///
/// This blocks, so must only be used on a background thread.
/// </summary>
2017-09-13 18:14:07 +02:00
/// <param name="xenref"></param>
/// <param name="cancelling">A delegate to check whether to cancel. May be null, in which case it's ignored</param>
2013-06-24 13:41:48 +02:00
public T WaitForCache < T > ( XenRef < T > xenref , Func < bool > cancelling ) where T : XenObject < T >
{
lock ( WaitForEventRegisteredLock )
{
if ( WaitForEventRegistered = = 0 )
XenObjectsUpdated + = WakeWaitFor ;
WaitForEventRegistered + + ;
}
try
{
for ( int i = 0 ; i < 120 ; i + + )
{
lock ( WaitForMonitor )
{
T result = Resolve < T > ( xenref ) ;
if ( result ! = null | | ( cancelling ! = null & & cancelling ( ) ) )
return result ;
System . Threading . Monitor . Wait ( WaitForMonitor , 500 ) ;
}
}
return null ;
}
finally
{
lock ( WaitForEventRegisteredLock )
{
WaitForEventRegistered - - ;
if ( WaitForEventRegistered = = 0 )
XenObjectsUpdated - = WakeWaitFor ;
}
}
}
2018-10-03 11:54:34 +02:00
/// <param name="clearCache">Whether the cache should be cleared (requires invoking onto the GUI thread)</param>
2018-03-20 14:23:00 +01:00
/// <param name="exiting"></param>
2018-10-03 11:54:34 +02:00
public void EndConnect ( bool clearCache = true , bool exiting = false )
2013-06-24 13:41:48 +02:00
{
ConnectTask t = connectTask ;
connectTask = null ;
2018-10-03 11:54:34 +02:00
EndConnect ( clearCache , t , exiting ) ;
2013-06-24 13:41:48 +02:00
}
/// <summary>
/// Closes the connecting dialog, stops the XenMetricsMonitor thread, marks this.task as Cancelled and
/// logs out of the task's Session on a background thread.
/// </summary>
/// <param name="clearCache">Whether the cache should be cleared (requires invoking onto the GUI thread)</param>
/// <param name="task"></param>
2018-03-20 14:23:00 +01:00
/// <param name="exiting"></param>
2017-11-27 07:38:21 +01:00
private void EndConnect ( bool clearCache , ConnectTask task , bool exiting )
2013-06-24 13:41:48 +02:00
{
OnBeforeConnectionEnd ( ) ;
lock ( connectTaskLock )
{
2023-03-22 23:10:52 +01:00
heartbeat ? . Stop ( ) ;
heartbeat = null ;
2013-06-24 13:41:48 +02:00
if ( task ! = null )
{
task . Cancelled = true ;
Session session = task . Session ;
task . Session = null ;
if ( session ! = null )
{
2017-11-27 07:38:21 +01:00
Logout ( session , exiting ) ;
2013-06-24 13:41:48 +02:00
}
}
}
MarkConnectActionComplete ( ) ;
2023-03-22 23:10:52 +01:00
log . Info ( $"Connection to {Hostname} is ended." ) ;
2013-06-24 13:41:48 +02:00
// Save list of addresses of current hosts in pool
List < string > members = new List < string > ( ) ;
foreach ( Host host in Cache . Hosts )
{
members . Add ( host . address ) ;
}
lock ( PoolMembersLock )
{
PoolMembers = members ;
}
// Clear the XenAPI object cache
if ( clearCache )
{
ClearCache ( ) ;
}
2022-10-20 14:52:12 +02:00
if ( ! PreventResettingPasswordPrompt )
{
// CA-371356: Preventing the reset of the prompt allows
// for it to be shown when attempting to reconnect to a host
// whose password has changed since last login
_promptForNewPassword = null ;
}
2018-10-03 12:05:41 +02:00
OnConnectionClosed ( ) ;
2013-06-24 13:41:48 +02:00
}
/// <summary>
/// Try to logout the given session. This will cause any threads blocking on Event.next() to get
2021-08-31 12:31:16 +02:00
/// a XenAPI.Failure (which is better than them freezing around forever).
/// Do on a background thread - otherwise, if the coordinator has died, then this will block
2013-06-24 13:41:48 +02:00
/// until the timeout is reached (default 20s).
2017-11-27 07:38:21 +01:00
/// However, in the case of exiting, the thread need to be set as foreground.
/// Otherwise the logging out operation can be terminated when other foreground threads finish.
2013-06-24 13:41:48 +02:00
/// </summary>
/// <param name="session">May be null, in which case nothing happens.</param>
2018-03-20 14:23:00 +01:00
/// <param name="exiting"></param>
2017-11-27 07:38:21 +01:00
public void Logout ( Session session , bool exiting = false )
2013-06-24 13:41:48 +02:00
{
2018-02-12 12:54:27 +01:00
if ( session = = null | | session . opaque_ref = = null )
2013-06-24 13:41:48 +02:00
return ;
Thread t = new Thread ( Logout_ ) ;
2018-02-12 12:54:27 +01:00
t . Name = string . Format ( "Logging out session {0}" , session . opaque_ref ) ;
2017-11-27 07:38:21 +01:00
if ( exiting )
{
t . IsBackground = false ;
t . Priority = ThreadPriority . AboveNormal ;
}
else
{
t . IsBackground = true ;
t . Priority = ThreadPriority . Lowest ;
}
2013-06-24 13:41:48 +02:00
t . Start ( session ) ;
}
public void Logout ( )
{
Logout ( Session ) ;
}
private static void Logout_ ( object o )
{
Session session = ( Session ) o ;
try
{
log . Debug ( "Trying Session.logout() on background thread" ) ;
session . logout ( ) ;
log . Debug ( "Session.logout() succeeded" ) ;
}
catch ( Exception e )
{
log . Debug ( "Session.logout() failed" , e ) ;
}
}
public void Interrupt ( )
{
ConnectTask t = connectTask ;
ICache coll = Cache ;
connectTask = null ;
if ( t ! = null & & t . Connected )
{
string poolopaqueref = null ;
Pool pool = coll = = null ? null : getAPool ( coll , out poolopaqueref ) ;
t . Cancelled = true ;
HandleConnectionLost ( t , pool , poolopaqueref ) ;
}
}
private void ClearCache ( )
{
OnClearingCache ( ) ;
ClearEventQueue ( ) ;
// This call to Clear needs to occur on the background thread, otherwise the event firing in response to all the changes
// block in one big lump rather than the smaller pieces that you get when invoking onto the event thread on a finer
// granularity. If you do all this on the event thread, then the app tends to go (Not Responding) when you lose a connection.
// It doesn't actually occur on the background thread all the time. There's a path from AddServerDialog.ConnectToServer.
Cache . Clear ( ) ;
}
private string GetReason ( Exception error )
{
2023-03-10 14:28:23 +01:00
if ( error is ArgumentException )
2013-06-24 13:41:48 +02:00
{
// This happens if the server API is incompatible with our bindings. This should
// never happen in production, but will happen during development if a field
// changes type, for example.
2021-03-16 02:50:45 +01:00
return string . Format ( Messages . SERVER_API_INCOMPATIBLE , BrandManager . BrandConsole ) ;
2013-06-24 13:41:48 +02:00
}
else if ( error is WebException )
{
WebException w = error as WebException ;
if ( w . Status = = WebExceptionStatus . NameResolutionFailure )
{
return string . Format ( Messages . CONNECT_RESOLUTION_FAILURE , this . Hostname ) ;
}
else if ( w . Status = = WebExceptionStatus . ConnectFailure )
{
return string . Format ( Messages . CONNCET_CONNECTION_FAILURE , this . Hostname ) ;
}
else if ( w . Status = = WebExceptionStatus . ReceiveFailure )
{
2018-09-21 03:08:54 +02:00
return string . Format ( Messages . ERROR_NO_XENSERVER , this . Hostname ) ;
2013-06-24 13:41:48 +02:00
}
2016-05-23 13:48:01 +02:00
else if ( w . Status = = WebExceptionStatus . SecureChannelFailure )
{
return string . Format ( Messages . ERROR_SECURE_CHANNEL_FAILURE , this . Hostname ) ;
}
2013-06-24 13:41:48 +02:00
else
{
return w . Message ;
}
}
else if ( error is Failure & & error ! = null & & ! string . IsNullOrEmpty ( error . Message ) )
{
Failure f = error as Failure ;
if ( f . ErrorDescription [ 0 ] = = Failure . RBAC_PERMISSION_DENIED )
{
// we use a different error message here from the standard one in friendly names
return Messages . ERROR_NO_PERMISSION ;
}
return error . Message ;
}
else if ( error is NullReferenceException & & error . Source . StartsWith ( "CookComputing" ) )
{
return string . Format ( Messages . CONNCET_CONNECTION_FAILURE , this . Hostname ) ;
}
else if ( error ! = null & & ! string . IsNullOrEmpty ( error . Message ) )
{
return error . Message ;
}
else
{
return null ;
}
}
2023-03-22 23:10:52 +01:00
private void HandleSuccessfulConnection ( ConnectTask task , Pool pool )
2013-06-24 13:41:48 +02:00
{
2023-03-22 23:10:52 +01:00
// Remove any other (disconnected) entries for this server from the tree
var existingConnections = ConnectionsManager . XenConnections . Where ( c = >
c . Hostname . Equals ( task . Hostname ) & & ! c . IsConnected ) . ToList ( ) ;
foreach ( var conn in existingConnections )
ConnectionsManager . ClearCacheAndRemoveConnection ( conn ) ;
2013-06-24 13:41:48 +02:00
// add server name to history (if it's not already there)
XenAdminConfigManager . Provider . UpdateServerHistory ( HostnameWithPort ) ;
if ( ! ConnectionsManager . XenConnectionsContains ( this ) )
{
lock ( ConnectionsManager . ConnectionsLock )
{
ConnectionsManager . XenConnections . Add ( this ) ;
}
InvokeHelper . Invoke ( XenAdminConfigManager . Provider . SaveSettingsIfRequired ) ;
}
2023-03-22 23:10:52 +01:00
string name = string . IsNullOrEmpty ( FriendlyName ) | | FriendlyName = = task . Hostname
? task . Hostname
: string . Format ( "{0} ({1})" , FriendlyName , task . Hostname ) ;
2013-06-24 13:41:48 +02:00
string title = string . Format ( Messages . CONNECTING_NOTICE_TITLE , name ) ;
string msg = string . Format ( Messages . CONNECTING_NOTICE_TEXT , name ) ;
2022-04-11 22:44:27 +02:00
ConnectAction = new DummyAction ( title , msg ) ;
2023-03-22 23:10:52 +01:00
SetPoolAndHostInAction ( ConnectAction , pool , PoolOpaqueRef ) ;
2013-06-24 13:41:48 +02:00
ExpectPasswordIsCorrect = true ;
OnConnectionResult ( true , null , null ) ;
2023-03-22 23:10:52 +01:00
log . InfoFormat ( "Completed connection phase for pool {0} ({1}:{2}, {3})." ,
FriendlyName , task . Hostname , task . Port , PoolOpaqueRef ) ;
2013-06-24 13:41:48 +02:00
}
/// <summary>
/// Check the password isn't null, which happens when the session is restored without remembering passwords.
/// </summary>
/// <returns>Whether to continue.</returns>
private bool HandlePromptForNewPassword ( )
{
if ( Password = = null & & ! PromptForNewPassword ( Password ) )
{
// if false the user has cancelled, set the password back to null and return
Password = null ;
return false ;
}
return true ;
}
private readonly object PromptLock = new object ( ) ;
/// <summary>
/// Prompts the user for the changed password for a server.
/// </summary>
/// <param name="old_password"></param>
/// <returns></returns>
private bool PromptForNewPassword ( string old_password )
{
// Serialise prompting for new passwords, so that we don't get multiple dialogs pop up.
lock ( PromptLock )
{
if ( Password ! = old_password )
{
// Some other thread has changed the password already. Retry using that one.
return true ;
}
bool result = ( _promptForNewPassword ! = null ) ? _promptForNewPassword ( this , old_password ) : false ;
return result ;
}
}
private void ClearEventQueue ( )
{
while ( eventQueue . NotEmpty )
{
eventQueue . Dequeue ( ) ;
}
// Suspend the cache update timer
cacheUpdateTimer . Change ( Timeout . Infinite , Timeout . Infinite ) ;
}
private void EventsPending ( )
{
lock ( cacheUpdateTimer )
{
if ( cacheUpdaterRunning )
updatesWaiting = true ;
else
cacheUpdateTimer . Change ( 50 , - 1 ) ;
}
}
private void cacheUpdater ( object state )
{
lock ( cacheUpdateTimer )
{
if ( cacheUpdaterRunning )
{
// there is a race-condition here which can be observed under high load: It is possible for the timer to fire even when
// cacheUpdaterRunning = true. The check ensures we don't get multiple threads calling cacheUpdater
// on the same connection.
updatesWaiting = true ;
return ;
}
cacheUpdaterRunning = true ;
updatesWaiting = false ;
}
try
{
cacheUpdater_ ( ) ;
}
finally
{
bool waiting ;
lock ( cacheUpdateTimer )
{
waiting = updatesWaiting ;
cacheUpdaterRunning = false ;
}
if ( waiting )
cacheUpdater ( null ) ;
}
}
private void cacheUpdater_ ( )
{
// Copy events off the event queue
// as we don't want events delivered while we're
// on the GUI thread to be included in this set of
// updates, otherwise we might hose the gui thread
// during an event storm (ie deleting 1000 vms)
2023-03-22 23:10:52 +01:00
2013-06-24 13:41:48 +02:00
List < ObjectChange > events = new List < ObjectChange > ( ) ;
while ( eventQueue . NotEmpty )
events . Add ( eventQueue . Dequeue ( ) ) ;
if ( events . Count > 0 )
{
2023-03-22 23:10:52 +01:00
InvokeHelper . Invoke ( ( ) = >
2013-06-24 13:41:48 +02:00
{
2023-03-22 23:10:52 +01:00
OnBeforeMajorChange ( false ) ;
bool fire = Cache . UpdateFrom ( this , events ) ;
OnAfterMajorChange ( false ) ;
2013-06-24 13:41:48 +02:00
2023-03-22 23:10:52 +01:00
if ( fire )
OnXenObjectsUpdated ( ) ;
2013-06-24 13:41:48 +02:00
} ) ;
2023-03-22 23:10:52 +01:00
if ( ! CacheIsPopulated )
2013-06-24 13:41:48 +02:00
{
2023-03-22 23:10:52 +01:00
lock ( connectTaskLock )
heartbeat ? . Start ( ) ;
CacheIsPopulated = true ;
MarkConnectActionComplete ( ) ;
log . Info ( $"Connection to {Hostname} successful." ) ;
2013-06-24 13:41:48 +02:00
}
}
}
/// <summary>
/// if not called the action will never finish and the gui will never close
/// </summary>
private void MarkConnectActionComplete ( )
{
if ( ConnectAction ! = null & & ! ConnectAction . IsCompleted )
{
string title = string . Format ( Messages . CONNECTION_OK_NOTICE_TITLE , Hostname ) ;
string msg = string . Format ( Messages . CONNECTION_OK_NOTICE_TEXT , Hostname ) ;
ConnectAction . Title = title ;
ConnectAction . Description = msg ;
2022-04-11 22:44:27 +02:00
Pool pool = Helpers . GetPoolOfOne ( this ) ;
if ( pool ! = null )
SetPoolAndHostInAction ( ConnectAction , pool , PoolOpaqueRef ) ;
2013-06-24 13:41:48 +02:00
// mark the connect action as completed
ConnectAction . Finished = DateTime . Now ;
ConnectAction . PercentComplete = 100 ;
ConnectAction . IsCompleted = true ;
}
}
private const int DEFAULT_MAX_SESSION_LOGIN_ATTEMPTS = 3 ;
2022-10-20 14:53:14 +02:00
private readonly string eventNextConnectionGroupName = Guid . NewGuid ( ) . ToString ( ) ;
2014-06-30 15:21:42 +02:00
2013-06-24 13:41:48 +02:00
/// <summary>
/// The main method for a connection to a XenServer. This method runs until the connection is lost, and
/// contains a loop that after doing an initial cache fill processes events off the wire when they arrive.
/// </summary>
/// <param name="o"></param>
private void ConnectWorkerThread ( object o )
{
ConnectTask task = ( ConnectTask ) o ;
Exception error = null ;
Pool pool = null ;
try
{
log . DebugFormat ( "IXenConnection: trying to connect to {0}" , HostnameWithPort ) ;
2018-01-19 14:14:13 +01:00
Session session = GetNewSession ( task . Hostname , task . Port , Username , Password , false ) ;
2013-06-24 13:41:48 +02:00
// Save the session so we can log it out later
task . Session = session ;
2023-05-10 16:22:46 +02:00
if ( session . APIVersion < API_Version . API_2_6 )
2013-06-24 13:41:48 +02:00
throw new ServerNotSupported ( ) ;
// Event.next uses a different session with a shorter timeout: see CA-33145.
2015-10-26 17:01:55 +01:00
Session eventNextSession = DuplicateSession ( EVENT_NEXT_TIMEOUT ) ;
eventNextSession . ConnectionGroupName = eventNextConnectionGroupName ; // this will force the eventNextSession onto its own set of TCP streams (see CA-108676)
2014-06-30 15:21:42 +02:00
2023-03-22 23:10:52 +01:00
CacheIsPopulated = false ;
2013-06-24 13:41:48 +02:00
session . CacheWarming = true ;
string token = "" ;
bool eventsExceptionLogged = false ;
while ( true )
{
if ( task . Cancelled )
break ;
EventNextBlocked = false ;
if ( session . CacheWarming )
{
if ( ! task . Connected )
{
// We've started cache sync: update the dialog text
OnConnectionMessageChanged ( string . Format ( Messages . LABEL_SYNC , this . Hostname ) ) ;
}
2023-03-22 23:10:52 +01:00
log . Debug ( "Cache is warming. Starting XenObjectDownloader.GetAllObjects" ) ;
2017-05-24 00:12:06 +02:00
XenObjectDownloader . GetAllObjects ( session , eventQueue , task . GetCancelled , ref token ) ;
2023-03-22 23:10:52 +01:00
log . Debug ( "Cache is warming. XenObjectDownloader.GetAllObjects finished successfully" ) ;
2013-06-24 13:41:48 +02:00
session . CacheWarming = false ;
}
else
{
try
{
2023-03-22 23:10:52 +01:00
log . Debug ( "Starting XenObjectDownloader.GetEvents" ) ;
2017-05-24 00:12:06 +02:00
XenObjectDownloader . GetEvents ( eventNextSession , eventQueue , task . GetCancelled , ref token ) ;
2023-03-22 23:10:52 +01:00
log . Debug ( "Starting XenObjectDownloader.GetEvents finished successfully" ) ;
2013-06-24 13:41:48 +02:00
eventsExceptionLogged = false ;
}
2015-10-20 11:28:51 +02:00
catch ( Exception exn )
2013-06-24 13:41:48 +02:00
{
2015-10-20 11:28:51 +02:00
if ( ! ExpectDisruption )
throw ;
log . DebugFormat ( "Exception (disruption is expected) in XenObjectDownloader.GetEvents: {0}" , exn . GetType ( ) . Name ) ;
// ignoring some exceptions when disruption is expected
2023-03-22 23:10:52 +01:00
if ( exn is IOException | |
( exn is WebException webEx & & ( webEx . Status = = WebExceptionStatus . KeepAliveFailure | | webEx . Status = = WebExceptionStatus . ConnectFailure ) ) )
2013-06-24 13:41:48 +02:00
{
if ( ! eventsExceptionLogged )
{
log . Debug ( "Ignoring keepalive/connect failure, because disruption is expected" ) ;
eventsExceptionLogged = true ;
}
}
else
{
throw ;
}
}
}
// check if requested to cancel
if ( task . Cancelled )
break ;
if ( ! task . Connected )
{
lock ( ConnectionsManager . ConnectionsLock )
{
pool = ObjectChange . GetPool ( eventQueue , out PoolOpaqueRef ) ;
2021-08-31 12:31:16 +02:00
Host coordinator = ObjectChange . GetCoordinator ( eventQueue ) ;
2013-06-24 13:41:48 +02:00
2021-08-31 12:31:16 +02:00
CoordinatorIPAddress = coordinator . address ;
2013-06-24 13:41:48 +02:00
foreach ( IXenConnection iconn in ConnectionsManager . XenConnections )
{
XenConnection connection = iconn as XenConnection ;
Trace . Assert ( connection ! = null ) ;
if ( ! connection . IsConnected )
continue ;
bool sameRef = PoolOpaqueRef = = connection . PoolOpaqueRef ;
if ( ! sameRef )
continue ;
2021-08-31 12:31:16 +02:00
bool sameCoordinator = CoordinatorIPAddress = = connection . CoordinatorIPAddress ;
2013-06-24 13:41:48 +02:00
2021-08-31 12:31:16 +02:00
if ( sameRef & & sameCoordinator )
2013-06-24 13:41:48 +02:00
throw new ConnectionExists ( connection ) ;
2023-03-22 23:10:52 +01:00
// CA-15633: XenCenter does not allow connection to host on which backup is restored.
throw new BadRestoreDetected ( connection ) ;
2013-06-24 13:41:48 +02:00
}
task . Connected = true ;
2017-09-03 04:33:29 +02:00
string poolName = pool . Name ( ) ;
2022-10-20 14:53:14 +02:00
2017-09-03 04:33:29 +02:00
FriendlyName = ! string . IsNullOrEmpty ( poolName )
? poolName
2021-08-31 12:31:16 +02:00
: ! string . IsNullOrEmpty ( coordinator . Name ( ) )
? coordinator . Name ( )
2017-09-03 04:33:29 +02:00
: task . Hostname ;
2013-06-24 13:41:48 +02:00
} // ConnectionsLock
log . DebugFormat ( "Getting server time for pool {0} ({1})..." , FriendlyName , PoolOpaqueRef ) ;
SetServerTimeOffset ( session , pool . master . opaque_ref ) ;
2023-03-22 23:10:52 +01:00
HandleSuccessfulConnection ( task , pool ) ;
2013-06-24 13:41:48 +02:00
}
EventsPending ( ) ;
}
}
catch ( Failure e )
{
if ( task . Cancelled & & e . ErrorDescription . Count > 0 & & e . ErrorDescription [ 0 ] = = Failure . SESSION_INVALID )
{
// Do nothing: this is probably a result of the user disconnecting, and us calling session.logout()
}
else
{
error = e ;
log . Warn ( e ) ;
}
}
catch ( WebException e )
{
error = e ;
log . Debug ( e . Message ) ;
}
catch ( TargetInvocationException e )
{
error = e . InnerException ;
log . Error ( "TargetInvocationException" , e ) ;
}
catch ( UriFormatException e )
{
// This can happen when the user types gobbledy-gook into the host-name field
// of the add server dialog...
error = e ;
log . Debug ( e . Message ) ;
}
catch ( CancelledException )
{
task . Cancelled = true ;
}
catch ( DisconnectionException e )
{
error = e ;
log . Debug ( e . Message ) ;
}
2017-05-24 10:23:47 +02:00
catch ( EventFromBlockedException e )
2013-06-24 13:41:48 +02:00
{
EventNextBlocked = true ;
error = e ;
log . Error ( e , e ) ;
}
catch ( Exception e )
{
error = e ;
log . Error ( e , e ) ;
}
finally
{
HandleConnectionResult ( task , error , pool ) ;
}
}
private void HandleConnectionResult ( ConnectTask task , Exception error , Pool pool )
{
if ( task ! = connectTask )
{
2020-08-25 13:49:36 +02:00
// We've been superseded by a newer ConnectTask. Exit silently without firing events.
2013-06-24 13:41:48 +02:00
// Can happen when user disconnects while sync is taking place, then reconnects
// (creating a new _connectTask) before the sync is complete.
}
else
{
ClearEventQueue ( ) ;
connectTask = null ;
2023-03-22 23:10:52 +01:00
// clean up action so we don't stay open forever
if ( ConnectAction ! = null )
ConnectAction . IsCompleted = true ;
2013-06-24 13:41:48 +02:00
2020-08-25 13:49:36 +02:00
if ( error is ServerNotSupported )
2013-06-24 13:41:48 +02:00
{
2017-11-27 07:38:21 +01:00
EndConnect ( true , task , false ) ;
2013-06-24 13:41:48 +02:00
log . Info ( error . Message ) ;
2015-10-28 19:12:31 +01:00
OnConnectionResult ( false , error . Message , error ) ;
2013-06-24 13:41:48 +02:00
}
else if ( task . Cancelled )
{
task . Connected = false ;
log . InfoFormat ( "IXenConnection: closing connection to {0}" , this . HostnameWithPort ) ;
2017-11-27 07:38:21 +01:00
EndConnect ( true , task , false ) ;
2013-06-24 13:41:48 +02:00
OnConnectionClosed ( ) ;
}
else if ( task . Connected )
{
HandleConnectionLost ( task , pool , PoolOpaqueRef ) ;
}
else
{
// We never connected
string reason = GetReason ( error ) ;
2014-04-28 16:01:11 +02:00
log . WarnFormat ( "IXenConnection: failed to connect to {0}: {1}" , HostnameWithPort , reason ) ;
2013-06-24 13:41:48 +02:00
Failure f = error as Failure ;
2014-04-28 16:01:11 +02:00
if ( f ! = null & & f . ErrorDescription [ 0 ] = = Failure . HOST_IS_SLAVE )
{
//do not log an event in this case
}
else if ( error is ConnectionExists )
{
//do not log an event in this case
}
else
{
// Create a new log message to say the connection attempt failed
string title = string . Format ( Messages . CONNECTION_FAILED_TITLE , HostnameWithPort ) ;
2022-10-20 14:53:14 +02:00
var action = new DummyAction ( title , reason , reason ) ;
2022-04-11 22:44:27 +02:00
SetPoolAndHostInAction ( action , pool , PoolOpaqueRef ) ;
action . Run ( ) ;
2014-04-28 16:01:11 +02:00
}
2021-08-31 12:31:16 +02:00
// We only want to continue the coordinator search in certain circumstances
if ( FindingNewCoordinator & & ( error is WebException | | ( f ! = null & & f . ErrorDescription [ 0 ] ! = Failure . RBAC_PERMISSION_DENIED ) ) )
2013-06-24 13:41:48 +02:00
{
if ( f ! = null )
{
if ( f . ErrorDescription [ 0 ] = = XenAPI . Failure . HOST_IS_SLAVE )
{
2021-08-31 12:31:16 +02:00
log . DebugFormat ( "Found a member of {0} at {1}; redirecting to the coordinator at {2}" ,
LastCoordinatorHostname , Hostname , f . ErrorDescription [ 1 ] ) ;
2013-06-24 13:41:48 +02:00
Hostname = f . ErrorDescription [ 1 ] ;
2021-08-31 12:31:16 +02:00
OnConnectionMessageChanged ( string . Format ( Messages . CONNECTION_REDIRECTING , LastCoordinatorHostname , Hostname ) ) ;
ReconnectCoordinator ( ) ;
2013-06-24 13:41:48 +02:00
}
else if ( f . ErrorDescription [ 0 ] = = XenAPI . Failure . HOST_STILL_BOOTING )
{
2021-08-31 12:31:16 +02:00
log . DebugFormat ( "Found a member of {0} at {1}, but it's still booting; trying the next pool member" ,
LastCoordinatorHostname , Hostname ) ;
MaybeStartNextPoolMemberTimer ( reason , error ) ;
2013-06-24 13:41:48 +02:00
}
else
{
2021-08-31 12:31:16 +02:00
log . DebugFormat ( "Found a member of {0} at {1}, but got a failure; trying the next pool member" ,
LastCoordinatorHostname , Hostname ) ;
MaybeStartNextPoolMemberTimer ( reason , error ) ;
2013-06-24 13:41:48 +02:00
}
}
else if ( PoolMemberRemaining ( ) )
{
log . DebugFormat ( "Connection to {0} failed; trying the next pool member" , Hostname ) ;
2021-08-31 12:31:16 +02:00
MaybeStartNextPoolMemberTimer ( reason , error ) ;
2013-06-24 13:41:48 +02:00
}
else
{
2021-08-31 12:31:16 +02:00
if ( ExpectDisruption | | DateTime . Now - FindingNewCoordinatorStartedAt < SEARCH_NEW_COORDINATOR_STOP_AFTER )
2013-06-24 13:41:48 +02:00
{
log . DebugFormat ( "While trying to find a connection for {0}, tried to connect to every remembered host. Will now loop back through pool members again." ,
this . HostnameWithPort ) ;
lock ( PoolMembersLock )
{
PoolMemberIndex = 0 ;
}
2021-08-31 12:31:16 +02:00
MaybeStartNextPoolMemberTimer ( reason , error ) ;
2013-06-24 13:41:48 +02:00
}
2021-08-31 12:31:16 +02:00
else if ( LastCoordinatorHostname ! = "" )
2013-06-24 13:41:48 +02:00
{
2021-08-31 12:31:16 +02:00
log . DebugFormat ( "Stopping search for new coordinator for {0}: timeout reached without success. Trying the old coordinator one last time" ,
2013-06-24 13:41:48 +02:00
LastConnectionFullName ) ;
2021-08-31 12:31:16 +02:00
FindingNewCoordinator = false ;
Hostname = LastCoordinatorHostname ;
ReconnectCoordinator ( ) ;
2013-06-24 13:41:48 +02:00
}
2016-03-07 17:55:18 +01:00
else
{
OnConnectionResult ( false , reason , error ) ;
}
2013-06-24 13:41:48 +02:00
}
}
else
{
OnConnectionResult ( false , reason , error ) ;
}
}
}
}
2021-08-31 12:31:16 +02:00
private void SetServerTimeOffset ( Session session , string coordinator_opaqueref )
2013-06-24 13:41:48 +02:00
{
2021-08-31 12:31:16 +02:00
DateTime t = Host . get_servertime ( session , coordinator_opaqueref ) ;
2013-06-24 13:41:48 +02:00
ServerTimeOffset = DateTime . UtcNow - t ;
}
/// <summary>
/// Called when a connection that had been made successfully is then lost.
/// </summary>
/// <param name="task"></param>
/// <param name="pool"></param>
/// <param name="poolopaqueref"></param>
private void HandleConnectionLost ( ConnectTask task , Pool pool , string poolopaqueref )
{
task . Connected = false ;
log . WarnFormat ( "Lost connection to {0}" , this . HostnameWithPort ) ;
// Cancel all current actions to do with this connection
if ( ! ExpectDisruption )
{
InvokeHelper . Invoke ( ( ) = > ConnectionsManager . CancelAllActions ( this ) ) ;
}
// Save list of addresses of current hosts in pool
List < string > members = new List < string > ( ) ;
foreach ( Host host in Cache . Hosts )
{
if ( ! string . IsNullOrEmpty ( host . address ) )
members . Add ( host . address ) ;
}
2021-08-31 12:31:16 +02:00
// Save coordinator's address so we don't try to reconnect to it first
Host coordinator = Helpers . GetCoordinator ( this ) ;
2013-06-24 13:41:48 +02:00
// Save ha_enabled status before we clear the cache
bool ha_enabled = IsHAEnabled ( ) ;
// NB line below clears the cache
2017-11-27 07:38:21 +01:00
EndConnect ( true , task , false ) ;
2013-06-24 13:41:48 +02:00
string description ;
2021-08-31 12:31:16 +02:00
LastCoordinatorHostname = Hostname ;
2017-09-03 04:33:29 +02:00
string poolName = pool . Name ( ) ;
2013-06-24 13:41:48 +02:00
if ( string . IsNullOrEmpty ( poolName ) )
{
LastConnectionFullName = HostnameWithPort ;
}
else
{
LastConnectionFullName = string . Format ( "'{0}' ({1})" , poolName , HostnameWithPort ) ;
}
2021-08-31 12:31:16 +02:00
if ( ! EventNextBlocked & & ( CoordinatorMayChange | | ha_enabled ) & & members . Count > 1 )
2013-06-24 13:41:48 +02:00
{
log . DebugFormat ( "Will now try to connect to another pool member" ) ;
lock ( PoolMembersLock )
{
PoolMembers . Clear ( ) ;
PoolMembers . AddRange ( members ) ;
PoolMemberIndex = 0 ;
2021-08-31 12:31:16 +02:00
// Don't reconnect to the coordinator straight away, try a supporter first
if ( coordinator ! = null & & PoolMembers [ 0 ] = = coordinator . address & & PoolMembers . Count > 1 )
2013-06-24 13:41:48 +02:00
{
PoolMemberIndex = 1 ;
}
}
2021-08-31 12:31:16 +02:00
FindingNewCoordinator = true ;
// Record the time at which we started the new coordinator search.
FindingNewCoordinatorStartedAt = DateTime . Now ;
StartReconnectCoordinatorTimer ( ) ;
description = string . Format ( Messages . CONNECTION_LOST_NOTICE_COORDINATOR_IN_X_SECONDS , LastConnectionFullName , XenConnection . SEARCH_NEW_COORDINATOR_TIMEOUT_MS / 1000 ) ;
log . DebugFormat ( "Beginning search for new coordinator; will give up after {0} seconds" , SEARCH_NEW_COORDINATOR_STOP_AFTER . TotalSeconds ) ;
2013-06-24 13:41:48 +02:00
}
else
{
log . DebugFormat ( "Will retry connection to {0} in {1} ms." , LastConnectionFullName , ReconnectHostTimeoutMs ) ;
StartReconnectSingleHostTimer ( ) ;
description = string . Format ( Messages . CONNECTION_LOST_RECONNECT_IN_X_SECONDS , LastConnectionFullName , ReconnectHostTimeoutMs / 1000 ) ;
}
2013-08-07 12:09:35 +02:00
string title = string . Format ( Messages . CONNECTION_LOST_NOTICE_TITLE ,
LastConnectionFullName ) ;
2022-10-20 14:53:14 +02:00
var action = new DummyAction ( title , description , description ) ;
2022-04-11 22:44:27 +02:00
SetPoolAndHostInAction ( action , pool , poolopaqueref ) ;
action . Run ( ) ;
2013-06-24 13:41:48 +02:00
OnConnectionLost ( ) ;
}
private bool PoolMemberRemaining ( )
{
lock ( PoolMembersLock )
{
return PoolMemberIndex < PoolMembers . Count ;
}
}
private bool IsHAEnabled ( )
{
Pool pool = Helpers . GetPoolOfOne ( this ) ;
return pool ! = null & & pool . ha_enabled ;
}
/// <summary>
/// When we lose connection to a non-HA host, the timeout before we try reconnecting.
/// </summary>
private const int RECONNECT_HOST_TIMEOUT_MS = 120 * 1000 ;
private const int RECONNECT_SHORT_TIMEOUT_MS = 5 * 1000 ;
private int ReconnectHostTimeoutMs
{
get
{
2023-03-22 23:10:52 +01:00
if ( EventNextBlocked | | IsSimulatedConnection )
2013-06-24 13:41:48 +02:00
return RECONNECT_SHORT_TIMEOUT_MS ;
2023-03-10 14:28:23 +01:00
return RECONNECT_HOST_TIMEOUT_MS ;
2013-06-24 13:41:48 +02:00
}
}
/// <summary>
2021-08-31 12:31:16 +02:00
/// When HA is enabled, the timeout after losing connection to the coordinator before we start searching for a new coordinator.
/// i.e. This should be the time it takes coordinator failover to be sorted out on the server, plus a margin.
2013-06-24 13:41:48 +02:00
/// NB we already have an additional built-in delay - it takes time for us to decide that the host is not responding,
2021-08-31 12:31:16 +02:00
/// and stop the connection to the dead host, before starting the search.
2013-06-24 13:41:48 +02:00
/// </summary>
2021-08-31 12:31:16 +02:00
private const int SEARCH_NEW_COORDINATOR_TIMEOUT_MS = 60 * 1000 ;
2013-06-24 13:41:48 +02:00
/// <summary>
2021-08-31 12:31:16 +02:00
/// When HA is enabled, and going through each of the supporters to try and find the new coordinator, the time between failing
/// to connect to one supporter and trying to connect to the next in the list.
2013-06-24 13:41:48 +02:00
/// </summary>
2021-08-31 12:31:16 +02:00
private const int SEARCH_NEXT_SUPPORTER_TIMEOUT_MS = 15 * 1000 ;
2013-06-24 13:41:48 +02:00
/// <summary>
2021-08-31 12:31:16 +02:00
/// When going through each of the remembered members of the pool looking for the new coordinator, don't start another pass
2013-06-24 13:41:48 +02:00
/// through connecting to each of the hosts if we've already been looking for this long.
/// </summary>
2021-08-31 12:31:16 +02:00
private static readonly TimeSpan SEARCH_NEW_COORDINATOR_STOP_AFTER = TimeSpan . FromMinutes ( 6 ) ;
2013-06-24 13:41:48 +02:00
private void StartReconnectSingleHostTimer ( )
{
ReconnectionTimer =
new System . Threading . Timer ( ( TimerCallback ) ReconnectSingleHostTimer , null ,
ReconnectHostTimeoutMs , ReconnectHostTimeoutMs ) ;
}
2021-08-31 12:31:16 +02:00
private void StartReconnectCoordinatorTimer ( )
2013-06-24 13:41:48 +02:00
{
2021-08-31 12:31:16 +02:00
StartReconnectCoordinatorTimer ( SEARCH_NEW_COORDINATOR_TIMEOUT_MS ) ;
2013-06-24 13:41:48 +02:00
}
2021-08-31 12:31:16 +02:00
private void MaybeStartNextPoolMemberTimer ( string reason , Exception error )
2013-06-24 13:41:48 +02:00
{
if ( PoolMemberRemaining ( ) )
2021-08-31 12:31:16 +02:00
StartReconnectCoordinatorTimer ( SEARCH_NEXT_SUPPORTER_TIMEOUT_MS ) ;
2022-10-20 14:53:14 +02:00
else
OnConnectionResult ( false , reason , error ) ;
2013-06-24 13:41:48 +02:00
}
2021-08-31 12:31:16 +02:00
private void StartReconnectCoordinatorTimer ( int timeout )
2013-06-24 13:41:48 +02:00
{
2022-10-20 14:53:14 +02:00
OnConnectionMessageChanged ( string . Format ( Messages . CONNECTION_WILL_RETRY_SUPPORTER , LastConnectionFullName . Ellipsise ( 25 ) , timeout / 1000 ) ) ;
2013-06-24 13:41:48 +02:00
ReconnectionTimer =
2021-08-31 12:31:16 +02:00
new System . Threading . Timer ( ( TimerCallback ) ReconnectCoordinatorTimer , null ,
2013-06-24 13:41:48 +02:00
timeout , Timeout . Infinite ) ;
}
private void ReconnectSingleHostTimer ( object state )
{
if ( IsConnected | | ! ConnectionsManager . XenConnectionsContains ( this ) )
{
log . DebugFormat ( "Host {0} already reconnected" , Hostname ) ;
if ( ReconnectionTimer ! = null )
{
ReconnectionTimer . Dispose ( ) ;
ReconnectionTimer = null ;
}
return ;
}
if ( ! ExpectDisruption ) // only try once unless expect disruption
{
if ( ReconnectionTimer ! = null )
{
ReconnectionTimer . Dispose ( ) ;
ReconnectionTimer = null ;
}
}
log . DebugFormat ( "Reconnecting to server {0}..." , Hostname ) ;
if ( ! XenAdminConfigManager . Provider . Exiting )
{
2022-10-20 14:53:14 +02:00
InvokeHelper . Invoke ( delegate ( )
2013-06-24 13:41:48 +02:00
{
/ * ConnectionResult + = new EventHandler < ConnectionResultEventArgs > ( XenConnection_ConnectionResult ) ;
CachePopulated + = new EventHandler < EventArgs > ( XenConnection_CachePopulated ) ; * /
BeginConnect ( false , _promptForNewPassword ) ;
} ) ;
OnConnectionReconnecting ( ) ;
}
}
2021-08-31 12:31:16 +02:00
private void ReconnectCoordinatorTimer ( object state )
2013-06-24 13:41:48 +02:00
{
if ( IsConnected | | ! ConnectionsManager . XenConnectionsContains ( this ) )
{
2021-08-31 12:31:16 +02:00
log . DebugFormat ( "Coordinator has been found for {0} at {1}" , LastCoordinatorHostname , Hostname ) ;
2013-06-24 13:41:48 +02:00
return ;
}
lock ( PoolMembersLock )
{
if ( PoolMemberIndex < PoolMembers . Count )
{
Hostname = PoolMembers [ PoolMemberIndex ] ;
PoolMemberIndex + + ;
}
}
2021-08-31 12:31:16 +02:00
OnConnectionMessageChanged ( string . Format ( Messages . CONNECTION_RETRYING_SUPPORTER , LastConnectionFullName . Ellipsise ( 25 ) , Hostname ) ) ;
ReconnectCoordinator ( ) ;
2013-06-24 13:41:48 +02:00
}
2021-08-31 12:31:16 +02:00
private void ReconnectCoordinator ( )
2013-06-24 13:41:48 +02:00
{
// Add an informational entry to the log
2021-08-31 12:31:16 +02:00
string title = string . Format ( Messages . CONNECTION_FINDING_COORDINATOR_TITLE , LastConnectionFullName ) ;
string descr = string . Format ( Messages . CONNECTION_FINDING_COORDINATOR_DESCRIPTION , LastConnectionFullName , Hostname ) ;
2022-10-20 14:53:14 +02:00
var action = new DummyAction ( title , descr ) ;
2013-06-24 13:41:48 +02:00
SetPoolAndHostInAction ( action , null , PoolOpaqueRef ) ;
2022-04-11 22:44:27 +02:00
action . Run ( ) ;
2021-08-31 12:31:16 +02:00
log . DebugFormat ( "Looking for coordinator for {0} on {1}..." , LastConnectionFullName , Hostname ) ;
2013-06-24 13:41:48 +02:00
if ( ! XenAdminConfigManager . Provider . Exiting )
{
2022-04-11 22:44:27 +02:00
InvokeHelper . Invoke ( ( ) = > BeginConnect ( false , _promptForNewPassword ) ) ;
2013-06-24 13:41:48 +02:00
OnConnectionReconnecting ( ) ;
}
}
private Pool getAPool ( ICache objects , out string opaqueref )
{
2021-10-04 16:09:42 +02:00
var pools = objects . Pools ;
if ( pools . Length > 0 )
2013-06-24 13:41:48 +02:00
{
2021-10-04 16:09:42 +02:00
var pool = pools . First ( ) ;
2013-06-24 13:41:48 +02:00
opaqueref = pool . opaque_ref ;
return pool ;
}
2021-10-04 16:09:42 +02:00
Trace . Assert ( false ) ;
2013-06-24 13:41:48 +02:00
opaqueref = null ;
return null ;
}
private void OnClearingCache ( )
{
2019-10-27 16:52:15 +01:00
ClearingCache ? . Invoke ( this ) ;
2013-06-24 13:41:48 +02:00
}
private void OnConnectionResult ( bool connected , string reason , Exception error )
{
if ( ConnectionResult ! = null )
ConnectionResult ( this , new ConnectionResultEventArgs ( connected , reason , error ) ) ;
OnConnectionStateChanged ( ) ;
}
private void OnConnectionClosed ( )
{
2019-10-27 16:52:15 +01:00
ConnectionClosed ? . Invoke ( this ) ;
2013-06-24 13:41:48 +02:00
OnConnectionStateChanged ( ) ;
}
private void OnConnectionLost ( )
{
2019-10-27 16:52:15 +01:00
ConnectionLost ? . Invoke ( this ) ;
2013-06-24 13:41:48 +02:00
OnConnectionStateChanged ( ) ;
}
private void OnConnectionReconnecting ( )
{
2019-10-27 16:52:15 +01:00
ConnectionReconnecting ? . Invoke ( this ) ;
2013-06-24 13:41:48 +02:00
OnConnectionStateChanged ( ) ;
}
private void OnBeforeConnectionEnd ( )
{
2019-10-27 16:52:15 +01:00
BeforeConnectionEnd ? . Invoke ( this ) ;
2013-06-24 13:41:48 +02:00
}
private void OnConnectionStateChanged ( )
{
2019-10-27 16:52:15 +01:00
ConnectionStateChanged ? . Invoke ( this ) ;
2013-06-24 13:41:48 +02:00
}
private void OnConnectionMessageChanged ( string message )
{
2019-10-27 16:52:15 +01:00
ConnectionMessageChanged ? . Invoke ( this , message ) ;
2013-06-24 13:41:48 +02:00
}
public void OnBeforeMajorChange ( bool background )
{
2019-10-27 16:52:15 +01:00
BeforeMajorChange ? . Invoke ( this , background ) ;
2013-06-24 13:41:48 +02:00
}
public void OnAfterMajorChange ( bool background )
{
2019-10-27 16:52:15 +01:00
AfterMajorChange ? . Invoke ( this , background ) ;
2013-06-24 13:41:48 +02:00
}
private void OnXenObjectsUpdated ( )
{
// Using BeginInvoke here means that the XenObjectsUpdated event gets fired after any
// CollectionChanged events fired by ChangeableDictionary during Cache.UpdateFrom.
2022-10-20 14:53:14 +02:00
InvokeHelper . BeginInvoke ( delegate ( )
2013-06-24 13:41:48 +02:00
{
if ( XenObjectsUpdated ! = null )
XenObjectsUpdated ( this , null ) ;
} ) ;
}
2018-03-03 17:14:21 +01:00
public T TryResolveWithTimeout < T > ( XenRef < T > t ) where T : XenObject < T >
{
log . DebugFormat ( "Resolving {0} {1}" , t , t . opaque_ref ) ;
int timeout = 120 ; // two minutes;
while ( timeout > 0 )
{
T obj = Resolve ( t ) ;
if ( obj ! = null )
return obj ;
Thread . Sleep ( 1000 ) ;
2018-06-27 02:24:56 +02:00
timeout - - ;
2018-03-03 17:14:21 +01:00
}
if ( typeof ( T ) = = typeof ( Host ) )
throw new Failure ( Failure . HOST_OFFLINE ) ;
throw new Failure ( Failure . HANDLE_INVALID , typeof ( T ) . Name , t . opaque_ref ) ;
}
2013-06-24 13:41:48 +02:00
/// <summary>
/// Stub to Cache.Resolve
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="xenRef">May be null, in which case null is returned.</param>
/// <returns></returns>
public virtual T Resolve < T > ( XenRef < T > xenRef ) where T : XenObject < T >
{
return Cache . Resolve ( xenRef ) ;
}
/// <summary>
/// Resolve every object in the given list. Skip any references that don't resolve.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="xenRefs">May be null, in which case the empty list is returned.</param>
/// <returns></returns>
public List < T > ResolveAll < T > ( IEnumerable < XenRef < T > > xenRefs ) where T : XenObject < T >
{
List < T > result = new List < T > ( ) ;
if ( xenRefs ! = null )
{
foreach ( XenRef < T > xenRef in xenRefs )
{
T o = Resolve ( xenRef ) ;
if ( o ! = null )
result . Add ( o ) ;
}
}
return result ;
}
public List < VDI > ResolveAllShownXenModelObjects ( List < XenRef < VDI > > xenRefs , bool showHiddenObjects )
{
List < VDI > result = ResolveAll ( xenRefs ) ;
2016-08-17 23:35:01 +02:00
result . RemoveAll ( vdi = > ! vdi . Show ( showHiddenObjects ) ) ;
2013-06-24 13:41:48 +02:00
return result ;
}
public static T FindByUUIDXenObject < T > ( string uuid ) where T : XenObject < T >
{
foreach ( IXenConnection c in ConnectionsManager . XenConnectionsCopy )
{
T o = c . Cache . Find_By_Uuid < T > ( uuid ) ;
if ( o ! = null )
return o ;
}
return null ;
}
/// <summary>
/// Find a XenObject corresponding to the given XenRef, or null if no such object is found.
/// </summary>
public static T FindByRef < T > ( XenRef < T > needle ) where T : XenObject < T >
{
foreach ( IXenConnection c in ConnectionsManager . XenConnectionsCopy )
{
T o = c . Resolve < T > ( needle ) ;
if ( o ! = null )
return o ;
}
return null ;
}
public static string ConnectedElsewhere ( string hostname )
{
lock ( ConnectionsManager . ConnectionsLock )
{
foreach ( IXenConnection connection in ConnectionsManager . XenConnections )
{
Pool pool = Helpers . GetPoolOfOne ( connection ) ;
if ( pool = = null )
continue ;
2021-08-31 12:31:16 +02:00
Host coordinator = connection . Resolve ( pool . master ) ;
if ( coordinator = = null )
2013-06-24 13:41:48 +02:00
continue ;
2021-08-31 12:31:16 +02:00
if ( coordinator . address = = hostname )
2013-06-24 13:41:48 +02:00
{
2021-08-31 12:31:16 +02:00
// we have tried to connect to a supporter that is a member of a pool we are already connected to.
2017-09-03 04:33:29 +02:00
return pool . Name ( ) ;
2013-06-24 13:41:48 +02:00
}
}
}
return null ;
}
#region IXmlSerializable Members
public System . Xml . Schema . XmlSchema GetSchema ( )
{
return null ;
}
public void ReadXml ( System . Xml . XmlReader reader )
{
Hostname = reader [ "Hostname" ] ;
}
public void WriteXml ( System . Xml . XmlWriter writer )
{
2022-10-20 14:53:14 +02:00
writer . WriteElementString ( "Hostname" , Hostname ) ;
2013-06-24 13:41:48 +02:00
}
#endregion
#region IDisposable Members
/// <summary>
/// Disposing this class will make it unusable - make sure you want to do this
/// </summary>
public void Dispose ( )
{
Dispose ( true ) ;
GC . SuppressFinalize ( this ) ;
}
private bool disposed ;
2023-03-22 23:10:52 +01:00
2013-06-24 13:41:48 +02:00
protected virtual void Dispose ( bool disposing )
{
2022-10-20 14:53:14 +02:00
if ( ! disposed )
2013-06-24 13:41:48 +02:00
{
if ( disposing )
{
ClearingCache = null ;
CachePopulated = null ;
ConnectionResult = null ;
ConnectionStateChanged = null ;
ConnectionLost = null ;
ConnectionClosed = null ;
ConnectionReconnecting = null ;
BeforeConnectionEnd = null ;
ConnectionMessageChanged = null ;
BeforeMajorChange = null ;
AfterMajorChange = null ;
XenObjectsUpdated = null ;
2023-03-22 23:10:52 +01:00
2013-06-24 13:41:48 +02:00
if ( ReconnectionTimer ! = null )
ReconnectionTimer . Dispose ( ) ;
if ( cacheUpdateTimer ! = null )
cacheUpdateTimer . Dispose ( ) ;
}
disposed = true ;
}
}
#endregion
}
public class ServerNotSupported : DisconnectionException
{
2023-05-10 16:22:46 +02:00
public override string Message = > string . Format ( Messages . SERVER_TOO_OLD , BrandManager . BrandConsole ,
BrandManager . ProductVersion712 , BrandManager . ProductVersion80 ) ;
2013-06-24 13:41:48 +02:00
}
public class ConnectionExists : DisconnectionException
{
2023-03-22 23:10:52 +01:00
protected readonly IXenConnection Connection ;
2013-06-24 13:41:48 +02:00
public ConnectionExists ( IXenConnection connection )
{
2023-03-22 23:10:52 +01:00
Connection = connection ;
2013-06-24 13:41:48 +02:00
}
public override string Message
{
get
{
2023-03-22 23:10:52 +01:00
if ( Connection = = null )
2013-06-24 13:41:48 +02:00
return Messages . CONNECTION_EXISTS_NULL ;
2023-03-22 23:10:52 +01:00
return string . Format ( Messages . CONNECTION_EXISTS , Connection . Hostname ) ;
2013-06-24 13:41:48 +02:00
}
}
2023-03-22 23:10:52 +01:00
public virtual string GetDialogMessage ( )
2013-06-24 13:41:48 +02:00
{
2023-03-22 23:10:52 +01:00
if ( Connection = = null )
return Messages . CONNECTION_EXISTS_NULL ;
Pool p = Helpers . GetPool ( Connection ) ;
2013-06-24 13:41:48 +02:00
if ( p = = null )
2023-03-22 23:10:52 +01:00
return string . Format ( Messages . ALREADY_CONNECTED , Connection . Hostname ) ;
2013-06-24 13:41:48 +02:00
2023-03-22 23:10:52 +01:00
return string . Format ( Messages . SUPPORTER_ALREADY_CONNECTED , Connection . Hostname , p . Name ( ) ) ;
2013-06-24 13:41:48 +02:00
}
}
2023-03-22 23:10:52 +01:00
internal class BadRestoreDetected : ConnectionExists
2013-06-24 13:41:48 +02:00
{
public BadRestoreDetected ( IXenConnection xc )
: base ( xc )
{
}
public override string Message
{
get
{
2023-03-22 23:10:52 +01:00
if ( Connection = = null )
return Messages . CONNECTION_EXISTS_NULL ;
return string . Format ( Messages . BAD_RESTORE_DETECTED , Connection . Name ) ;
2013-06-24 13:41:48 +02:00
}
}
2023-03-22 23:10:52 +01:00
public override string GetDialogMessage ( )
2013-06-24 13:41:48 +02:00
{
return Message ;
}
}
}