@@ -41,27 +41,38 @@ public final class CloudStackVersion implements Comparable<CloudStackVersion> {
4141
4242 private final static Pattern NUMBER_VERSION_FORMAT = Pattern .compile ("\\ d+\\ .\\ d+\\ .\\ d+(?:\\ .\\ d+)?" );
4343 private final static Pattern FULL_VERSION_FORMAT = Pattern .compile ("\\ d+\\ .\\ d+\\ .\\ d+(?:\\ .\\ d+)?(?:-[a-zA-Z]+)?(?:-\\ d+)?(?:-SNAPSHOT)?" );
44+ // Kept in sync with the same constant in engine/schema/templateConfig.sh and
45+ // scripts/installer/export-templates.sh, which independently generate/consume
46+ // system VM template metadata using this same versioning cutover rule.
4447 private final static int NEW_VERSIONING_CUTOVER_MAJOR_VERSION = 24 ;
4548
4649 private final int majorRelease ;
4750 private final int minorRelease ;
48- private final Integer patchRelease ;
51+ private final int patchRelease ;
4952 private final Integer securityRelease ;
53+ // Whether this instance was parsed via parse(value, true) — a version scheme other than
54+ // CloudStack's own. Deliberately excluded from equals()/hashCode(): two instances with the same
55+ // component values represent the same version regardless of which parsing mode produced them.
56+ // It only gates whether the new-versioning cutover rule's *interpretation* (toString()'s
57+ // canonicalization, usesNewVersioning()) applies, since that rule is specific to CloudStack's own
58+ // release numbering.
59+ private final boolean external ;
5060
51- private CloudStackVersion (final int majorRelease , final int minorRelease , final Integer patchRelease , final Integer securityRelease ) {
61+ private CloudStackVersion (final int majorRelease , final int minorRelease , final int patchRelease , final Integer securityRelease , final boolean external ) {
5262
5363 super ();
5464
5565 checkArgument (majorRelease >= 0 , CloudStackVersion .class .getName () + "(int, int, int, Integer) requires a majorRelease greater than 0." );
5666 checkArgument (minorRelease >= 0 , CloudStackVersion .class .getName () + "(int, int, int, Integer) requires a minorRelease greater than 0." );
57- checkArgument (patchRelease == null || patchRelease >= 0 , CloudStackVersion .class .getName () + "(int, int, int, Integer) requires a patchRelease greater than 0." );
67+ checkArgument (patchRelease >= 0 , CloudStackVersion .class .getName () + "(int, int, int, Integer) requires a patchRelease greater than 0." );
5868 checkArgument (securityRelease == null || securityRelease >= 0 ,
5969 CloudStackVersion .class .getName () + "(int, int, int, Integer) requires a null securityRelease or a non-null value greater than 0." );
6070
6171 this .majorRelease = majorRelease ;
6272 this .minorRelease = minorRelease ;
6373 this .patchRelease = patchRelease ;
6474 this .securityRelease = securityRelease ;
75+ this .external = external ;
6576
6677 }
6778
@@ -90,6 +101,26 @@ private CloudStackVersion(final int majorRelease, final int minorRelease, final
90101 *
91102 */
92103 public static CloudStackVersion parse (final String value ) {
104+ return parse (value , false );
105+ }
106+
107+ /**
108+ * Parses a version string the same way as {@link #parse(String)}, but with {@code external} set to
109+ * <code>true</code>, always applies the legacy major.minor.patch(.security) component mapping and
110+ * never the major-24-and-above new-versioning cutover rule.
111+ * <p>
112+ * CloudStack's own versioning cutover (see {@link #NEW_VERSIONING_CUTOVER_MAJOR_VERSION}) is a fact
113+ * about CloudStack's own release numbering. It has no bearing on unrelated version schemes, such as a
114+ * VMware/ESXi hypervisor version, that may coincidentally reach the same major version number. Callers
115+ * parsing such external version strings must pass <code>external = true</code> so a value like
116+ * <code>24.0.1</code> is not misread as a CloudStack security release.
117+ *
118+ * @param value The value to parse which must be non-blank and conform the formats listed above
119+ * @param external whether {@code value} comes from a version scheme other than CloudStack's own
120+ *
121+ * @return <code>value</code> parsed into a <code>CloudStackVersion</code> instance
122+ */
123+ public static CloudStackVersion parse (final String value , final boolean external ) {
93124
94125 // Strip out any legacy patch information from the version string ...
95126 final String trimmedValue = StringUtils .substringBefore (value , "-" );
@@ -111,12 +142,12 @@ public static CloudStackVersion parse(final String value) {
111142 final Integer securityRelease ;
112143
113144 if (components .length == 4 ) {
114- checkArgument (isLegacyVersioning (majorRelease ), CloudStackVersion .class .getName () + ".parse(String) passed " + value +
145+ checkArgument (external || isLegacyVersioning (majorRelease ), CloudStackVersion .class .getName () + ".parse(String) passed " + value +
115146 ", but major versions at or above 24 do not support legacy int.int.int.int format" );
116147 // Deprecated legacy format: major.minor.patch.security
117148 patchRelease = thirdComponent ;
118149 securityRelease = Integer .valueOf (components [3 ]);
119- } else if (isNewVersioning (majorRelease )) {
150+ } else if (! external && isNewVersioning (majorRelease )) {
120151 // New format: major.minor.securityRelease (patch dropped)
121152 patchRelease = 0 ;
122153 securityRelease = thirdComponent ;
@@ -126,7 +157,7 @@ public static CloudStackVersion parse(final String value) {
126157 securityRelease = null ;
127158 }
128159
129- return new CloudStackVersion (majorRelease , minorRelease , patchRelease , securityRelease );
160+ return new CloudStackVersion (majorRelease , minorRelease , patchRelease , securityRelease , external );
130161
131162 }
132163
@@ -143,7 +174,26 @@ public static CloudStackVersion parse(final String value) {
143174 * @since 4.12.0.0
144175 */
145176 public static int compare (String version1 , String version2 ) {
146- return parse (version1 ).compareTo (parse (version2 ));
177+ return compare (version1 , version2 , false );
178+ }
179+
180+ /**
181+ * Shortcut method to {@link #parse(String, boolean)} and {@link #compareTo(CloudStackVersion)} two versions.
182+ * Pass <code>external = true</code> when comparing version strings from a scheme other than CloudStack's
183+ * own (e.g. a VMware/ESXi or NSX/Nicira NVP version), so CloudStack's own new-versioning cutover rule is
184+ * not applied to them.
185+ *
186+ * @param version1 the first value to be parsed and compared
187+ * @param version2 the second value to be parsed and compared
188+ * @param external whether <code>version1</code>/<code>version2</code> come from a version scheme other
189+ * than CloudStack's own
190+ *
191+ * @return A value less than zero (0) indicates <code>version1</code> is less than <code>version2</code>. A value
192+ * equal to zero (0) indicates <code>version1</code> equals <code>version2</code>. A value greater than zero (0)
193+ * indicates <code>version1</code> is greater than <code>version2</code>.
194+ */
195+ public static int compare (String version1 , String version2 , boolean external ) {
196+ return parse (version1 , external ).compareTo (parse (version2 , external ));
147197 }
148198
149199 /**
@@ -214,7 +264,11 @@ public static String trimRouterVersion(String version) {
214264 return tokens [2 ];
215265 }
216266
217- return "0" ;
267+ // A sentinel that sorts lower than any real version, so callers that feed this straight into
268+ // parse()/compare() (as the router-version-check call sites do, with no try/catch) get a
269+ // "definitely needs upgrading" result instead of an uncaught parse failure. Must itself be a
270+ // value parse() accepts.
271+ return "0.0.0" ;
218272 }
219273
220274 /**
@@ -289,7 +343,18 @@ public Integer getSecurityRelease() {
289343 }
290344
291345 public boolean usesNewVersioning () {
292- return isNewVersioning (majorRelease );
346+ return !external && isNewVersioning (majorRelease );
347+ }
348+
349+ /**
350+ * The release component that identifies this version's "tiny"/build release: the security
351+ * release for a new-versioning (post-cutover) CloudStack version, or the patch release otherwise.
352+ * Consolidates a mapping that callers (e.g. system VM template version resolution, the Veeam
353+ * integration's version reporting) would otherwise have to re-derive from
354+ * {@link #usesNewVersioning()} themselves.
355+ */
356+ public int getTinyRelease () {
357+ return usesNewVersioning () ? securityRelease : patchRelease ;
293358 }
294359
295360 @ Override
@@ -319,7 +384,7 @@ public int hashCode() {
319384 @ Override
320385 public String toString () {
321386 // Canonicalize cutover-and-later versions to major.minor.securityRelease.
322- if (securityRelease != null && patchRelease == 0 && isNewVersioning ( majorRelease )) {
387+ if (securityRelease != null && patchRelease == 0 && usesNewVersioning ( )) {
323388 return Joiner .on ("." ).join (ImmutableList .of (majorRelease , minorRelease , securityRelease ));
324389 }
325390
@@ -332,7 +397,7 @@ public String toString() {
332397 */
333398 public static String getVMwareParentVersion (String hypervisorVersion ) {
334399 try {
335- CloudStackVersion version = CloudStackVersion .parse (hypervisorVersion );
400+ CloudStackVersion version = CloudStackVersion .parse (hypervisorVersion , true );
336401 String parentVersion = String .format ("%s.%s" , version .getMajorRelease (), version .getMinorRelease ());
337402 if (version .getPatchRelease () != 0 ) {
338403 parentVersion = String .format ("%s.%s" , parentVersion , version .getPatchRelease ());
0 commit comments