/* 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 XenAPI; using XenAdmin.Diagnostics.Problems; using XenAdmin.Core; using System.IO; using XenAdmin.Diagnostics.Problems.HostProblem; using XenAdmin.Diagnostics.Problems.SRProblem; using System.Text.RegularExpressions; using System.Xml; using System.Collections.Generic; using XenAdmin.Actions; namespace XenAdmin.Diagnostics.Checks { class PatchPrecheckCheck : HostCheck { private readonly Pool_patch _patch; private readonly Pool_update _update; private static Regex PrecheckErrorRegex = new Regex("()"); private static Regex LivePatchResponseRegex = new Regex("()"); private readonly Dictionary livePatchCodesByHost; private SR srUploadedUpdates; public PatchPrecheckCheck(Host host, Pool_patch patch) : this(host, patch, null) { } public PatchPrecheckCheck(Host host, Pool_update update) : this(host, update, null) { } public PatchPrecheckCheck(Host host, Pool_patch patch, Dictionary livePatchCodesByHost) : base(host) { _patch = patch; this.livePatchCodesByHost = livePatchCodesByHost; } public PatchPrecheckCheck(Host host, Pool_update update, Dictionary livePatchCodesByHost, SR srUploadedUpdates = null) : base(host) { _update = update; this.livePatchCodesByHost = livePatchCodesByHost; this.srUploadedUpdates = srUploadedUpdates; } protected override Problem RunCheck() { // // Check that the SR where the update was uploaded is still attached // if (srUploadedUpdates != null && ((srUploadedUpdates.shared && !srUploadedUpdates.CanBeSeenFrom(Host)) || (!srUploadedUpdates.shared && srUploadedUpdates.IsBroken()))) { return new BrokenSRWarning(this, Host, srUploadedUpdates); } // // Check patch isn't already applied here // if ((Patch != null && Patch.AppliedOn(Host) != DateTime.MaxValue) || (Update != null && Update.AppliedOn(Host))) { return new PatchAlreadyApplied(this, Host); } if (!Host.IsLive()) return new HostNotLiveWarning(this, Host); if (!Host.Connection.IsConnected) throw new EndOfStreamException(Helpers.GetName(Host.Connection)); Session session = Host.Connection.DuplicateSession(); try { if (Patch != null) { string result = Pool_patch.precheck(session, Patch.opaque_ref, Host.opaque_ref); log.DebugFormat("Pool_patch.precheck returned: '{0}'", result); return FindProblem(result); } else if (Helpers.ElyOrGreater(Host)) { var livepatchStatus = Pool_update.precheck(session, Update.opaque_ref, Host.opaque_ref); log.DebugFormat("Pool_update.precheck returned livepatch_status: '{0}'", livepatchStatus); if (livePatchCodesByHost != null) livePatchCodesByHost[Host.uuid] = livepatchStatus; } //trying to apply update to partially upgraded pool else if (Helpers.ElyOrGreater(Helpers.GetMaster(Host.Connection)) && !Helpers.ElyOrGreater(Host)) { return new WrongServerVersion(this, Host); } return null; } catch (Failure f) { log.Error(f.ToString()); if(f.ErrorDescription.Count>0) log.Error(f.ErrorDescription[0]); if (f.ErrorDescription.Count > 1) log.Error(f.ErrorDescription[1]); if (f.ErrorDescription.Count > 2) log.Error(f.ErrorDescription[2]); if (f.ErrorDescription.Count > 3) log.Error(f.ErrorDescription[3]); // try and find problem from the xapi failure Problem problem = FindProblem(f); return problem ?? new PrecheckFailed(this, Host, f); } } public override string Description { get { return Messages.SERVER_SIDE_CHECK_DESCRIPTION; } } public Pool_patch Patch { get { return _patch; } } public Pool_update Update { get { return _update; } } private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); /// /// Find problem from xml result /// /// xml result, as returned by Pool_patch.precheck() call. /// E.g.: /// /// 2049859584 /// 10000000000 /// /// /// Problem or null, if no problem found private Problem FindProblem(string result) { if (!PrecheckErrorRegex.IsMatch(result.Replace("\n", ""))) return null; Match m = PrecheckErrorRegex.Match(result.Replace("\n", "")); XmlDocument doc = new XmlDocument(); doc.LoadXml(m.ToString()); log.Error(m.ToString()); XmlNode errorNode = doc.FirstChild; string errorcode = errorNode.Attributes != null && errorNode.Attributes["errorcode"] != null ? errorNode.Attributes["errorcode"].Value : string.Empty; if (errorcode == "") return null; var found = ""; var required = ""; foreach (XmlNode node in errorNode.ChildNodes) { if (node.Name == "found") found = node.InnerXml; else if (node.Name == "required") required = node.InnerXml; } var problem = FindProblem(errorcode, found, required); return problem ?? new PrecheckFailed(this, Host, new Failure(errorcode)); } /// /// Find problem from xapi Failure /// /// Xapi failure, thrown by Pool_patch.precheck() call. /// E.g.: failure.ErrorDescription.Count = 4 /// ErrorDescription[0] = "PATCH_PRECHECK_FAILED_WRONG_SERVER_VERSION" /// ErrorDescription[1] = "OpaqueRef:612b5eee-03dc-bbf5-3385-6905fdc9b079" /// ErrorDescription[2] = "6.5.0" /// ErrorDescription[3] = "^6\\.2\\.0$" /// E.g.: failure.ErrorDescription.Count = 2 /// ErrorDescription[0] = "OUT_OF_SPACE" /// ErrorDescription[1] = "/var/patch" /// /// Problem or null, if no problem found private Problem FindProblem(Failure failure) { if (failure.ErrorDescription.Count == 0) return null; var errorcode = failure.ErrorDescription[0]; var found = ""; var required = ""; if (failure.ErrorDescription.Count > 2) found = failure.ErrorDescription[2]; if (failure.ErrorDescription.Count > 3) required = failure.ErrorDescription[3]; return FindProblem(errorcode, found, required); } private Problem FindProblem(string errorcode, string found, string required) { long requiredSpace = 0; long foundSpace = 0; long reclaimableDiskSpace = 0; DiskSpaceRequirements diskSpaceReq; switch (errorcode) { case "UPDATE_PRECHECK_FAILED_WRONG_SERVER_VERSION": return new WrongServerVersion(this, Host); case "UPDATE_PRECHECK_FAILED_CONFLICT_PRESENT": return new ConflictingUpdatePresent(this, found, Host); case "UPDATE_PRECHECK_FAILED_PREREQUISITE_MISSING": return new PrerequisiteUpdateMissing(this, found, Host); case "PATCH_PRECHECK_FAILED_WRONG_SERVER_VERSION": return new WrongServerVersion(this, required, Host); case "PATCH_PRECHECK_FAILED_OUT_OF_SPACE": System.Diagnostics.Trace.Assert(Helpers.CreamOrGreater(Host.Connection)); // If not Cream or greater, we shouldn't get this error System.Diagnostics.Trace.Assert(!Helpers.ElyOrGreater(Host.Connection)); // If Ely or greater, we shouldn't get this error long.TryParse(found, out foundSpace); long.TryParse(required, out requiredSpace); // get reclaimable disk space (excluding current patch) try { var args = new Dictionary { { "exclude", Patch.uuid } }; var resultReclaimable = Host.call_plugin(Host.Connection.Session, Host.opaque_ref, "disk-space", "get_reclaimable_disk_space", args); reclaimableDiskSpace = Convert.ToInt64(resultReclaimable); } catch (Failure failure) { log.WarnFormat("Plugin call disk-space.get_reclaimable_disk_space on {0} failed with {1}", Host.Name(), failure.Message); } diskSpaceReq = new DiskSpaceRequirements(DiskSpaceRequirements.OperationTypes.install, Host, Patch.Name(), requiredSpace, foundSpace, reclaimableDiskSpace); return new HostOutOfSpaceProblem(this, Host, Patch, diskSpaceReq); case "UPDATE_PRECHECK_FAILED_OUT_OF_SPACE": System.Diagnostics.Trace.Assert(Helpers.ElyOrGreater(Host.Connection)); // If not Ely or greater, we shouldn't get this error long.TryParse(found, out foundSpace); long.TryParse(required, out requiredSpace); diskSpaceReq = new DiskSpaceRequirements(DiskSpaceRequirements.OperationTypes.install, Host, Update.Name(), requiredSpace, foundSpace, 0); return new HostOutOfSpaceProblem(this, Host, Update, diskSpaceReq); case "OUT_OF_SPACE": if (Helpers.CreamOrGreater(Host.Connection) && (Patch != null || Update != null)) { var action = Patch != null ? new GetDiskSpaceRequirementsAction(Host, Patch, true) : new GetDiskSpaceRequirementsAction(Host, Update.Name(), Update.installation_size, true); try { action.RunExternal(action.Session); } catch { log.WarnFormat("Could not get disk space requirements"); } if (action.Succeeded) return Patch != null ? new HostOutOfSpaceProblem(this, Host, Patch, action.DiskSpaceRequirements) : new HostOutOfSpaceProblem(this, Host, Update, action.DiskSpaceRequirements); } break; case "LICENCE_RESTRICTION": return new LicenseRestrictionProblem(this, Host); case "UPDATE_PRECHECK_FAILED_UNKNOWN_ERROR": // try to find the problem from the error parameters as xml string // e.g. // ErrorDescription[0] = "UPDATE_PRECHECK_FAILED_UNKNOWN_ERROR" // ErrorDescription[1] = "test-update" // ErrorDescription[2] = "" return FindProblem(found); } return null; } } }