/* Copyright (c) Citrix Systems, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ using System; using System.Collections.Generic; using XenAPI; using XenAdmin.Network; using XenAdmin.Core; using System.Globalization; namespace XenAdmin.Model { public enum HealthCheckStatus { Disabled, Enabled, Undefined } public enum DiagnosticAlertSeverity { Info, Warning, Error } public class HealthCheckSettings { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); public HealthCheckStatus Status; public int IntervalInDays, TimeOfDay, RetryInterval; public DayOfWeek DayOfWeek; public string UploadTokenSecretUuid; public string NewUploadRequest; public string UserNameSecretUuid; public string PasswordSecretUuid; public string LastSuccessfulUpload; public string LastFailedUpload; public string DiagnosticTokenSecretUuid; public string UploadUuid; public DiagnosticAlertSeverity ReportAnalysisSeverity; public int ReportAnalysisIssuesDetected; public string ReportAnalysisUploadUuid; public string ReportAnalysisUploadTime; public const int DEFAULT_INTERVAL_IN_DAYS = 14; public const int DEFAULT_RETRY_INTERVAL = 7; // in days public const int UPLOAD_REQUEST_VALIDITY_INTERVAL = 30; // in minutes public const string STATUS = "Enrollment"; public const string INTERVAL_IN_DAYS = "Schedule.IntervalInDays"; public const string DAY_OF_WEEK = "Schedule.DayOfWeek"; public const string TIME_OF_DAY = "Schedule.TimeOfDay"; public const string RETRY_INTERVAL = "Schedule.RetryInterval"; public const string UPLOAD_TOKEN_SECRET = "UploadToken.Secret"; public const string UPLOAD_CREDENTIAL_USER_SECRET = "User.Secret"; public const string UPLOAD_CREDENTIAL_PASSWORD_SECRET = "Password.Secret"; public const string UPLOAD_LOCK = "UploadLock"; public const string LAST_SUCCESSFUL_UPLOAD = "LastSuccessfulUpload"; public const string LAST_FAILED_UPLOAD = "LastFailedUpload"; public const string NEW_UPLOAD_REQUEST = "NewUploadRequest"; public const string HEALTH_CHECK_PIPE = "HealthCheckServicePipe"; public const string HEALTH_CHECK_PIPE_END_MESSAGE = "HealthCheckServicePipe"; public const string UPLOAD_UUID = "UploadUuid"; public const string PROXY_SETTINGS = "ProxySettings"; public const string XENCENTER_METADATA = "XenCenterMetadata"; public const string REPORT_TIME_PLACEHOLDER = "@HealthCheckReportTime@"; public const string DIAGNOSTIC_TOKEN_SECRET = "DiagnosticToken.Secret"; public const string REPORT_ANALYSIS_SEVERITY = "ReportAnalysis.Severity"; public const string REPORT_ANALYSIS_ISSUES_DETECTED = "ReportAnalysis.IssuesDetected"; public const string REPORT_ANALYSIS_UPLOAD_UUID = "ReportAnalysis.UploadUuid"; public const string REPORT_ANALYSIS_UPLOAD_TIME = "ReportAnalysis.UploadTime"; private const string REPORT_LINK_DOMAIN_NAME = "https://cis.citrix.com"; private const string REPORT_LINK_PATH = "AutoSupport/analysis/upload_overview"; public HealthCheckSettings(Dictionary config) { Status = config == null || !config.ContainsKey(STATUS) ? HealthCheckStatus.Undefined : (BoolKey(config, STATUS) ? HealthCheckStatus.Enabled : HealthCheckStatus.Disabled); IntervalInDays = IntKey(config, INTERVAL_IN_DAYS, DEFAULT_INTERVAL_IN_DAYS); if (!Enum.TryParse(Get(config, DAY_OF_WEEK), out DayOfWeek)) DayOfWeek = (DayOfWeek) GetDefaultDay(); TimeOfDay = IntKey(config, TIME_OF_DAY, GetDefaultTime()); RetryInterval = IntKey(config, RETRY_INTERVAL, DEFAULT_RETRY_INTERVAL); UploadTokenSecretUuid = Get(config, UPLOAD_TOKEN_SECRET); DiagnosticTokenSecretUuid = Get(config, DIAGNOSTIC_TOKEN_SECRET); NewUploadRequest = Get(config, NEW_UPLOAD_REQUEST); UserNameSecretUuid = Get(config, UPLOAD_CREDENTIAL_USER_SECRET); PasswordSecretUuid = Get(config, UPLOAD_CREDENTIAL_PASSWORD_SECRET); LastSuccessfulUpload = Get(config, LAST_SUCCESSFUL_UPLOAD); LastFailedUpload = Get(config, LAST_FAILED_UPLOAD); UploadUuid = Get(config, UPLOAD_UUID); ReportAnalysisSeverity = StringToDiagnosticAlertSeverity(Get(config, REPORT_ANALYSIS_SEVERITY)); ReportAnalysisIssuesDetected = IntKey(config, REPORT_ANALYSIS_ISSUES_DETECTED, 0); ReportAnalysisUploadUuid = Get(config, REPORT_ANALYSIS_UPLOAD_UUID); ReportAnalysisUploadTime = Get(config, REPORT_ANALYSIS_UPLOAD_TIME); } public Dictionary ToDictionary(Dictionary baseDictionary) { var newConfig = baseDictionary == null ? new Dictionary() : new Dictionary(baseDictionary); newConfig[STATUS] = Status == HealthCheckStatus.Enabled ? "true" : "false"; newConfig[INTERVAL_IN_DAYS] = IntervalInDays.ToString(); var day = (int) DayOfWeek; newConfig[DAY_OF_WEEK] = day.ToString(); newConfig[TIME_OF_DAY] = TimeOfDay.ToString(); newConfig[RETRY_INTERVAL] = RetryInterval.ToString(); newConfig[UPLOAD_TOKEN_SECRET] = UploadTokenSecretUuid; newConfig[DIAGNOSTIC_TOKEN_SECRET] = DiagnosticTokenSecretUuid; newConfig[NEW_UPLOAD_REQUEST] = NewUploadRequest; return newConfig; } public int IntervalInWeeks { get { return IntervalInDays / 7; } } public string StatusDescription { get { return Status == HealthCheckStatus.Enabled ? ReportAnalysisStatus : Messages.HEALTHCHECK_STATUS_NOT_ENROLLED; } } public bool CanRequestNewUpload { get { if (Status != HealthCheckStatus.Enabled) return false; var uploadRequestExpiryTime = NewUploadRequestTime.AddMinutes(UPLOAD_REQUEST_VALIDITY_INTERVAL); return DateTime.Compare(uploadRequestExpiryTime, DateTime.UtcNow) < 0; } } public DateTime NewUploadRequestTime { get { if (!string.IsNullOrEmpty(NewUploadRequest)) { try { return StringToDateTime(NewUploadRequest); } catch (Exception exn) { log.Error("Exception while parsing NewUploadRequest", exn); } } return DateTime.MinValue; } } public string ReportAnalysisStatus { get { if (HasAnalysisResult) { switch (ReportAnalysisIssuesDetected) { case 0: return Messages.HEALTHCHECK_STATUS_NO_ISSUES_FOUND; case 1: return Messages.HEALTHCHECK_STATUS_ONE_ISSUE_FOUND; default: return String.Format(Messages.HEALTHCHECK_STATUS_ISSUES_FOUND, ReportAnalysisIssuesDetected); } } return HasUpload ? Messages.HEALTHCHECK_STATUS_NOT_AVAILABLE_YET : Messages.HEALTHCHECK_STATUS_NO_UPLOAD_YET; } } public string GetReportAnalysisLink(string domainName) { return string.Format("{0}/{1}/{2}", string.IsNullOrEmpty(domainName) ? REPORT_LINK_DOMAIN_NAME : domainName, REPORT_LINK_PATH, ReportAnalysisUploadUuid); } public bool HasUpload { get { return !string.IsNullOrEmpty(UploadUuid); } } public bool HasAnalysisResult { get { return HasUpload && ReportAnalysisUploadUuid == UploadUuid; } } public bool HasOldAnalysisResult { get { return !HasAnalysisResult && !string.IsNullOrEmpty(ReportAnalysisUploadUuid) && !string.IsNullOrEmpty(ReportAnalysisUploadTime); } } public DateTime LastFailedUploadTime { get { DateTime lastFailedUploadTime; return !string.IsNullOrEmpty(LastFailedUpload) && TryParseStringToDateTime(LastFailedUpload, out lastFailedUploadTime) ? lastFailedUploadTime : DateTime.MinValue; } } public DateTime LastSuccessfulUploadTime { get { DateTime lastSuccessfulUploadTime; return !string.IsNullOrEmpty(LastSuccessfulUpload) && TryParseStringToDateTime(LastSuccessfulUpload, out lastSuccessfulUploadTime) ? lastSuccessfulUploadTime : DateTime.MinValue; } } #region Helper functions private static T Get(Dictionary d, string k) where T : class { return d != null && d.ContainsKey(k) ? d[k] : null; } private static bool BoolKey(Dictionary d, string k) { string v = Get(d, k); return v == null ? false : v == "true"; } private static int IntKey(Dictionary d, string k, int def) { int result; string s = Get(d, k); return s != null && int.TryParse(s, out result) ? result : def; } private static int GetDefaultDay() { return new Random().Next(1, 7); } public static int GetDefaultTime() { return new Random().Next(1, 5); } public static string DateTimeToString(DateTime dateTime) { // Round-trip format time DateTime rtime = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc); return rtime.ToString("o"); } public static DateTime StringToDateTime(string dateTimeString) { // Round-trip format time DateTime dateTime = DateTime.ParseExact(dateTimeString, "o", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); return dateTime; } public static bool TryParseStringToDateTime(string dateTimeString, out DateTime dateTime) { // Round-trip format time return DateTime.TryParseExact(dateTimeString, "o", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTime); } #endregion public string GetSecretyInfo(Session session, string secretType) { string UUID = string.Empty; switch (secretType) { case HealthCheckSettings.UPLOAD_CREDENTIAL_USER_SECRET: UUID = UserNameSecretUuid; break; case HealthCheckSettings.UPLOAD_CREDENTIAL_PASSWORD_SECRET: UUID = PasswordSecretUuid; break; case HealthCheckSettings.UPLOAD_TOKEN_SECRET: UUID = UploadTokenSecretUuid; break; case HealthCheckSettings.DIAGNOSTIC_TOKEN_SECRET: UUID = DiagnosticTokenSecretUuid; break; default: log.ErrorFormat("Error getting the {0} from the xapi secret", secretType); break; } if (session == null || string.IsNullOrEmpty(UUID)) return null; try { string opaqueref = Secret.get_by_uuid(session, UUID); return Secret.get_value(session, opaqueref); } catch (Exception e) { log.Error("Exception getting the upload token from the xapi secret", e); return null; } } public string GetSecretyInfo(IXenConnection connection, string secretType) { if (connection == null) return null; return GetSecretyInfo(connection.Session, secretType); } public bool TryGetExistingTokens(IXenConnection connection, out string uploadToken, out string diagnosticToken) { uploadToken = null; diagnosticToken = null; if (connection == null) return false; uploadToken = GetSecretyInfo(connection, UPLOAD_TOKEN_SECRET); diagnosticToken = GetSecretyInfo(connection, DIAGNOSTIC_TOKEN_SECRET); if (!String.IsNullOrEmpty(uploadToken) && !String.IsNullOrEmpty(diagnosticToken)) return true; return TryGetExistingTokensFromOtherConnections(connection, out uploadToken, out diagnosticToken); } private static bool TryGetExistingTokensFromOtherConnections(IXenConnection currentConnection, out string uploadToken, out string diagnosticToken) { uploadToken = null; diagnosticToken = null; foreach (var connection in ConnectionsManager.XenConnectionsCopy) { if (connection == currentConnection || !connection.IsConnected) continue; var poolOfOne = Helpers.GetPoolOfOne(connection); if (poolOfOne != null) { uploadToken = poolOfOne.HealthCheckSettings.GetSecretyInfo(connection, UPLOAD_TOKEN_SECRET); diagnosticToken = poolOfOne.HealthCheckSettings.GetSecretyInfo(connection, DIAGNOSTIC_TOKEN_SECRET); if (!String.IsNullOrEmpty(uploadToken) && !String.IsNullOrEmpty(diagnosticToken)) return true; } } return false; } internal static DiagnosticAlertSeverity StringToDiagnosticAlertSeverity(string severity) { DiagnosticAlertSeverity result; if (Enum.TryParse(severity, out result)) return result; return DiagnosticAlertSeverity.Info; } } }