New logic for connection to a xapi database and some connection tidy up:

- Removed event that had no subscribers.
- The xml docs are more useful on the properties and not their supporting private fields.
- Stop silencing cache errors as they may reveal other issues.
- Adding server to history was called twice.
- Connection null checks and refactoring of ConnectionExists exceptions.
- Added some logging.

Signed-off-by: Konstantina Chremmou <Konstantina.Chremmou@cloud.com>
This commit is contained in:
Konstantina Chremmou 2023-03-22 22:10:52 +00:00
parent 8375754a5b
commit a4df1eed61
10 changed files with 463 additions and 252 deletions

View File

@ -39,6 +39,7 @@ using XenAPI;
using XenAdmin.Actions; using XenAdmin.Actions;
using XenAdmin.Actions.Wlb; using XenAdmin.Actions.Wlb;
using XenAdmin.Commands; using XenAdmin.Commands;
using XenAdmin.Network;
namespace XenAdmin.Controls.Wlb namespace XenAdmin.Controls.Wlb
@ -526,7 +527,7 @@ namespace XenAdmin.Controls.Wlb
{ {
Program.AssertOnEventThread(); Program.AssertOnEventThread();
if(_xenObject == null) if (_xenObject == null || _xenObject.Connection is XenConnection conn && conn.IsSimulatedConnection)
return; return;
if (Helpers.WlbEnabled(_xenObject.Connection)) if (Helpers.WlbEnabled(_xenObject.Connection))

View File

@ -29,6 +29,7 @@
*/ */
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using XenAdmin.Core; using XenAdmin.Core;
using XenAdmin.Network; using XenAdmin.Network;
@ -146,19 +147,12 @@ namespace XenAdmin.Dialogs
conn = connection; conn = connection;
foreach (var server in servers) foreach (var server in servers)
{ ConnectToServer(conn, server, username, password);
StringUtility.ParseHostnamePort(server, out var hostname, out var port);
if (port == 0)
port = ConnectionsManager.DEFAULT_XEN_PORT;
ConnectToServer(conn, hostname, port, username, password);
}
Close(); Close();
} }
private void ConnectToServer(IXenConnection conn, string hostname, int port, string username, string password) private void ConnectToServer(IXenConnection conn, string server, string username, string password)
{ {
if (conn == null) if (conn == null)
{ {
@ -170,14 +164,27 @@ namespace XenAdmin.Dialogs
conn.EndConnect(); // in case we're already connected conn.EndConnect(); // in case we're already connected
} }
conn.Hostname = hostname;
conn.Port = port;
conn.Username = username; conn.Username = username;
conn.Password = password; conn.Password = password;
conn.ExpectPasswordIsCorrect = false; conn.ExpectPasswordIsCorrect = false;
if (!_changedPass) if (File.Exists(server))
{
conn.Hostname = server;
conn.Port = ConnectionsManager.DEFAULT_XEN_PORT;
XenConnectionUI.ConnectToXapiDatabase(conn, this);
}
else if (!_changedPass)
{
StringUtility.ParseHostnamePort(server, out var hostname, out var port);
if (port == 0)
port = ConnectionsManager.DEFAULT_XEN_PORT;
conn.Hostname = hostname;
conn.Port = port;
XenConnectionUI.BeginConnect(conn, true, Owner, false); XenConnectionUI.BeginConnect(conn, true, Owner, false);
}
} }
private void conn_CachePopulated(IXenConnection conn) private void conn_CachePopulated(IXenConnection conn)

View File

