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.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))

View File

@ -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)

View File

@ -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);

View File

@ -44,6 +44,18 @@ namespace XenAdmin.Network
{
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>
/// Start connecting to a server
/// </summary>
@ -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);
}
}

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);
void EndConnect(bool resetState = true, bool exiting = false);
void Interrupt();
Session Connect(string user, string password);
List<string> 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<bool> predicate, Func<bool> cancelling);
TimeSpan ServerTimeOffset { get; set; }
Session Session { get; }
event EventHandler<EventArgs> TimeSkewUpdated;
string UriScheme { get; }
event EventHandler<EventArgs> XenObjectsUpdated;
NetworkCredential NetworkCredential { get; set; }

View File

@ -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
/// </summary>
public bool fromDialog = false;
private volatile bool _expectedDisruption;
private volatile bool _suppressErrors;
private volatile bool _preventResettingPasswordPrompt;
private volatile bool _coordinatorMayChange;
/// <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>
private volatile bool _expectedDisruption = false;
public bool ExpectDisruption { get { return _expectedDisruption; } set { _expectedDisruption = value; } }
public bool ExpectDisruption
{
get => _expectedDisruption;
set => _expectedDisruption = value;
}
/// <summary>
/// If we are 'expecting' this connection's Password property to contain the correct password
@ -88,16 +97,12 @@ namespace XenAdmin.Network
/// <summary>
/// Used by the patch wizard, suppress any errors coming from reconnect attempts
/// </summary>
private volatile bool _suppressErrors;
public bool SuppressErrors
{
get => _suppressErrors;
set => _suppressErrors = value;
}
private volatile bool _preventResettingPasswordPrompt;
/// <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.
@ -112,9 +117,11 @@ namespace XenAdmin.Network
/// <summary>
/// Indicates whether we are expecting the pool coordinator to change soon (e.g. when explicitly designating a new coordinator).
/// </summary>
private volatile bool _coordinatorMayChange = false;
public bool CoordinatorMayChange { get { return _coordinatorMayChange; } set { _coordinatorMayChange = value; } }
public bool CoordinatorMayChange
{
get => _coordinatorMayChange;
set => _coordinatorMayChange = value;
}
/// <summary>
/// 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 = "";
/// <summary>
/// The lock that must be taken around connectTask and monitor.
/// The lock that must be taken around connectTask and heartbeat.
/// </summary>
private readonly object connectTaskLock = new object();
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>
/// 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();
/// <summary>
/// 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
/// <summary>
/// The cache of XenAPI objects for this connection.
/// </summary>
private readonly ICache _cache = new Cache();
public ICache Cache { get { return _cache; } }
public ICache Cache { get; } = new Cache();
private readonly LockFreeQueue<ObjectChange> eventQueue = new LockFreeQueue<ObjectChange>();
private readonly System.Threading.Timer cacheUpdateTimer;
@ -259,12 +265,20 @@ namespace XenAdmin.Network
/// <summary>
/// Whether the cache for this connection has been populated.
/// </summary>
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);
}
/// <summary>
/// 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;
}
#region Events
/// <summary>
/// Fired just before the cache is cleared (i.e. the cache is still populated).
@ -351,8 +316,48 @@ namespace XenAdmin.Network
/// </summary>
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 event EventHandler<EventArgs> 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<string> members = new List<string>();
@ -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);
}
/// <summary>
@ -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();
/// <summary>
/// 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<ObjectChange> events = new List<ObjectChange>();
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
}
}
/// <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;
public static bool IsSimulatorConnection(string url)
{
return url.EndsWith(".db") || url.EndsWith(".xml") || url.EndsWith(".tmp");
}
private readonly string eventNextConnectionGroupName = Guid.NewGuid().ToString();
/// <summary>
@ -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<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);
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;
}

View File

@ -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;
}
}
}

View File

@ -30,6 +30,7 @@
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
namespace XenAPI
@ -45,7 +46,7 @@ namespace XenAPI
/// <returns></returns>
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<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>
{
var rs = (object[])table[key];
return rs == null ? null : XenRef<T>.Create(rs);
return ParseSxpList((string)table[key]).Select(XenRef<T>.Create).ToList();
}
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);
return map == null ? null : XenRef<T>.Create<U>(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<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="XenObjectDownloader.cs" />
<Compile Include="XenAPI\XenRef.cs" />
<Compile Include="Db.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\Branding\Branding.ja.resx">