String representation of a version that conforms one of the following
* formats into a CloudStackVersion instance:
* <major>.<minor>.<patch>.<security><major>.<minor>.<patch>.<security>.<security><major>.<minor>.<patch>.<security>.<security>-<any string><major>.<minor>.<patch> (legacy, deprecated since 24.0.0, allowed only below major version 24)<major>.<minor>.<patch>.<security> (legacy, deprecated since 24.0.0, allowed only below major version 24)<major>.<minor>.<security release> (for versions >= 24.0.0)4.23.0.1-SNAPSHOT,
+ * but only below major version 24: a 4-position value whose major release is at or above 24, e.g.
+ * 24.0.0.1, is rejected.
+ *
* If the string contains a suffix that begins with a "-" character, then the "-" and all characters following it
* will be dropped.
*
@@ -85,13 +101,33 @@ private CloudStackVersion(final int majorRelease, final int minorRelease, final
*
*/
public static CloudStackVersion parse(final String value) {
+ return parse(value, false);
+ }
+
+ /**
+ * Parses a version string the same way as {@link #parse(String)}, but with {@code external} set to
+ * true, always applies the legacy major.minor.patch(.security) component mapping and
+ * never the major-24-and-above new-versioning cutover rule.
+ *
+ * CloudStack's own versioning cutover (see {@link #NEW_VERSIONING_CUTOVER_MAJOR_VERSION}) is a fact
+ * about CloudStack's own release numbering. It has no bearing on unrelated version schemes, such as a
+ * VMware/ESXi hypervisor version, that may coincidentally reach the same major version number. Callers
+ * parsing such external version strings must pass external = true so a value like
+ * 24.0.1 is not misread as a CloudStack security release.
+ *
+ * @param value The value to parse which must be non-blank and conform the formats listed above
+ * @param external whether {@code value} comes from a version scheme other than CloudStack's own
+ *
+ * @return value parsed into a CloudStackVersion instance
+ */
+ public static CloudStackVersion parse(final String value, final boolean external) {
// Strip out any legacy patch information from the version string ...
final String trimmedValue = StringUtils.substringBefore(value, "-");
checkArgument(StringUtils.isNotBlank(trimmedValue), CloudStackVersion.class.getName() + ".parse(String) requires a non-blank value");
checkArgument(NUMBER_VERSION_FORMAT.matcher(trimmedValue).matches(), CloudStackVersion.class.getName() + ".parse(String) passed " +
- value + ", but requires a value in the format of int.int.int(.int)(-external = true when comparing version strings from a scheme other than CloudStack's
+ * own (e.g. a VMware/ESXi or NSX/Nicira NVP version), so CloudStack's own new-versioning cutover rule is
+ * not applied to them.
+ *
+ * @param version1 the first value to be parsed and compared
+ * @param version2 the second value to be parsed and compared
+ * @param external whether version1/version2 come from a version scheme other
+ * than CloudStack's own
+ *
+ * @return A value less than zero (0) indicates version1 is less than version2. A value
+ * equal to zero (0) indicates version1 equals version2. A value greater than zero (0)
+ * indicates version1 is greater than version2.
+ */
+ public static int compare(String version1, String version2, boolean external) {
+ return parse(version1, external).compareTo(parse(version2, external));
}
/**
@@ -187,11 +260,28 @@ public int compareTo(final CloudStackVersion thatVersion) {
public static String trimRouterVersion(String version) {
final String[] tokens = version.split(" ");
- if (tokens.length >= 3 && FULL_VERSION_FORMAT.matcher(tokens[2]).matches()) {
+ if (tokens.length >= 3 && FULL_VERSION_FORMAT.matcher(tokens[2]).matches() && isParseableVersion(tokens[2])) {
return tokens[2];
}
- return "0";
+ // A sentinel that sorts lower than any real version, so callers that feed this straight into
+ // parse()/compare() (as the router-version-check call sites do, with no try/catch) get a
+ // "definitely needs upgrading" result instead of an uncaught parse failure. Must itself be a
+ // value parse() accepts.
+ return "0.0.0";
+ }
+
+ /**
+ * Whether {@link #parse(String)} would accept the given value, following the same cutover rule that
+ * rejects a 4-position major.minor.patch.security value once the major release reaches 24.
+ */
+ private static boolean isParseableVersion(final String value) {
+ try {
+ parse(value);
+ return true;
+ } catch (RuntimeException e) {
+ return false;
+ }
}
private static ImmutableList