@ -62,7 +62,7 @@ namespace XenAdmin.Dialogs
{ {
InitializeComponent(); InitializeComponent();
UserDetails ud = session.CurrentUserDetails; UserDetails ud = session.CurrentUserDetails;
labelCurrentUserValue.Text = ud.UserDisplayName ?? ud.UserName ?? Messages.UNKNOWN_AD_USER; labelCurrentUserValue.Text = ud?.UserDisplayName ?? ud?.UserName ?? Messages.UNKNOWN_AD_USER;
labelCurrentRoleValue.Text = Role.FriendlyCSVRoleList(session.Roles); labelCurrentRoleValue.Text = Role.FriendlyCSVRoleList(session.Roles);
authorizedRoles.Sort((r1, r2) => r2.CompareTo(r1)); authorizedRoles.Sort((r1, r2) => r2.CompareTo(r1));
labelRequiredRoleValue.Text = Role.FriendlyCSVRoleList(authorizedRoles); labelRequiredRoleValue.Text = Role.FriendlyCSVRoleList(authorizedRoles);

View File

@ -44,6 +44,18 @@ namespace XenAdmin.Network
{ {
public static Dictionary<IXenConnection, ConnectingToServerDialog> connectionDialogs = new Dictionary<IXenConnection, ConnectingToServerDialog>(); public static Dictionary<IXenConnection, ConnectingToServerDialog> connectionDialogs = new Dictionary<IXenConnection, ConnectingToServerDialog>();
public static void ConnectToXapiDatabase(IXenConnection conn, Form owner)
{
try
{
((XenConnection)conn).LoadFromDatabaseFile(conn.Hostname);
}
catch (ConnectionExists e)
{
ShowConnectingDialogError(owner, conn, e);
}
}
/// <summary> /// <summary>
/// Start connecting to a server /// Start connecting to a server
/// </summary> /// </summary>
@ -254,7 +266,7 @@ namespace XenAdmin.Network
if (!Program.RunInAutomatedTestMode) if (!Program.RunInAutomatedTestMode)
{ {
using (var dlg = new InformationDialog(c.GetDialogMessage(connection))) using (var dlg = new InformationDialog(c.GetDialogMessage()))
dlg.ShowDialog(owner); dlg.ShowDialog(owner);
} }
} }

181
XenModel/Db.cs Normal file
View File

@ -0,0 +1,181 @@
/* Copyright (c) Cloud Software Group, Inc.
*
* 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;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml;
using XenAdmin.Core;
using XenAPI;
namespace XenAdmin
{
public class Db
{
private static readonly log4net.ILog _log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public Db(XmlDocument document)
{
XmlNode dbNode = document.ChildNodes.Cast<XmlNode>().FirstOrDefault(node => node.Name == "database") ??
throw new ArgumentException("Couldn't find database tag in state db");
foreach (XmlNode n in dbNode.ChildNodes)
{
if (n.Name != "table")
continue;
try
{
Tables.Add(new DbTable(n));
}
catch (Exception e)
{
_log.Error(e);
}
}
}
public List<DbTable> Tables { get; } = new List<DbTable>();
}
/// <summary>
/// Corresponds to an API class type. Its Rows are the class instances (API objects)
/// </summary>
public class DbTable
{
private static readonly log4net.ILog _log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly Type[] AssemblyTypes = Assembly.GetExecutingAssembly().GetTypes();
public DbTable(XmlNode tableNode)
{
var name = tableNode.Attributes?["name"].Value;
ApiClassType = AssemblyTypes
.Where(t => t.Namespace == nameof(XenAPI))
.FirstOrDefault(t => string.Equals(t.Name, name, StringComparison.CurrentCultureIgnoreCase));
foreach (XmlNode n in tableNode.ChildNodes)
{
if (n.Name != "row")
continue;
try
{
Rows.Add(new Row(ApiClassType, n));
}
catch (Exception e)
{
_log.Error(e);
}
}
}
public Type ApiClassType { get; }
public List<Row> Rows { get; } = new List<Row>();
}
/// <summary>
/// Corresponds to an API object
/// </summary>
public class Row
{
private readonly XmlNode _rowNode;
public Row(Type apiClassType, XmlNode rowNode)
{
_rowNode = rowNode;
HashTable = ConvertToHashtable(out var opaqueRef);
XenObject = ConvertToXenObject(apiClassType);
if (XenObject != null)
XenObject.opaque_ref = opaqueRef;
ObjectChange = new ObjectChange(apiClassType, opaqueRef, XenObject);
}
public Hashtable HashTable { get; }
public IXenObject XenObject { get; }
public ObjectChange ObjectChange { get; }
private Hashtable ConvertToHashtable(out string opaqueRef)
{
opaqueRef = "OpaqueRef:NULL";
if (_rowNode.Attributes == null)
return null;
var hashtable = new Hashtable();
foreach (XmlAttribute attr in _rowNode.Attributes)
{
if (attr.Name.StartsWith("__"))
continue;
if (attr.Name == "_ref")
{
opaqueRef = attr.Value;
continue;
}
var key = attr.Name.Replace("__", "_");
hashtable[key] = EscapeXapiChars(attr.Value);
}
return hashtable;
}
private IXenObject ConvertToXenObject(Type apiClassType)
{
return apiClassType?.GetConstructor(new[] { typeof(Hashtable) })?.Invoke(new object[] { HashTable }) as IXenObject;
}
private string EscapeXapiChars(string input)
{
input = input.Replace("%.", " ");
input = input.Replace("%_", " ");
input = input.Replace("%n", "\n");
input = input.Replace("%r", "\r");
input = input.Replace("%t", "\t");
input = input.Replace("%%", "%");
return input;
}
}
}

View File

@ -62,9 +62,7 @@ namespace XenAdmin.Network
Session DuplicateSession(int timeout); Session DuplicateSession(int timeout);
void EndConnect(bool resetState = true, bool exiting = false); void EndConnect(bool resetState = true, bool exiting = false);
void Interrupt(); void Interrupt();
Session Connect(string user, string password);
List<string> PoolMembers { get; set; } List<string> PoolMembers { get; set; }
void LoadCache(Session session);
bool SuppressErrors { get; set; } bool SuppressErrors { get; set; }
bool PreventResettingPasswordPrompt { get;set; } bool PreventResettingPasswordPrompt { get;set; }
bool CoordinatorMayChange { get; set; } bool CoordinatorMayChange { get; set; }
@ -83,7 +81,6 @@ namespace XenAdmin.Network
void WaitFor(Func<bool> predicate, Func<bool> cancelling); void WaitFor(Func<bool> predicate, Func<bool> cancelling);
TimeSpan ServerTimeOffset { get; set; } TimeSpan ServerTimeOffset { get; set; }
Session Session { get; } Session Session { get; }
event EventHandler<EventArgs> TimeSkewUpdated;
string UriScheme { get; } string UriScheme { get; }
event EventHandler<EventArgs> XenObjectsUpdated; event EventHandler<EventArgs> XenObjectsUpdated;
NetworkCredential NetworkCredential { get; set; } NetworkCredential NetworkCredential { get; set; }

View File

@ -39,7 +39,9 @@ using XenAdmin.Core;
using XenAPI; using XenAPI;
using XenCenterLib; using XenCenterLib;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Xml;
using System.Xml.Serialization; using System.Xml.Serialization;
@ -71,13 +73,20 @@ namespace XenAdmin.Network
/// </summary> /// </summary>
public bool fromDialog = false; public bool fromDialog = false;
private volatile bool _expectedDisruption;
private volatile bool _suppressErrors;
private volatile bool _preventResettingPasswordPrompt;
private volatile bool _coordinatorMayChange;
/// <summary> /// <summary>
/// Whether we're expecting network disruption, say because we're reconfiguring the network at this time. In that case, we ignore /// 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. /// keepalive failures, and expect task polling to be disrupted.
/// </summary> /// </summary>
private volatile bool _expectedDisruption = false; public bool ExpectDisruption
{
public bool ExpectDisruption { get { return _expectedDisruption; } set { _expectedDisruption = value; } } get => _expectedDisruption;
set => _expectedDisruption = value;
}
/// <summary> /// <summary>
/// If we are 'expecting' this connection's Password property to contain the correct password /// If we are 'expecting' this connection's Password property to contain the correct password
@ -88,16 +97,12 @@ namespace XenAdmin.Network
/// <summary> /// <summary>
/// Used by the patch wizard, suppress any errors coming from reconnect attempts /// Used by the patch wizard, suppress any errors coming from reconnect attempts
/// </summary> /// </summary>
private volatile bool _suppressErrors;
public bool SuppressErrors public bool SuppressErrors
{ {
get => _suppressErrors; get => _suppressErrors;
set => _suppressErrors = value; set => _suppressErrors = value;
} }
private volatile bool _preventResettingPasswordPrompt;
/// <summary> /// <summary>
/// The password prompting function (<see cref="_promptForNewPassword"/>) is set to null when the connection is closed. /// 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. /// Set this value to true in order to prevent that from happening.
@ -112,9 +117,11 @@ namespace XenAdmin.Network
/// <summary> /// <summary>
/// Indicates whether we are expecting the pool coordinator to change soon (e.g. when explicitly designating a new coordinator). /// Indicates whether we are expecting the pool coordinator to change soon (e.g. when explicitly designating a new coordinator).
/// </summary> /// </summary>
private volatile bool _coordinatorMayChange = false; public bool CoordinatorMayChange
{
public bool CoordinatorMayChange { get { return _coordinatorMayChange; } set { _coordinatorMayChange = value; } } get => _coordinatorMayChange;
set => _coordinatorMayChange = value;
}
/// <summary> /// <summary>
/// Set when we detect that Event.next() has become blocked and we need to reset the connection. See CA-33145. /// Set when we detect that Event.next() has become blocked and we need to reset the connection. See CA-33145.
@ -132,11 +139,15 @@ namespace XenAdmin.Network
private string CoordinatorIPAddress = ""; private string CoordinatorIPAddress = "";
/// <summary> /// <summary>
/// The lock that must be taken around connectTask and monitor. /// The lock that must be taken around connectTask and heartbeat.
/// </summary> /// </summary>
private readonly object connectTaskLock = new object(); private readonly object connectTaskLock = new object();
private ConnectTask connectTask = null; private ConnectTask connectTask = null;
private Heartbeat heartbeat = null;
/// <summary>
/// This is the metrics monitor. Has to be accessed within the connectTaskLock.
/// </summary>
private Heartbeat heartbeat;
/// <summary> /// <summary>
/// Whether we are trying to automatically connect to the new coordinator. Set in HandleConnectionLost. /// Whether we are trying to automatically connect to the new coordinator. Set in HandleConnectionLost.
@ -166,7 +177,7 @@ namespace XenAdmin.Network
private DateTime m_startTime = DateTime.MinValue; private DateTime m_startTime = DateTime.MinValue;
private int m_lastDebug; private int m_lastDebug;
private TimeSpan ServerTimeOffset_ = TimeSpan.Zero; private TimeSpan _serverTimeOffset = TimeSpan.Zero;
private object ServerTimeOffsetLock = new object(); private object ServerTimeOffsetLock = new object();
/// <summary> /// <summary>
/// The offset between the clock at the client and the clock at the server. server time + ServerTimeOffset = client time. /// The offset between the clock at the client and the clock at the server. server time + ServerTimeOffset = client time.
@ -179,15 +190,14 @@ namespace XenAdmin.Network
{ {
lock (ServerTimeOffsetLock) lock (ServerTimeOffsetLock)
{ {
return ServerTimeOffset_; return _serverTimeOffset;
} }
} }
set set
{ {
lock (ServerTimeOffsetLock) lock (ServerTimeOffsetLock)
{ {
TimeSpan diff = ServerTimeOffset_ - value; TimeSpan diff = _serverTimeOffset - value;
if (diff.TotalSeconds < -1 || 1 < diff.TotalSeconds) if (diff.TotalSeconds < -1 || 1 < diff.TotalSeconds)
{ {
@ -212,11 +222,8 @@ namespace XenAdmin.Network
} }
} }
ServerTimeOffset_ = value; _serverTimeOffset = value;
} }
if (TimeSkewUpdated != null)
TimeSkewUpdated(this, EventArgs.Empty);
} }
} }
@ -250,8 +257,7 @@ namespace XenAdmin.Network
/// <summary> /// <summary>
/// The cache of XenAPI objects for this connection. /// The cache of XenAPI objects for this connection.
/// </summary> /// </summary>
private readonly ICache _cache = new Cache(); public ICache Cache { get; } = new Cache();
public ICache Cache { get { return _cache; } }
private readonly LockFreeQueue<ObjectChange> eventQueue = new LockFreeQueue<ObjectChange>(); private readonly LockFreeQueue<ObjectChange> eventQueue = new LockFreeQueue<ObjectChange>();
private readonly System.Threading.Timer cacheUpdateTimer; private readonly System.Threading.Timer cacheUpdateTimer;
@ -259,12 +265,20 @@ namespace XenAdmin.Network
/// <summary> /// <summary>
/// Whether the cache for this connection has been populated. /// Whether the cache for this connection has been populated.
/// </summary> /// </summary>
private bool cacheIsPopulated = false;
public bool CacheIsPopulated public bool CacheIsPopulated
{ {
get { return cacheIsPopulated; } get => _cacheIsPopulated;
private set
{
_cacheIsPopulated = value;
if (_cacheIsPopulated)
CachePopulated?.Invoke(this);
}
} }
private bool _cacheIsPopulated;
private bool cacheUpdaterRunning = false; private bool cacheUpdaterRunning = false;
private bool updatesWaiting = false; private bool updatesWaiting = false;
@ -280,56 +294,7 @@ namespace XenAdmin.Network
cacheUpdateTimer = new Timer(cacheUpdater); cacheUpdateTimer = new Timer(cacheUpdater);
} }
/// <summary> #region Events
/// For use by unit tests only.
/// </summary>
/// <param name="user"></param>
/// <param name="password"></param>
/// <returns></returns>
public Session Connect(string user, string password)
{
heartbeat = new Heartbeat(this, XenAdminConfigManager.Provider.ConnectionTimeout);
Session session = new Session(Session.STANDARD_TIMEOUT, this, Hostname, Port);
try
{
session.login_with_password(user, password, Helper.APIVersionString(API_Version.LATEST), Session.UserAgent);
// this is required so connection.IsConnected returns true in the unit tests.
connectTask = new ConnectTask("test", 0);
connectTask.Connected = true;
connectTask.Session = session;
return session;
}
catch (Exception)
{
return null;
}
}
/// <summary>
/// Used by unit tests only.
/// </summary>
/// <param name="session">The session.</param>
public void LoadCache(Session session)
{
this.Cache.Clear();
cacheIsPopulated = false;
string token = "";
XenObjectDownloader.GetAllObjects(session, eventQueue, () => false, ref token);
List<ObjectChange> events = new List<ObjectChange>();
while (eventQueue.NotEmpty)
events.Add(eventQueue.Dequeue());
this.Cache.UpdateFrom(this, events);
cacheIsPopulated = true;
}
/// <summary> /// <summary>
/// Fired just before the cache is cleared (i.e. the cache is still populated). /// Fired just before the cache is cleared (i.e. the cache is still populated).
@ -351,8 +316,48 @@ namespace XenAdmin.Network
/// </summary> /// </summary>
public event EventHandler<EventArgs> XenObjectsUpdated; public event EventHandler<EventArgs> XenObjectsUpdated;
#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();
var session = new Session(Session.STANDARD_TIMEOUT, this, Path.GetFileName(Hostname), Port);
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();
}
public NetworkCredential NetworkCredential { get; set; } public NetworkCredential NetworkCredential { get; set; }
public event EventHandler<EventArgs> TimeSkewUpdated;
public bool IsSimulatedConnection { get; private set; }
public bool IsConnected public bool IsConnected
{ {
@ -608,6 +613,8 @@ namespace XenAdmin.Network
lock (connectTaskLock) lock (connectTaskLock)
{ {
//if connectTask != null a connection is already in progress
if (connectTask == null) if (connectTask == null)
{ {
ClearEventQueue(); ClearEventQueue();
@ -615,17 +622,16 @@ namespace XenAdmin.Network
Cache.Clear(); Cache.Clear();
OnAfterMajorChange(false); OnAfterMajorChange(false);
connectTask = new ConnectTask(Hostname, Port); connectTask = new ConnectTask(Hostname, Port);
StopMonitor();
heartbeat?.Stop();
heartbeat = null;
heartbeat = new Heartbeat(this, XenAdminConfigManager.Provider.ConnectionTimeout); heartbeat = new Heartbeat(this, XenAdminConfigManager.Provider.ConnectionTimeout);
Thread t = new Thread(ConnectWorkerThread); Thread t = new Thread(ConnectWorkerThread);
t.Name = "Connection to " + Hostname; t.Name = "Connection to " + Hostname;
t.IsBackground = true; t.IsBackground = true;
t.Start(connectTask); t.Start(connectTask);
} }
else
{
// a connection is already in progress
}
} }
} }
@ -745,7 +751,9 @@ namespace XenAdmin.Network
lock (connectTaskLock) lock (connectTaskLock)
{ {
StopMonitor(); heartbeat?.Stop();
heartbeat = null;
if (task != null) if (task != null)
{ {
task.Cancelled = true; task.Cancelled = true;
@ -759,6 +767,7 @@ namespace XenAdmin.Network
} }
MarkConnectActionComplete(); MarkConnectActionComplete();
log.Info($"Connection to {Hostname} is ended.");
// Save list of addresses of current hosts in pool // Save list of addresses of current hosts in pool
List<string> members = new List<string>(); List<string> members = new List<string>();
@ -861,18 +870,6 @@ namespace XenAdmin.Network
Cache.Clear(); Cache.Clear();
} }
private void OnCachePopulated()
{
lock (connectTaskLock)
{
if (heartbeat != null)
heartbeat.Start();
}
CachePopulated?.Invoke(this);
MarkConnectActionComplete();
}
private string GetReason(Exception error) private string GetReason(Exception error)
{ {
if (error is ArgumentException) if (error is ArgumentException)
@ -930,8 +927,16 @@ namespace XenAdmin.Network
} }
} }
private void HandleSuccessfulConnection(string taskHostname, int task_port) private void HandleSuccessfulConnection(ConnectTask task, Pool pool)
{ {
// 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);
// add server name to history (if it's not already there) // add server name to history (if it's not already there)
XenAdminConfigManager.Provider.UpdateServerHistory(HostnameWithPort); XenAdminConfigManager.Provider.UpdateServerHistory(HostnameWithPort);
@ -945,18 +950,20 @@ namespace XenAdmin.Network
InvokeHelper.Invoke(XenAdminConfigManager.Provider.SaveSettingsIfRequired); InvokeHelper.Invoke(XenAdminConfigManager.Provider.SaveSettingsIfRequired);
} }
log.InfoFormat("Connected to {0} ({1}:{2})", FriendlyName, taskHostname, task_port); string name = string.IsNullOrEmpty(FriendlyName) || FriendlyName == task.Hostname
string name = string.IsNullOrEmpty(FriendlyName) || FriendlyName == taskHostname ? task.Hostname
? taskHostname : string.Format("{0} ({1})", FriendlyName, task.Hostname);
: string.Format("{0} ({1})", FriendlyName, taskHostname);
string title = string.Format(Messages.CONNECTING_NOTICE_TITLE, name); string title = string.Format(Messages.CONNECTING_NOTICE_TITLE, name);
string msg = string.Format(Messages.CONNECTING_NOTICE_TEXT, name); string msg = string.Format(Messages.CONNECTING_NOTICE_TEXT, name);
log.Info($"Connecting to {name} in progress.");
ConnectAction = new DummyAction(title, msg); ConnectAction = new DummyAction(title, msg);
SetPoolAndHostInAction(ConnectAction, pool, PoolOpaqueRef);
ExpectPasswordIsCorrect = true; ExpectPasswordIsCorrect = true;
OnConnectionResult(true, null, null); OnConnectionResult(true, null, null);
log.InfoFormat("Completed connection phase for pool {0} ({1}:{2}, {3}).",
FriendlyName, task.Hostname, task.Port, PoolOpaqueRef);
} }
/// <summary> /// <summary>
@ -974,13 +981,6 @@ namespace XenAdmin.Network
return true; return true;
} }
private void HandleConnectionTermination()
{
// clean up action so we dont stay open forever
if (ConnectAction != null)
ConnectAction.IsCompleted = true;
}
private readonly object PromptLock = new object(); private readonly object PromptLock = new object();
/// <summary> /// <summary>
/// Prompts the user for the changed password for a server. /// Prompts the user for the changed password for a server.
@ -1066,6 +1066,7 @@ namespace XenAdmin.Network
// on the GUI thread to be included in this set of // on the GUI thread to be included in this set of
// updates, otherwise we might hose the gui thread // updates, otherwise we might hose the gui thread
// during an event storm (ie deleting 1000 vms) // during an event storm (ie deleting 1000 vms)
List<ObjectChange> events = new List<ObjectChange>(); List<ObjectChange> events = new List<ObjectChange>();
while (eventQueue.NotEmpty) while (eventQueue.NotEmpty)
@ -1073,43 +1074,24 @@ namespace XenAdmin.Network
if (events.Count > 0) if (events.Count > 0)
{ {
InvokeHelper.Invoke(delegate () InvokeHelper.Invoke(() =>
{ {
try OnBeforeMajorChange(false);
{ bool fire = Cache.UpdateFrom(this, events);
OnBeforeMajorChange(false); OnAfterMajorChange(false);
bool fire = Cache.UpdateFrom(this, events);
OnAfterMajorChange(false);
if (fire)
OnXenObjectsUpdated();
}
catch (Exception e)
{
log.Error("Exception updating cache.", e);
#if DEBUG
if (System.Diagnostics.Debugger.IsAttached)
throw;
#endif
}
if (fire)
OnXenObjectsUpdated();
}); });
if (!cacheIsPopulated) if (!CacheIsPopulated)
{ {
cacheIsPopulated = true; lock (connectTaskLock)
try heartbeat?.Start();
{
OnCachePopulated(); CacheIsPopulated = true;
} MarkConnectActionComplete();
catch (Exception e) log.Info($"Connection to {Hostname} successful.");
{
log.Error("Exception calling OnCachePopulated.", e);
#if DEBUG
if (System.Diagnostics.Debugger.IsAttached)
throw;
#endif
}
} }
} }
} }
@ -1123,7 +1105,6 @@ namespace XenAdmin.Network
{ {
string title = string.Format(Messages.CONNECTION_OK_NOTICE_TITLE, Hostname); string title = string.Format(Messages.CONNECTION_OK_NOTICE_TITLE, Hostname);
string msg = string.Format(Messages.CONNECTION_OK_NOTICE_TEXT, Hostname); string msg = string.Format(Messages.CONNECTION_OK_NOTICE_TEXT, Hostname);
log.Info($"Connection to {Hostname} successful.");
ConnectAction.Title = title; ConnectAction.Title = title;
ConnectAction.Description = msg; ConnectAction.Description = msg;
@ -1138,26 +1119,8 @@ namespace XenAdmin.Network
} }
} }
/// <summary>
/// Stops this connection's XenMetricsMonitor thread.
/// Expects to be locked under connectTaskLock.
/// </summary>
private void StopMonitor()
{
if (heartbeat != null)
{
heartbeat.Stop();
heartbeat = null;
}
}
private const int DEFAULT_MAX_SESSION_LOGIN_ATTEMPTS = 3; private const int DEFAULT_MAX_SESSION_LOGIN_ATTEMPTS = 3;
public static bool IsSimulatorConnection(string url)
{
return url.EndsWith(".db") || url.EndsWith(".xml") || url.EndsWith(".tmp");
}
private readonly string eventNextConnectionGroupName = Guid.NewGuid().ToString(); private readonly string eventNextConnectionGroupName = Guid.NewGuid().ToString();
/// <summary> /// <summary>
@ -1186,7 +1149,7 @@ namespace XenAdmin.Network
Session eventNextSession = DuplicateSession(EVENT_NEXT_TIMEOUT); Session eventNextSession = DuplicateSession(EVENT_NEXT_TIMEOUT);
eventNextSession.ConnectionGroupName = eventNextConnectionGroupName; // this will force the eventNextSession onto its own set of TCP streams (see CA-108676) eventNextSession.ConnectionGroupName = eventNextConnectionGroupName; // this will force the eventNextSession onto its own set of TCP streams (see CA-108676)
cacheIsPopulated = false; CacheIsPopulated = false;
session.CacheWarming = true; session.CacheWarming = true;
string token = ""; string token = "";
@ -1207,14 +1170,18 @@ namespace XenAdmin.Network
OnConnectionMessageChanged(string.Format(Messages.LABEL_SYNC, this.Hostname)); OnConnectionMessageChanged(string.Format(Messages.LABEL_SYNC, this.Hostname));
} }
log.Debug("Cache is warming. Starting XenObjectDownloader.GetAllObjects");
XenObjectDownloader.GetAllObjects(session, eventQueue, task.GetCancelled, ref token); XenObjectDownloader.GetAllObjects(session, eventQueue, task.GetCancelled, ref token);
log.Debug("Cache is warming. XenObjectDownloader.GetAllObjects finished successfully");
session.CacheWarming = false; session.CacheWarming = false;
} }
else else
{ {
try try
{ {
log.Debug("Starting XenObjectDownloader.GetEvents");
XenObjectDownloader.GetEvents(eventNextSession, eventQueue, task.GetCancelled, ref token); XenObjectDownloader.GetEvents(eventNextSession, eventQueue, task.GetCancelled, ref token);
log.Debug("Starting XenObjectDownloader.GetEvents finished successfully");
eventsExceptionLogged = false; eventsExceptionLogged = false;
} }
catch (Exception exn) catch (Exception exn)
@ -1225,8 +1192,8 @@ namespace XenAdmin.Network
log.DebugFormat("Exception (disruption is expected) in XenObjectDownloader.GetEvents: {0}", exn.GetType().Name); log.DebugFormat("Exception (disruption is expected) in XenObjectDownloader.GetEvents: {0}", exn.GetType().Name);
// ignoring some exceptions when disruption is expected // ignoring some exceptions when disruption is expected
if (exn is System.IO.IOException || if (exn is IOException ||
(exn is WebException && ((exn as WebException).Status == WebExceptionStatus.KeepAliveFailure || (exn as WebException).Status == WebExceptionStatus.ConnectFailure))) (exn is WebException webEx && (webEx.Status == WebExceptionStatus.KeepAliveFailure || webEx.Status == WebExceptionStatus.ConnectFailure)))
{ {
if (!eventsExceptionLogged) if (!eventsExceptionLogged)
{ {
@ -1269,16 +1236,10 @@ namespace XenAdmin.Network
bool sameCoordinator = CoordinatorIPAddress == connection.CoordinatorIPAddress; bool sameCoordinator = CoordinatorIPAddress == connection.CoordinatorIPAddress;
if (sameRef && sameCoordinator) if (sameRef && sameCoordinator)
{
throw new ConnectionExists(connection); throw new ConnectionExists(connection);
}
else
{
// CA-15633: XenCenter does not allow connection to host
// on which backup is restored.
throw new BadRestoreDetected(connection); // CA-15633: XenCenter does not allow connection to host on which backup is restored.
} throw new BadRestoreDetected(connection);
} }
task.Connected = true; task.Connected = true;
@ -1292,39 +1253,10 @@ namespace XenAdmin.Network
: task.Hostname; : task.Hostname;
} // ConnectionsLock } // ConnectionsLock
// Remove any other (disconnected) entries for this server from the tree
List<IXenConnection> existingConnections = new List<IXenConnection>();
foreach (IXenConnection connection in ConnectionsManager.XenConnections)
{
if (connection.Hostname.Equals(task.Hostname) && !connection.IsConnected)
{
existingConnections.Add(connection);
}
}
foreach (IXenConnection connection in existingConnections)
{
ConnectionsManager.ClearCacheAndRemoveConnection(connection);
}
log.DebugFormat("Getting server time for pool {0} ({1})...", FriendlyName, PoolOpaqueRef); log.DebugFormat("Getting server time for pool {0} ({1})...", FriendlyName, PoolOpaqueRef);
SetServerTimeOffset(session, pool.master.opaque_ref); SetServerTimeOffset(session, pool.master.opaque_ref);
// add server name to history (if it's not already there) HandleSuccessfulConnection(task, pool);
XenAdminConfigManager.Provider.UpdateServerHistory(HostnameWithPort);
HandleSuccessfulConnection(task.Hostname, task.Port);
try
{
SetPoolAndHostInAction(ConnectAction, pool, PoolOpaqueRef);
}
catch (Exception e)
{
log.Error(e, e);
}
log.DebugFormat("Completed connection phase for pool {0} ({1}).", FriendlyName, PoolOpaqueRef);
} }
EventsPending(); EventsPending();
@ -1398,7 +1330,10 @@ namespace XenAdmin.Network
ClearEventQueue(); ClearEventQueue();
connectTask = null; connectTask = null;
HandleConnectionTermination();
// clean up action so we don't stay open forever
if (ConnectAction != null)
ConnectAction.IsCompleted = true;
if (error is ServerNotSupported) if (error is ServerNotSupported)
{ {
@ -1617,7 +1552,7 @@ namespace XenAdmin.Network
{ {
get get
{ {
if (EventNextBlocked || IsSimulatorConnection(Hostname)) if (EventNextBlocked || IsSimulatedConnection)
return RECONNECT_SHORT_TIMEOUT_MS; return RECONNECT_SHORT_TIMEOUT_MS;
return RECONNECT_HOST_TIMEOUT_MS; return RECONNECT_HOST_TIMEOUT_MS;
@ -1962,6 +1897,7 @@ namespace XenAdmin.Network
} }
private bool disposed; private bool disposed;
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (!disposed) if (!disposed)
@ -1980,7 +1916,7 @@ namespace XenAdmin.Network
BeforeMajorChange = null; BeforeMajorChange = null;
AfterMajorChange = null; AfterMajorChange = null;
XenObjectsUpdated = null; XenObjectsUpdated = null;
TimeSkewUpdated = null;
if (ReconnectionTimer != null) if (ReconnectionTimer != null)
ReconnectionTimer.Dispose(); ReconnectionTimer.Dispose();
if (cacheUpdateTimer != null) if (cacheUpdateTimer != null)
@ -2002,35 +1938,39 @@ namespace XenAdmin.Network
public class ConnectionExists : DisconnectionException public class ConnectionExists : DisconnectionException
{ {
public IXenConnection connection; protected readonly IXenConnection Connection;
public ConnectionExists(IXenConnection connection) public ConnectionExists(IXenConnection connection)
{ {
this.connection = connection; Connection = connection;
} }
public override string Message public override string Message
{ {
get get
{ {
if (connection != null) if (Connection == null)
return string.Format(Messages.CONNECTION_EXISTS, connection.Hostname);
else
return Messages.CONNECTION_EXISTS_NULL; return Messages.CONNECTION_EXISTS_NULL;
return string.Format(Messages.CONNECTION_EXISTS, Connection.Hostname);
} }
} }
public virtual string GetDialogMessage(IXenConnection _this) public virtual string GetDialogMessage()
{ {
Pool p = Helpers.GetPool(connection); if (Connection == null)
if (p == null) return Messages.CONNECTION_EXISTS_NULL;
return String.Format(Messages.ALREADY_CONNECTED, _this.Hostname);
return String.Format(Messages.SUPPORTER_ALREADY_CONNECTED, _this.Hostname, p.Name()); Pool p = Helpers.GetPool(Connection);
if (p == null)
return string.Format(Messages.ALREADY_CONNECTED, Connection.Hostname);
return string.Format(Messages.SUPPORTER_ALREADY_CONNECTED, Connection.Hostname, p.Name());
} }
} }
class BadRestoreDetected : ConnectionExists internal class BadRestoreDetected : ConnectionExists
{ {
public BadRestoreDetected(IXenConnection xc) public BadRestoreDetected(IXenConnection xc)
: base(xc) : base(xc)
@ -2041,11 +1981,14 @@ namespace XenAdmin.Network
{ {
get get
{ {
return String.Format(Messages.BAD_RESTORE_DETECTED, connection.Name); if (Connection == null)
return Messages.CONNECTION_EXISTS_NULL;
return string.Format(Messages.BAD_RESTORE_DETECTED, Connection.Name);
} }
} }
public override string GetDialogMessage(IXenConnection _this) public override string GetDialogMessage()
{ {
return Message; return Message;
} }

View File

@ -163,7 +163,7 @@ namespace XenAPI
//Sort roles from highest to lowest //Sort roles from highest to lowest
roles.Sort((r1, r2) => r2.CompareTo(r1)); roles.Sort((r1, r2) => r2.CompareTo(r1));
//Take the highest role //Take the highest role
return roles[0].FriendlyName(); return roles.Count > 0 ? roles[0].FriendlyName() : Messages.UNKNOWN_AD_USER;
} }
} }
} }

View File

@ -30,6 +30,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections; using System.Collections;
using System.Linq;
namespace XenAPI namespace XenAPI
@ -45,7 +46,7 @@ namespace XenAPI
/// <returns></returns> /// <returns></returns>
public static object convertStruct(Type t, Hashtable table) public static object convertStruct(Type t, Hashtable table)
{ {
return t.GetConstructor(new Type[] {typeof(Hashtable)}).Invoke(new object[] {table}); return t.GetConstructor(new Type[] { typeof(Hashtable) }).Invoke(new object[] { table });
} }
public static Type GetXenAPIType(string name) public static Type GetXenAPIType(string name)
@ -55,31 +56,30 @@ namespace XenAPI
public static bool ParseBool(Hashtable table, string key) public static bool ParseBool(Hashtable table, string key)
{ {
var val = table[key]; bool.TryParse((string)table[key], out var result);
return val == null ? false : (bool)table[key]; return result;
} }
public static DateTime ParseDateTime(Hashtable table, string key) public static DateTime ParseDateTime(Hashtable table, string key)
{ {
var val = table[key]; DateTime.TryParse((string)table[key], out var result);
return val == null ? DateTime.MinValue : (DateTime)table[key]; return result;
} }
public static double ParseDouble(Hashtable table, string key) public static double ParseDouble(Hashtable table, string key)
{ {
var val = table[key]; double.TryParse((string)table[key], out var result);
return val == null ? 0.0 : (double)table[key]; return result;
} }
public static Hashtable ParseHashTable(Hashtable table, string key) public static Hashtable ParseHashTable(Hashtable table, string key)
{ {
return (Hashtable)table[key]; return ParseSxpDict((string)table[key]);
} }
public static long ParseLong(Hashtable table, string key) public static long ParseLong(Hashtable table, string key)
{ {
long result; long.TryParse((string)table[key], out var result);
long.TryParse((string)table[key], out result);
return result; return result;
} }
@ -90,14 +90,12 @@ namespace XenAPI
public static string[] ParseStringArray(Hashtable table, string key) public static string[] ParseStringArray(Hashtable table, string key)
{ {
var val = (object[])table[key]; return ParseSxpList((string)table[key]).ToArray();
return val == null ? new string[0] : Array.ConvertAll(val, o => o.ToString());
} }
public static long[] ParseLongArray(Hashtable table, string key) public static long[] ParseLongArray(Hashtable table, string key)
{ {
var val = (object[])table[key]; return ParseSxpList((string)table[key]).Select(long.Parse).ToArray();
return val == null ? new long[0] : Array.ConvertAll(val, o => long.Parse(o.ToString()));
} }
public static XenRef<T> ParseRef<T>(Hashtable table, string key) where T : XenObject<T> public static XenRef<T> ParseRef<T>(Hashtable table, string key) where T : XenObject<T>
@ -108,14 +106,85 @@ namespace XenAPI
public static List<XenRef<T>> ParseSetRef<T>(Hashtable table, string key) where T : XenObject<T> public static List<XenRef<T>> ParseSetRef<T>(Hashtable table, string key) where T : XenObject<T>
{ {
var rs = (object[])table[key]; return ParseSxpList((string)table[key]).Select(XenRef<T>.Create).ToList();
return rs == null ? null : XenRef<T>.Create(rs);
} }
public static Dictionary<XenRef<T>, T> ParseMapRefRecord<T, U>(Hashtable table, string key) where T : XenObject<T>
private static Hashtable ParseSxpDict(string p)
{ {
Hashtable map = ParseHashTable(table, key); var result = new Hashtable();
return map == null ? null : XenRef<T>.Create<U>(map);
using (var enumerator = Tokenize(p).GetEnumerator())
{
if (!enumerator.MoveNext())
return result;
while (enumerator.MoveNext())
{
if (enumerator.Current == ")")
break;
enumerator.MoveNext();
var key = enumerator.Current;
enumerator.MoveNext();
var value = enumerator.Current;
enumerator.MoveNext();
result[key] = value;
}
return result;
}
}
private static List<string> ParseSxpList(string p)
{
var result = new List<string>();
foreach (var token in Tokenize(p))
{
if (token == "(" || token == ")")
continue;
result.Add(token);
}
return result;
}
private static IEnumerable<string> Tokenize(string str)
{
bool inStr = false;
int j = 0;
for (int i = 0; i < str.Length; i++)
{
switch (str[i])
{
case '(':
if (!inStr)
yield return "(";
break;
case ')':
if (!inStr)
yield return ")";
break;
case '\'':
case '"':
if (!inStr)
{
inStr = true;
j = i;
}
else if (str[i - 1] != '\\')
{
inStr = false;
yield return str.Substring(j + 1, i - j - 1).Replace("\\\"", "\"").Replace("\\\'", "\'");
}
break;
}
}
} }
} }
} }

View File

@ -532,6 +532,7 @@
<Compile Include="XenAPI\XenObject.cs" /> <Compile Include="XenAPI\XenObject.cs" />
<Compile Include="XenObjectDownloader.cs" /> <Compile Include="XenObjectDownloader.cs" />
<Compile Include="XenAPI\XenRef.cs" /> <Compile Include="XenAPI\XenRef.cs" />
<Compile Include="Db.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="..\Branding\Branding.ja.resx"> <EmbeddedResource Include="..\Branding\Branding.ja.resx">