From cc4d3d9ee71b8926e8833b4b4930312772ddaf3b Mon Sep 17 00:00:00 2001 From: Konstantina Chremmou Date: Fri, 20 May 2022 23:10:34 +0100 Subject: [PATCH] Extended product version checker to cope with RPM versions. Use xapi_build instead of xapi for version comparisons. Signed-off-by: Konstantina Chremmou --- XenAdminTests/UnitTests/RpmVersionTests.cs | 64 +++++++ XenAdminTests/XenAdminTests.csproj | 1 + XenModel/Utils/Helpers.Versions.cs | 191 +++++++++++++-------- XenModel/XenAPI-Extensions/Host.cs | 2 +- 4 files changed, 189 insertions(+), 69 deletions(-) create mode 100644 XenAdminTests/UnitTests/RpmVersionTests.cs diff --git a/XenAdminTests/UnitTests/RpmVersionTests.cs b/XenAdminTests/UnitTests/RpmVersionTests.cs new file mode 100644 index 000000000..a2d72bd0e --- /dev/null +++ b/XenAdminTests/UnitTests/RpmVersionTests.cs @@ -0,0 +1,64 @@ +/* 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 NUnit.Framework; +using XenAdmin.Core; + +namespace XenAdminTests.UnitTests +{ + [TestFixture, Category(TestCategories.Unit)] + class RpmVersionTests + { + [Test] + [TestCase("0.1", "0.2", ExpectedResult = -1)] + [TestCase("0.1.2", "0.1.3", ExpectedResult = -1)] + [TestCase("0.1.2", "0.2", ExpectedResult = -1)] + [TestCase("0.1", "0.1.0", ExpectedResult = 0)] + [TestCase("0.1", "0.1.0.0.0", ExpectedResult = 0)] + [TestCase("7.1", "7.1.50", ExpectedResult = -1)] + [TestCase("7.1.6", "7.1.50", ExpectedResult = -1)] + [TestCase("22.19.0", "22.19.0-1.xs8", ExpectedResult = -1)] + [TestCase("22.19.0-1.xs8", "22.19.0-1.xs9", ExpectedResult = 0)] + [TestCase("22.19.0-1.xs8", "22.19.0-2.xs8", ExpectedResult = -1)] + [TestCase("22.19.0-1.xs8", "22.19.0-1.2.xs8", ExpectedResult = -1)] + [TestCase("22.19.0-1.xs8", "22.19.0-1.0.g1234abc.xs8", ExpectedResult = 0)] + [TestCase("22.19.0-1.0.xs8", "22.19.0-1.0.g1234abc.xs8", ExpectedResult = 0)] + [TestCase("22.19.0-1.1.g2234abc.xs8", "22.19.0-1.1.g1234abc.xs8", ExpectedResult = 0)] + [TestCase("1:22.19.0-1.xs8", "22.19.0-1.xs8", ExpectedResult = 1)] + [TestCase("1:22.19.0-1.xs8", "2:22.19.0-1.xs8", ExpectedResult = -1)] + [TestCase(null, "22.19.0-1.xs8", ExpectedResult = -1)] + [TestCase("22.19.0-1.xs8", null, ExpectedResult = 1)] + public int TestVersionComparer(string version1, string version2) + { + return Helpers.ProductVersionCompare(version1, version2); + } + } +} diff --git a/XenAdminTests/XenAdminTests.csproj b/XenAdminTests/XenAdminTests.csproj index 933b7eebc..657bb6701 100644 --- a/XenAdminTests/XenAdminTests.csproj +++ b/XenAdminTests/XenAdminTests.csproj @@ -112,6 +112,7 @@ + diff --git a/XenModel/Utils/Helpers.Versions.cs b/XenModel/Utils/Helpers.Versions.cs index 663b02b25..b3dd443ae 100644 --- a/XenModel/Utils/Helpers.Versions.cs +++ b/XenModel/Utils/Helpers.Versions.cs @@ -29,89 +29,144 @@ * SUCH DAMAGE. */ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; using XenAdmin.Network; using XenAPI; +using XenCenterLib; + namespace XenAdmin.Core { public static partial class Helpers { - /// - /// Only log the unrecognised version message once (CA-11201). - /// - private static bool _unrecognisedVersionWarned = false; - /// - /// Numbers should have three parts, i.e. be in the form a.b.c, otherwise they won't be parsed. - /// - /// May be null. - /// May be null. - /// + private static readonly Regex RpmVersionRegex = new Regex(@"^([0-9]+:)?([0-9a-zA-Z\.]+)(-[0-9a-zA-Z\.]+)?$"); + + public class RpmVersion + { + public int Epoch { get; private set; } + public List Version { get; } = new List(); + public List Release { get; } = new List(); + + /// + /// The xapi RPMs have versions like 1:22.13.0.1.g2226c2e3a-1.1.g4e82970.xs8. + /// The parts g* referring to git commits and the last part xs8 referring to + /// the distro are ignored by the parser. + /// + public static RpmVersion Parse(string versionString) + { + if (string.IsNullOrEmpty(versionString)) + return null; + + var match = RpmVersionRegex.Match(versionString); + if (!match.Success || match.Groups.Count < 4) + return null; + + var rpmVersion = new RpmVersion(); + + var epoch = match.Groups[1].Value.TrimEnd(':'); + if (int.TryParse(epoch, out var epochRes)) + rpmVersion.Epoch = epochRes; + + var version = match.Groups[2].Value.Split('.'); + foreach (var v in version) + { + if (int.TryParse(v, out var result)) + rpmVersion.Version.Add(result); + else + break; + } + + var release = match.Groups[3].Value.TrimStart('-').Split('.'); + foreach (var v in release) + { + if (int.TryParse(v, out var result)) + rpmVersion.Release.Add(result); + else + break; + } + + return rpmVersion; + } + } + + /// Unlike the .NET Framework's Version.CompareTo() method, this method + /// considers 1.2.0 and 1.2 as equal. public static int ProductVersionCompare(string version1, string version2) { - // Assume version numbers are of form 'a.b.c' - int a1 = 99, b1 = 99, c1 = 99, a2 = 99, b2 = 99, c2 = 99; + var v1 = RpmVersion.Parse(version1); + var v2 = RpmVersion.Parse(version2); - - string[] tokens = null; - if (version1 != null) - tokens = version1.Split('.'); - if (tokens != null && tokens.Length == 3) - { - a1 = int.Parse(tokens[0]); - b1 = int.Parse(tokens[1]); - c1 = int.Parse(tokens[2]); - } - else - { - if (!_unrecognisedVersionWarned) - { - log.DebugFormat("Unrecognised version format {0} - treating as developer version", version1); - _unrecognisedVersionWarned = true; - } - } - tokens = null; - if (version2 != null) - tokens = version2.Split('.'); - if (tokens != null && tokens.Length == 3) - { - a2 = int.Parse(tokens[0]); - b2 = int.Parse(tokens[1]); - c2 = int.Parse(tokens[2]); - } - else - { - if (!_unrecognisedVersionWarned) - { - log.DebugFormat("Unrecognised version format {0} - treating as developer version", version2); - _unrecognisedVersionWarned = true; - } - } - - if (a2 > a1) - { + if (v1 == null && v2 == null) + return 0; + if (v1 == null) return -1; - } - else if (a2 == a1) + if (v2 == null) + return 1; + + int result = v1.Epoch.CompareTo(v2.Epoch); + if (result != 0) + return result; + + int i = 0; + var min = Math.Min(v1.Version.Count, v2.Version.Count); + + while (i < min) { - if (b2 > b1) - { - return -1; - } - else if (b2 == b1) - { - if (c2 > c1) - { - return -1; - } - else if (c1 == c2) - { - return 0; - } - } + result = v1.Version[i].CompareTo(v2.Version[i]); + if (result != 0) + return result; + i++; } - return 1; + + while (i < v1.Version.Count) + { + result = v1.Version[i].CompareTo(0); + if (result != 0) + return result; + i++; + } + + while (i < v2.Version.Count) + { + result = 0.CompareTo(v2.Version[i]); + if (result != 0) + return result; + i++; + } + + i = 0; + min = Math.Min(v1.Release.Count, v2.Release.Count); + + while (i < min) + { + result = v1.Release[i].CompareTo(v2.Release[i]); + if (result != 0) + return result; + i++; + } + + while (i < v1.Release.Count) + { + result = v1.Release[i].CompareTo(0); + if (result != 0) + return result; + i++; + } + + while (i < v2.Release.Count) + { + result = 0.CompareTo(v2.Release[i]); + if (result != 0) + return result; + i++; + } + + return result; } + #region Versions /// May be null, in which case true is returned. diff --git a/XenModel/XenAPI-Extensions/Host.cs b/XenModel/XenAPI-Extensions/Host.cs index ac9fd428a..578c11d6e 100644 --- a/XenModel/XenAPI-Extensions/Host.cs +++ b/XenModel/XenAPI-Extensions/Host.cs @@ -644,7 +644,7 @@ namespace XenAPI public string GetXapiVersion() { - return Get(software_version, "xapi"); + return Get(software_version, "xapi_build") ?? Get(software_version, "xapi"); } ///