diff --git a/XenAdmin/Controls/Wlb/WlbOptimizePool.cs b/XenAdmin/Controls/Wlb/WlbOptimizePool.cs index 3242265ec..f6595e492 100644 --- a/XenAdmin/Controls/Wlb/WlbOptimizePool.cs +++ b/XenAdmin/Controls/Wlb/WlbOptimizePool.cs @@ -39,6 +39,7 @@ using XenAPI; using XenAdmin.Actions; using XenAdmin.Actions.Wlb; using XenAdmin.Commands; +using XenAdmin.Network; namespace XenAdmin.Controls.Wlb @@ -526,7 +527,7 @@ namespace XenAdmin.Controls.Wlb { Program.AssertOnEventThread(); - if(_xenObject == null) + if (_xenObject == null || _xenObject.Connection is XenConnection conn && conn.IsSimulatedConnection) return; if (Helpers.WlbEnabled(_xenObject.Connection)) diff --git a/XenAdmin/Dialogs/AddServerDialog.cs b/XenAdmin/Dialogs/AddServerDialog.cs index dbd9965d4..d809eb135 100644 --- a/XenAdmin/Dialogs/AddServerDialog.cs +++ b/XenAdmin/Dialogs/AddServerDialog.cs @@ -29,6 +29,7 @@ */ using System; +using System.IO; using System.Linq; using XenAdmin.Core; using XenAdmin.Network; @@ -146,19 +147,12 @@ namespace XenAdmin.Dialogs conn = connection; foreach (var server in servers) - { - StringUtility.ParseHostnamePort(server, out var hostname, out var port); - - if (port == 0) - port = ConnectionsManager.DEFAULT_XEN_PORT; - - ConnectToServer(conn, hostname, port, username, password); - } + ConnectToServer(conn, server, username, password); 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) { @@ -170,14 +164,27 @@ namespace XenAdmin.Dialogs conn.EndConnect(); // in case we're already connected } - conn.Hostname = hostname; - conn.Port = port; conn.Username = username; conn.Password = password; 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); + } } private void conn_CachePopulated(IXenConnection conn) diff --git a/XenAdmin/Dialogs/RoleElevationDialog.cs b/XenAdmin/Dialogs/RoleElevationDialog.cs index 77fb328cd..1f3c57675 100644 --- a/XenAdmin/Dialogs/RoleElevationDialog.cs +++ b/XenAdmin/Dialogs/RoleElevationDialog.cs @@ -62,7 +62,7 @@ namespace XenAdmin.Dialogs { InitializeComponent(); 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); authorizedRoles.Sort((r1, r2) => r2.CompareTo(r1)); labelRequiredRoleValue.Text = Role.FriendlyCSVRoleList(authorizedRoles); diff --git a/XenAdmin/Network/XenConnectionUI.cs b/XenAdmin/Network/XenConnectionUI.cs index 6742a1ec8..f25fd2f16 100644 --- a/XenAdmin/Network/XenConnectionUI.cs +++ b/XenAdmin/Network/XenConnectionUI.cs @@ -44,6 +44,18 @@ namespace XenAdmin.Network { public static Dictionary connectionDialogs = new Dictionary(); + public static void ConnectToXapiDatabase(IXenConnection conn, Form owner) + { + try + { + ((XenConnection)conn).LoadFromDatabaseFile(conn.Hostname); + } + catch (ConnectionExists e) + { + ShowConnectingDialogError(owner, conn, e); + } + } + /// /// Start connecting to a server /// @@ -254,7 +266,7 @@ namespace XenAdmin.Network if (!Program.RunInAutomatedTestMode) { - using (var dlg = new InformationDialog(c.GetDialogMessage(connection))) + using (var dlg = new InformationDialog(c.GetDialogMessage())) dlg.ShowDialog(owner); } } diff --git a/XenModel/Db.cs b/XenModel/Db.cs new file mode 100644 index 000000000..c3ca92cf3 --- /dev/null +++ b/XenModel/Db.cs @@ -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().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 Tables { get; } = new List(); + } + + + /// + /// Corresponds to an API class type. Its Rows are the class instances (API objects) + /// + 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 Rows { get; } = new List(); + } + + + /// + /// Corresponds to an API object + /// + 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; + } + } +} \ No newline at end of file diff --git a/XenModel/Network/IXenConnection.cs b/XenModel/Network/IXenConnection.cs index 73a8c4522..0a9a58985 100644 --- a/XenModel/Network/IXenConnection.cs +++ b/XenModel/Network/IXenConnection.cs @@ -62,9 +62,7 @@ namespace XenAdmin.Network Session DuplicateSession(int timeout); void EndConnect(bool resetState = true, bool exiting = false); void Interrupt(); - Session Connect(string user, string password); List PoolMembers { get; set; } - void LoadCache(Session session); bool SuppressErrors { get; set; } bool PreventResettingPasswordPrompt { get;set; } bool CoordinatorMayChange { get; set; } @@ -83,7 +81,6 @@ namespace XenAdmin.Network void WaitFor(Func predicate, Func cancelling); TimeSpan ServerTimeOffset { get; set; } Session Session { get; } - event EventHandler TimeSkewUpdated; string UriScheme { get; } event EventHandler XenObjectsUpdated; NetworkCredential NetworkCredential { get; set; } diff --git a/XenModel/Network/XenConnection.cs b/XenModel/Network/XenConnection.cs index 5660abaa6..f97b7b9cc 100644 --- a/XenModel/Network/XenConnection.cs +++ b/XenModel/Network/XenConnection.cs @@ -39,7 +39,9 @@ using XenAdmin.Core; using XenAPI; using XenCenterLib; using System.Diagnostics; +using System.IO; using System.Linq; +using System.Xml; using System.Xml.Serialization; @@ -71,13 +73,20 @@ namespace XenAdmin.Network /// public bool fromDialog = false; + private volatile bool _expectedDisruption; + private volatile bool _suppressErrors; + private volatile bool _preventResettingPasswordPrompt; + private volatile bool _coordinatorMayChange; + /// /// 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. /// - private volatile bool _expectedDisruption = false; - - public bool ExpectDisruption { get { return _expectedDisruption; } set { _expectedDisruption = value; } } + public bool ExpectDisruption + { + get => _expectedDisruption; + set => _expectedDisruption = value; + } /// /// If we are 'expecting' this connection's Password property to contain the correct password @@ -88,16 +97,12 @@ namespace XenAdmin.Network /// /// Used by the patch wizard, suppress any errors coming from reconnect attempts /// - private volatile bool _suppressErrors; public bool SuppressErrors { get => _suppressErrors; set => _suppressErrors = value; } - - private volatile bool _preventResettingPasswordPrompt; - /// /// The password prompting function () is set to null when the connection is closed. /// Set this value to true in order to prevent that from happening. @@ -112,9 +117,11 @@ namespace XenAdmin.Network /// /// Indicates whether we are expecting the pool coordinator to change soon (e.g. when explicitly designating a new coordinator). /// - private volatile bool _coordinatorMayChange = false; - - public bool CoordinatorMayChange { get { return _coordinatorMayChange; } set { _coordinatorMayChange = value; } } + public bool CoordinatorMayChange + { + get => _coordinatorMayChange; + set => _coordinatorMayChange = value; + } /// /// 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 = ""; /// - /// The lock that must be taken around connectTask and monitor. + /// The lock that must be taken around connectTask and heartbeat. /// private readonly object connectTaskLock = new object(); private ConnectTask connectTask = null; - private Heartbeat heartbeat = null; + + /// + /// This is the metrics monitor. Has to be accessed within the connectTaskLock. + /// + private Heartbeat heartbeat; /// /// 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 int m_lastDebug; - private TimeSpan ServerTimeOffset_ = TimeSpan.Zero; + private TimeSpan _serverTimeOffset = TimeSpan.Zero; private object ServerTimeOffsetLock = new object(); /// /// 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) { - return ServerTimeOffset_; + return _serverTimeOffset; } } - set { lock (ServerTimeOffsetLock) { - TimeSpan diff = ServerTimeOffset_ - value; + TimeSpan diff = _serverTimeOffset - value; 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 /// /// The cache of XenAPI objects for this connection. /// - private readonly ICache _cache = new Cache(); - public ICache Cache { get { return _cache; } } + public ICache Cache { get; } = new Cache(); private readonly LockFreeQueue eventQueue = new LockFreeQueue(); private readonly System.Threading.Timer cacheUpdateTimer; @@ -259,12 +265,20 @@ namespace XenAdmin.Network /// /// Whether the cache for this connection has been populated. /// - private bool cacheIsPopulated = false; 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 updatesWaiting = false; @@ -280,56 +294,7 @@ namespace XenAdmin.Network cacheUpdateTimer = new Timer(cacheUpdater); } - /// - /// For use by unit tests only. - /// - /// - /// - /// - 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; - } - - } - - /// - /// Used by unit tests only. - /// - /// The session. - public void LoadCache(Session session) - { - this.Cache.Clear(); - - cacheIsPopulated = false; - - string token = ""; - XenObjectDownloader.GetAllObjects(session, eventQueue, () => false, ref token); - List events = new List(); - - while (eventQueue.NotEmpty) - events.Add(eventQueue.Dequeue()); - - this.Cache.UpdateFrom(this, events); - cacheIsPopulated = true; - - } + #region Events /// /// Fired just before the cache is cleared (i.e. the cache is still populated). @@ -351,8 +316,48 @@ namespace XenAdmin.Network /// public event EventHandler XenObjectsUpdated; + #endregion + + public void LoadFromDatabaseFile(string databaseFile) + { + IsSimulatedConnection = true; + + var foundConn = ConnectionsManager.XenConnections.Cast().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 event EventHandler TimeSkewUpdated; + + public bool IsSimulatedConnection { get; private set; } public bool IsConnected { @@ -608,6 +613,8 @@ namespace XenAdmin.Network lock (connectTaskLock) { + //if connectTask != null a connection is already in progress + if (connectTask == null) { ClearEventQueue(); @@ -615,17 +622,16 @@ namespace XenAdmin.Network Cache.Clear(); OnAfterMajorChange(false); connectTask = new ConnectTask(Hostname, Port); - StopMonitor(); + + heartbeat?.Stop(); + heartbeat = null; heartbeat = new Heartbeat(this, XenAdminConfigManager.Provider.ConnectionTimeout); + Thread t = new Thread(ConnectWorkerThread); t.Name = "Connection to " + Hostname; t.IsBackground = true; t.Start(connectTask); } - else - { - // a connection is already in progress - } } } @@ -745,7 +751,9 @@ namespace XenAdmin.Network lock (connectTaskLock) { - StopMonitor(); + heartbeat?.Stop(); + heartbeat = null; + if (task != null) { task.Cancelled = true; @@ -759,6 +767,7 @@ namespace XenAdmin.Network } MarkConnectActionComplete(); + log.Info($"Connection to {Hostname} is ended."); // Save list of addresses of current hosts in pool List members = new List(); @@ -861,18 +870,6 @@ namespace XenAdmin.Network Cache.Clear(); } - private void OnCachePopulated() - { - lock (connectTaskLock) - { - if (heartbeat != null) - heartbeat.Start(); - } - - CachePopulated?.Invoke(this); - MarkConnectActionComplete(); - } - private string GetReason(Exception error) { 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) XenAdminConfigManager.Provider.UpdateServerHistory(HostnameWithPort); @@ -945,18 +950,20 @@ namespace XenAdmin.Network InvokeHelper.Invoke(XenAdminConfigManager.Provider.SaveSettingsIfRequired); } - log.InfoFormat("Connected to {0} ({1}:{2})", FriendlyName, taskHostname, task_port); - string name = string.IsNullOrEmpty(FriendlyName) || FriendlyName == taskHostname - ? taskHostname - : string.Format("{0} ({1})", FriendlyName, taskHostname); + string name = string.IsNullOrEmpty(FriendlyName) || FriendlyName == task.Hostname + ? task.Hostname + : string.Format("{0} ({1})", FriendlyName, task.Hostname); string title = string.Format(Messages.CONNECTING_NOTICE_TITLE, name); string msg = string.Format(Messages.CONNECTING_NOTICE_TEXT, name); - log.Info($"Connecting to {name} in progress."); ConnectAction = new DummyAction(title, msg); + SetPoolAndHostInAction(ConnectAction, pool, PoolOpaqueRef); ExpectPasswordIsCorrect = true; OnConnectionResult(true, null, null); + + log.InfoFormat("Completed connection phase for pool {0} ({1}:{2}, {3}).", + FriendlyName, task.Hostname, task.Port, PoolOpaqueRef); } /// @@ -974,13 +981,6 @@ namespace XenAdmin.Network 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(); /// /// 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 // updates, otherwise we might hose the gui thread // during an event storm (ie deleting 1000 vms) + List events = new List(); while (eventQueue.NotEmpty) @@ -1073,43 +1074,24 @@ namespace XenAdmin.Network if (events.Count > 0) { - InvokeHelper.Invoke(delegate () + InvokeHelper.Invoke(() => { - try - { - OnBeforeMajorChange(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 - } + OnBeforeMajorChange(false); + bool fire = Cache.UpdateFrom(this, events); + OnAfterMajorChange(false); + if (fire) + OnXenObjectsUpdated(); }); - if (!cacheIsPopulated) + if (!CacheIsPopulated) { - cacheIsPopulated = true; - try - { - OnCachePopulated(); - } - catch (Exception e) - { - log.Error("Exception calling OnCachePopulated.", e); -#if DEBUG - if (System.Diagnostics.Debugger.IsAttached) - throw; -#endif - } + lock (connectTaskLock) + heartbeat?.Start(); + + CacheIsPopulated = true; + MarkConnectActionComplete(); + log.Info($"Connection to {Hostname} successful."); } } } @@ -1123,7 +1105,6 @@ namespace XenAdmin.Network { string title = string.Format(Messages.CONNECTION_OK_NOTICE_TITLE, Hostname); string msg = string.Format(Messages.CONNECTION_OK_NOTICE_TEXT, Hostname); - log.Info($"Connection to {Hostname} successful."); ConnectAction.Title = title; ConnectAction.Description = msg; @@ -1138,26 +1119,8 @@ namespace XenAdmin.Network } } - /// - /// Stops this connection's XenMetricsMonitor thread. - /// Expects to be locked under connectTaskLock. - /// - private void StopMonitor() - { - if (heartbeat != null) - { - heartbeat.Stop(); - heartbeat = null; - } - } - 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(); /// @@ -1186,7 +1149,7 @@ namespace XenAdmin.Network Session eventNextSession = DuplicateSession(EVENT_NEXT_TIMEOUT); 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; string token = ""; @@ -1207,14 +1170,18 @@ namespace XenAdmin.Network OnConnectionMessageChanged(string.Format(Messages.LABEL_SYNC, this.Hostname)); } + log.Debug("Cache is warming. Starting XenObjectDownloader.GetAllObjects"); XenObjectDownloader.GetAllObjects(session, eventQueue, task.GetCancelled, ref token); + log.Debug("Cache is warming. XenObjectDownloader.GetAllObjects finished successfully"); session.CacheWarming = false; } else { try { + log.Debug("Starting XenObjectDownloader.GetEvents"); XenObjectDownloader.GetEvents(eventNextSession, eventQueue, task.GetCancelled, ref token); + log.Debug("Starting XenObjectDownloader.GetEvents finished successfully"); eventsExceptionLogged = false; } catch (Exception exn) @@ -1225,8 +1192,8 @@ namespace XenAdmin.Network log.DebugFormat("Exception (disruption is expected) in XenObjectDownloader.GetEvents: {0}", exn.GetType().Name); // ignoring some exceptions when disruption is expected - if (exn is System.IO.IOException || - (exn is WebException && ((exn as WebException).Status == WebExceptionStatus.KeepAliveFailure || (exn as WebException).Status == WebExceptionStatus.ConnectFailure))) + if (exn is IOException || + (exn is WebException webEx && (webEx.Status == WebExceptionStatus.KeepAliveFailure || webEx.Status == WebExceptionStatus.ConnectFailure))) { if (!eventsExceptionLogged) { @@ -1269,16 +1236,10 @@ namespace XenAdmin.Network bool sameCoordinator = CoordinatorIPAddress == connection.CoordinatorIPAddress; if (sameRef && sameCoordinator) - { 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; @@ -1292,39 +1253,10 @@ namespace XenAdmin.Network : task.Hostname; } // ConnectionsLock - // Remove any other (disconnected) entries for this server from the tree - List existingConnections = new List(); - 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); SetServerTimeOffset(session, pool.master.opaque_ref); - // add server name to history (if it's not already there) - 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); + HandleSuccessfulConnection(task, pool); } EventsPending(); @@ -1398,7 +1330,10 @@ namespace XenAdmin.Network ClearEventQueue(); connectTask = null; - HandleConnectionTermination(); + + // clean up action so we don't stay open forever + if (ConnectAction != null) + ConnectAction.IsCompleted = true; if (error is ServerNotSupported) { @@ -1617,7 +1552,7 @@ namespace XenAdmin.Network { get { - if (EventNextBlocked || IsSimulatorConnection(Hostname)) + if (EventNextBlocked || IsSimulatedConnection) return RECONNECT_SHORT_TIMEOUT_MS; return RECONNECT_HOST_TIMEOUT_MS; @@ -1962,6 +1897,7 @@ namespace XenAdmin.Network } private bool disposed; + protected virtual void Dispose(bool disposing) { if (!disposed) @@ -1980,7 +1916,7 @@ namespace XenAdmin.Network BeforeMajorChange = null; AfterMajorChange = null; XenObjectsUpdated = null; - TimeSkewUpdated = null; + if (ReconnectionTimer != null) ReconnectionTimer.Dispose(); if (cacheUpdateTimer != null) @@ -2002,35 +1938,39 @@ namespace XenAdmin.Network public class ConnectionExists : DisconnectionException { - public IXenConnection connection; + protected readonly IXenConnection Connection; public ConnectionExists(IXenConnection connection) { - this.connection = connection; + Connection = connection; } public override string Message { get { - if (connection != null) - return string.Format(Messages.CONNECTION_EXISTS, connection.Hostname); - else + if (Connection == 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 (p == null) - return String.Format(Messages.ALREADY_CONNECTED, _this.Hostname); + if (Connection == null) + return Messages.CONNECTION_EXISTS_NULL; - 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) : base(xc) @@ -2041,11 +1981,14 @@ namespace XenAdmin.Network { 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; } diff --git a/XenModel/XenAPI-Extensions/Session.cs b/XenModel/XenAPI-Extensions/Session.cs index 7026c89af..a6a52bf1e 100644 --- a/XenModel/XenAPI-Extensions/Session.cs +++ b/XenModel/XenAPI-Extensions/Session.cs @@ -163,7 +163,7 @@ namespace XenAPI //Sort roles from highest to lowest roles.Sort((r1, r2) => r2.CompareTo(r1)); //Take the highest role - return roles[0].FriendlyName(); + return roles.Count > 0 ? roles[0].FriendlyName() : Messages.UNKNOWN_AD_USER; } } } diff --git a/XenModel/XenAPI/Marshalling.cs b/XenModel/XenAPI/Marshalling.cs index 7a35cb440..ecb5cd363 100644 --- a/XenModel/XenAPI/Marshalling.cs +++ b/XenModel/XenAPI/Marshalling.cs @@ -30,6 +30,7 @@ using System; using System.Collections.Generic; using System.Collections; +using System.Linq; namespace XenAPI @@ -45,7 +46,7 @@ namespace XenAPI /// 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) @@ -55,31 +56,30 @@ namespace XenAPI public static bool ParseBool(Hashtable table, string key) { - var val = table[key]; - return val == null ? false : (bool)table[key]; + bool.TryParse((string)table[key], out var result); + return result; } public static DateTime ParseDateTime(Hashtable table, string key) { - var val = table[key]; - return val == null ? DateTime.MinValue : (DateTime)table[key]; + DateTime.TryParse((string)table[key], out var result); + return result; } public static double ParseDouble(Hashtable table, string key) { - var val = table[key]; - return val == null ? 0.0 : (double)table[key]; + double.TryParse((string)table[key], out var result); + return result; } 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) { - long result; - long.TryParse((string)table[key], out result); + long.TryParse((string)table[key], out var result); return result; } @@ -90,14 +90,12 @@ namespace XenAPI public static string[] ParseStringArray(Hashtable table, string key) { - var val = (object[])table[key]; - return val == null ? new string[0] : Array.ConvertAll(val, o => o.ToString()); + return ParseSxpList((string)table[key]).ToArray(); } public static long[] ParseLongArray(Hashtable table, string key) { - var val = (object[])table[key]; - return val == null ? new long[0] : Array.ConvertAll(val, o => long.Parse(o.ToString())); + return ParseSxpList((string)table[key]).Select(long.Parse).ToArray(); } public static XenRef ParseRef(Hashtable table, string key) where T : XenObject @@ -108,14 +106,85 @@ namespace XenAPI public static List> ParseSetRef(Hashtable table, string key) where T : XenObject { - var rs = (object[])table[key]; - return rs == null ? null : XenRef.Create(rs); + return ParseSxpList((string)table[key]).Select(XenRef.Create).ToList(); } - public static Dictionary, T> ParseMapRefRecord(Hashtable table, string key) where T : XenObject + + private static Hashtable ParseSxpDict(string p) { - Hashtable map = ParseHashTable(table, key); - return map == null ? null : XenRef.Create(map); + var result = new Hashtable(); + + 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 ParseSxpList(string p) + { + var result = new List(); + + foreach (var token in Tokenize(p)) + { + if (token == "(" || token == ")") + continue; + + result.Add(token); + } + + return result; + } + + private static IEnumerable 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; + } + } } } } diff --git a/XenModel/XenModel.csproj b/XenModel/XenModel.csproj index d94af503b..1458e0c94 100755 --- a/XenModel/XenModel.csproj +++ b/XenModel/XenModel.csproj @@ -532,6 +532,7 @@ +