diff --git a/pkg/sdkclient/serverinfo/serverinfo.go b/pkg/sdkclient/serverinfo/serverinfo.go index 932ab2ff50..d01acd1cda 100644 --- a/pkg/sdkclient/serverinfo/serverinfo.go +++ b/pkg/sdkclient/serverinfo/serverinfo.go @@ -170,7 +170,7 @@ func checkNumaflowCompatibility(numaflowVersion string, minNumaflowVersion strin numaflowConstraint := fmt.Sprintf(">= %s", minNumaflowVersion) if err = checkConstraint(numaflowVersionSemVer, numaflowConstraint); err != nil { return fmt.Errorf("numaflow version %s must be upgraded to at least %s, in order to work with current SDK version: %w", - numaflowVersionSemVer.String(), minNumaflowVersion, err) + numaflowVersionSemVer.String(), humanReadable(minNumaflowVersion), err) } return nil } @@ -193,7 +193,7 @@ func checkSDKCompatibility(sdkVersion string, sdkLanguage Language, minSupported if !c.Check(sdkVersionPEP440) { return fmt.Errorf("SDK version %s must be upgraded to at least %s, in order to work with current numaflow version: %w", - sdkVersionPEP440.String(), sdkRequiredVersion, err) + sdkVersionPEP440.String(), humanReadable(sdkRequiredVersion), err) } } else { sdkVersionSemVer, err := semver.NewVersion(sdkVersion) @@ -203,7 +203,7 @@ func checkSDKCompatibility(sdkVersion string, sdkLanguage Language, minSupported if err := checkConstraint(sdkVersionSemVer, sdkConstraint); err != nil { return fmt.Errorf("SDK version %s must be upgraded to at least %s, in order to work with current numaflow version: %w", - sdkVersionSemVer.String(), sdkRequiredVersion, err) + sdkVersionSemVer.String(), humanReadable(sdkRequiredVersion), err) } } } diff --git a/pkg/sdkclient/serverinfo/serverinfo_test.go b/pkg/sdkclient/serverinfo/serverinfo_test.go index e96919e243..ad7f06e690 100644 --- a/pkg/sdkclient/serverinfo/serverinfo_test.go +++ b/pkg/sdkclient/serverinfo/serverinfo_test.go @@ -97,12 +97,57 @@ func Test_CheckNumaflowCompatibility(t *testing.T) { errMessage string }{ { - name: "Test with incompatible numaflow version", + name: "Test with incompatible numaflow version, min is a stable version 1.1.7", numaflowVersion: "v1.1.6", - minNumaflowVersion: "1.1.7", + minNumaflowVersion: "1.1.7-z", shouldErr: true, errMessage: "numaflow version 1.1.6 must be upgraded to at least 1.1.7, in order to work with current SDK version", }, + { + name: "Test with compatible numaflow version - min is a stable version 1.1.6", + numaflowVersion: "1.1.7", + minNumaflowVersion: "1.1.6-z", + shouldErr: false, + }, + { + name: "Test with incompatible numaflow version - min is a stable version 1.1.7, numaflow version is a pre-release version", + numaflowVersion: "v1.1.7-rc1", + minNumaflowVersion: "1.1.7-z", + shouldErr: true, + errMessage: "numaflow version 1.1.7-rc1 must be upgraded to at least 1.1.7, in order to work with current SDK version", + }, + { + name: "Test with compatible numaflow version - min is a stable version 1.1.6, numaflow version is a pre-release version", + numaflowVersion: "1.1.7-rc1", + minNumaflowVersion: "1.1.6-z", + shouldErr: false, + }, + { + name: "Test with incompatible numaflow version, min is a rc version 1.1.7-rc1", + numaflowVersion: "v1.1.6", + minNumaflowVersion: "1.1.7-rc1", + shouldErr: true, + errMessage: "numaflow version 1.1.6 must be upgraded to at least 1.1.7-rc1, in order to work with current SDK version", + }, + { + name: "Test with compatible numaflow version - min is a rc version 1.1.6-rc1", + numaflowVersion: "1.1.7", + minNumaflowVersion: "1.1.6-rc1", + shouldErr: false, + }, + { + name: "Test with incompatible numaflow version - min is a rc version 1.1.7-rc2, numaflow version is a pre-release version", + numaflowVersion: "v1.1.7-rc1", + minNumaflowVersion: "1.1.7-rc2", + shouldErr: true, + errMessage: "numaflow version 1.1.7-rc1 must be upgraded to at least 1.1.7-rc2, in order to work with current SDK version", + }, + { + name: "Test with compatible numaflow version - min is a rc version 1.1.6-rc2, numaflow version is a pre-release version", + numaflowVersion: "1.1.6-rc2", + minNumaflowVersion: "1.1.6-rc2", + shouldErr: false, + }, { name: "Test with empty MinimumNumaflowVersion field", numaflowVersion: "1.1.7", @@ -111,10 +156,17 @@ func Test_CheckNumaflowCompatibility(t *testing.T) { errMessage: "server info does not contain minimum numaflow version. Upgrade to newer SDK version", }, { - name: "Test with compatible numaflow version", - numaflowVersion: "1.1.7", - minNumaflowVersion: "1.1.6", - shouldErr: false, + name: "Test with invalid numaflow version", + numaflowVersion: "", + minNumaflowVersion: "1.1.7", + shouldErr: true, + errMessage: "error parsing numaflow version: Invalid Semantic Version", + }, + { + name: "Test with empty min numaflow version", + numaflowVersion: "1.1.7", + shouldErr: true, + errMessage: "server info does not contain minimum numaflow version. Upgrade to newer SDK version", }, } for _, tt := range tests { @@ -130,12 +182,13 @@ func Test_CheckNumaflowCompatibility(t *testing.T) { } } -func Test_CheckSDKCompatibility(t *testing.T) { +// this test suite is to test SDK compatibility check when all the minimum-supported versions are stable releases +func Test_CheckSDKCompatibility_MinimumBeingStableReleases(t *testing.T) { var testMinimumSupportedSDKVersions = sdkConstraints{ - Go: "0.6.0-0", - Python: "0.6.0a", - Java: "0.6.0-0", - Rust: "0.1.0", + Python: "0.6.0rc100", + Go: "0.6.0-z", + Java: "0.6.0-z", + Rust: "0.1.0-z", } tests := []struct { name string @@ -146,37 +199,57 @@ func Test_CheckSDKCompatibility(t *testing.T) { errMessage string }{ { - name: "Test with incompatible Python version", + name: "python pre-release version is lower than minimum supported version", sdkVersion: "v0.5.3a1", sdkLanguage: Python, minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, shouldErr: true, - errMessage: "SDK version 0.5.3a1 must be upgraded to at least 0.6.0a, in order to work with current numaflow version", + errMessage: "SDK version 0.5.3a1 must be upgraded to at least 0.6.0, in order to work with current numaflow version", }, { - name: "Test with compatible Python version", - sdkVersion: "v0.6.0a2", + name: "python pre-release version is compatible with minimum supported version", + sdkVersion: "v0.6.3a1", sdkLanguage: Python, minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, shouldErr: false, }, { - name: "Test with incompatible Java version", - sdkVersion: "v0.4.3", - sdkLanguage: Java, + name: "python stable release version is compatible with minimum supported version", + sdkVersion: "v0.6.0", + sdkLanguage: Python, + minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, + shouldErr: false, + }, + { + name: "python stable release version is lower than minimum supported version", + sdkVersion: "v0.5.3", + sdkLanguage: Python, minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, shouldErr: true, - errMessage: "SDK version 0.4.3 must be upgraded to at least 0.6.0-0, in order to work with current numaflow version", + errMessage: "SDK version 0.5.3 must be upgraded to at least 0.6.0, in order to work with current numaflow version", + }, + { + name: "java release version is compatible with minimum supported version", + sdkVersion: "v0.7.3", + sdkLanguage: Java, + minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, + shouldErr: false, }, { - name: "Test with compatible Go version", - sdkVersion: "v0.6.0-rc2", + name: "golang rc release version is compatible with minimum supported version", + sdkVersion: "v0.6.2-rc2", sdkLanguage: Go, minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, shouldErr: false, + }, { + name: "rust pre-release version is compatible with minimum supported version", + sdkVersion: "v0.1.2-0.20240913163521-4910018031a7", + sdkLanguage: Rust, + minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, + shouldErr: false, }, { - name: "Test with incompatible Rust version", + name: "rust release version is lower than minimum supported version", sdkVersion: "v0.0.3", sdkLanguage: Rust, minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, @@ -184,12 +257,125 @@ func Test_CheckSDKCompatibility(t *testing.T) { errMessage: "SDK version 0.0.3 must be upgraded to at least 0.1.0, in order to work with current numaflow version", }, { - name: "Test with compatible Rust version", - sdkVersion: "v0.1.1", + name: "java rc release version is lower than minimum supported version", + sdkVersion: "v0.6.0-rc1", + sdkLanguage: Java, + minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, + shouldErr: true, + errMessage: "SDK version 0.6.0-rc1 must be upgraded to at least 0.6.0, in order to work with current numaflow version", + }, + { + name: "golang pre-release version is lower than minimum supported version", + sdkVersion: "v0.6.0-0.20240913163521-4910018031a7", + sdkLanguage: Go, + minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, + shouldErr: true, + errMessage: "SDK version 0.6.0-0.20240913163521-4910018031a7 must be upgraded to at least 0.6.0, in order to work with current numaflow version", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := checkSDKCompatibility(tt.sdkVersion, tt.sdkLanguage, tt.minimumSupportedSDKVersions) + if tt.shouldErr { + assert.Error(t, err, "Expected error") + assert.Contains(t, err.Error(), tt.errMessage) + } else { + assert.NoError(t, err, "Expected no error") + } + }) + } +} + +// this test suite is to test SDK compatibility check when all the minimum-supported versions are pre-releases +func Test_CheckSDKCompatibility_MinimumBeingPreReleases(t *testing.T) { + var testMinimumSupportedSDKVersions = sdkConstraints{ + Python: "0.6.0b1", + Go: "0.6.0-rc2", + Java: "0.6.0-rc2", + Rust: "0.1.0-rc3", + } + tests := []struct { + name string + sdkVersion string + sdkLanguage Language + minimumSupportedSDKVersions sdkConstraints + shouldErr bool + errMessage string + }{ + { + name: "python pre-release version is lower than minimum supported version", + sdkVersion: "v0.5.3a1", + sdkLanguage: Python, + minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, + shouldErr: true, + errMessage: "SDK version 0.5.3a1 must be upgraded to at least 0.6.0b1, in order to work with current numaflow version", + }, + { + name: "python pre-release version is compatible with minimum supported version", + sdkVersion: "v0.6.3a1", + sdkLanguage: Python, + minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, + shouldErr: false, + }, + { + name: "python stable release version is compatible with minimum supported version", + sdkVersion: "v0.6.0", + sdkLanguage: Python, + minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, + shouldErr: false, + }, + { + name: "python stable release version is lower than minimum supported version", + sdkVersion: "v0.5.3", + sdkLanguage: Python, + minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, + shouldErr: true, + errMessage: "SDK version 0.5.3 must be upgraded to at least 0.6.0b1, in order to work with current numaflow version", + }, + { + name: "java release version is compatible with minimum supported version", + sdkVersion: "v0.7.3", + sdkLanguage: Java, + minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, + shouldErr: false, + }, + { + name: "golang rc release version is compatible with minimum supported version", + sdkVersion: "v0.6.2-rc2", + sdkLanguage: Go, + minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, + shouldErr: false, + }, { + name: "rust pre-release version is compatible with minimum supported version", + sdkVersion: "v0.1.2-0.20240913163521-4910018031a7", sdkLanguage: Rust, minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, shouldErr: false, }, + { + name: "rust release version is lower than minimum supported version", + sdkVersion: "v0.0.3", + sdkLanguage: Rust, + minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, + shouldErr: true, + errMessage: "SDK version 0.0.3 must be upgraded to at least 0.1.0-rc3, in order to work with current numaflow version", + }, + { + name: "java rc release version is lower than minimum supported version", + sdkVersion: "v0.6.0-rc1", + sdkLanguage: Java, + minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, + shouldErr: true, + errMessage: "SDK version 0.6.0-rc1 must be upgraded to at least 0.6.0-rc2, in order to work with current numaflow version", + }, + { + name: "golang pre-release version is lower than minimum supported version", + sdkVersion: "v0.6.0-0.20240913163521-4910018031a7", + sdkLanguage: Go, + minimumSupportedSDKVersions: testMinimumSupportedSDKVersions, + shouldErr: true, + errMessage: "SDK version 0.6.0-0.20240913163521-4910018031a7 must be upgraded to at least 0.6.0-rc2, in order to work with current numaflow version", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/sdkclient/serverinfo/types.go b/pkg/sdkclient/serverinfo/types.go index fc8fdd9b81..9e4a152d03 100644 --- a/pkg/sdkclient/serverinfo/types.go +++ b/pkg/sdkclient/serverinfo/types.go @@ -16,6 +16,8 @@ limitations under the License. package serverinfo +import "strings" + type Language string const ( @@ -27,11 +29,76 @@ const ( type sdkConstraints map[Language]string +/* +minimumSupportedSDKVersions is the minimum supported version of each SDK for the current numaflow version. +It is used to check if the SDK is compatible with the current numaflow version. + +NOTE: when updating it, please also update MINIMUM_SUPPORTED_SDK_VERSIONS for mono vertex at rust/monovertex/server_info.rs + +Python SDK versioning follows PEP 440 (https://www.python.org/dev/peps/pep-0440/). +The other SDKs follow the semver versioning scheme (https://semver.org/). + +How to update this map: + +There are two types of releases, one is the stable release and the other is the pre-release. +Below are the typical formats of the versioning scheme: + + +------------------+-------------------------+-----------------------------+ + | | PEP 440 | semver | + +------------------+-------------------------+-----------------------------+ + | stable | 0.8.0 | 0.8.0 | + +------------------+-------------------------+-----------------------------+ + | pre-release | 0.8.0a1, | 0.8.0-rc1, | + | | 0.8.0b3, | 0.8.0-0.20240913163521, | + | | or 0.8.0rc1 | etc. | + +------------------+-------------------------+-----------------------------+ + +There are two cases to consider when updating the map: + +1. The minimum supported version is a pre-release version. +In this case, directly put the exact pre-release version in the map. +E.g., if the minimum supported version is "0.8.0-rc1", then put "0.8.0-rc1" for java, go, rust. +"0.8.0b1", "0.8.0b1" for python. +2. The minimum supported version is a stable version. +In this case, put (almost) the largest available pre-release version of the stable version in the map. +This is because the go semver library considers pre-releases to be invalid if the constraint range does not include pre-releases. +Therefore, we have to put a pre-release version of the stable version in the map and choose the largest one. +For python, we use "rc100" as the largest pre-release version. For go, rust, we use "-z" as the largest pre-release version. +E.g., if the minimum supported version is "0.8.0", then put "0.8.0-z" for java, go, rust, "0.8.0rc100" for python. +A constraint ">=0.8.0-z" will match any pre-release version of 0.8.0, including "0.8.0-rc1", "0.8.0-rc2", etc. + +More details about version comparison can be found in the PEP 440 and semver documentation. +*/ var minimumSupportedSDKVersions = sdkConstraints{ - Go: "0.8.0", - Python: "0.8.0", - Java: "0.8.0", - Rust: "0.1.0", + // meaning the minimum supported python SDK version is 0.8.0 + Python: "0.8.0rc100", + // meaning the minimum supported go SDK version is 0.8.0 + Go: "0.8.0-z", + // meaning the minimum supported java SDK version is 0.8.0 + Java: "0.8.0-z", + // meaning the minimum supported rust SDK version is 0.1.0 + Rust: "0.1.0-z", +} + +// humanReadable returns the human-readable minimum supported version. +// it's used for logging purposes. +// it translates the version we used in the constraints to the real minimum supported version. +// e.g., if the given version is "0.8.0rc100", human-readable version is "0.8.0". +// if the given version is "0.8.0-z", "0.8.0". +// if "0.8.0-rc1", "0.8.0-rc1". +func humanReadable(ver string) string { + if ver == "" { + return "" + } + // semver + if strings.HasSuffix(ver, "-z") { + return ver[:len(ver)-2] + } + // PEP 440 + if strings.HasSuffix(ver, "rc100") { + return ver[:len(ver)-5] + } + return ver } type Protocol string diff --git a/pkg/shared/clients/nats/test/server.go b/pkg/shared/clients/nats/test/server.go index 556a7390c2..b64db98645 100644 --- a/pkg/shared/clients/nats/test/server.go +++ b/pkg/shared/clients/nats/test/server.go @@ -27,7 +27,7 @@ import ( func RunNatsServer(t *testing.T) *server.Server { t.Helper() opts := natstestserver.DefaultTestOptions - opts.Port = 4223 + opts.Port = -1 // Use random port to avoid conflicts return natstestserver.RunServer(&opts) } @@ -35,7 +35,7 @@ func RunNatsServer(t *testing.T) *server.Server { func RunJetStreamServer(t *testing.T) *server.Server { t.Helper() opts := natstestserver.DefaultTestOptions - opts.Port = -1 // Random port + opts.Port = -1 // Use random port to avoid conflicts opts.JetStream = true storeDir, err := os.MkdirTemp("", "") if err != nil { diff --git a/rust/monovertex/src/server_info.rs b/rust/monovertex/src/server_info.rs index 225218b158..98484869fd 100644 --- a/rust/monovertex/src/server_info.rs +++ b/rust/monovertex/src/server_info.rs @@ -77,24 +77,6 @@ pub async fn check_for_server_compatibility( Ok(()) } -/// Checks if the given version meets the specified constraint. -fn check_constraint(version: &Version, constraint: &str) -> error::Result<()> { - // Parse the given constraint as a semantic version requirement - let version_req = VersionReq::parse(constraint).map_err(|e| { - Error::ServerInfoError(format!( - "Error parsing constraint: {}, constraint string: {}", - e, constraint - )) - })?; - - // Check if the provided version satisfies the parsed constraint - if !version_req.matches(version) { - return Err(Error::ServerInfoError("invalid version".to_string())); - } - - Ok(()) -} - /// Checks if the current numaflow version is compatible with the given minimum numaflow version. fn check_numaflow_compatibility( numaflow_version: &str, @@ -105,8 +87,11 @@ fn check_numaflow_compatibility( return Err(Error::ServerInfoError("invalid version".to_string())); } + // Strip the 'v' prefix if present. + let numaflow_version_stripped = numaflow_version.trim_start_matches('v'); + // Parse the provided numaflow version as a semantic version - let numaflow_version_semver = Version::parse(numaflow_version) + let numaflow_version_semver = Version::parse(numaflow_version_stripped) .map_err(|e| Error::ServerInfoError(format!("Error parsing Numaflow version: {}", e)))?; // Create a version constraint based on the minimum numaflow version @@ -114,7 +99,7 @@ fn check_numaflow_compatibility( check_constraint(&numaflow_version_semver, &numaflow_constraint).map_err(|e| { Error::ServerInfoError(format!( "numaflow version {} must be upgraded to at least {}, in order to work with current SDK version {}", - numaflow_version_semver, min_numaflow_version, e + numaflow_version_semver, human_readable(min_numaflow_version), e )) }) } @@ -141,7 +126,7 @@ fn check_sdk_compatibility( if !specifiers.contains(&sdk_version_pep440) { return Err(Error::ServerInfoError(format!( "SDK version {} must be upgraded to at least {}, in order to work with the current numaflow version", - sdk_version_pep440, sdk_required_version + sdk_version_pep440, human_readable(sdk_required_version) ))); } } else { @@ -156,7 +141,7 @@ fn check_sdk_compatibility( check_constraint(&sdk_version_semver, &sdk_constraint).map_err(|_| { Error::ServerInfoError(format!( "SDK version {} must be upgraded to at least {}, in order to work with the current numaflow version", - sdk_version_semver, sdk_required_version + sdk_version_semver, human_readable(sdk_required_version) )) })?; } @@ -176,6 +161,86 @@ fn check_sdk_compatibility( Ok(()) } +// human_readable returns the human-readable minimum supported version. +// it's used for logging purposes. +// it translates the version we used in the constraints to the real minimum supported version. +// e.g., if the given version is "0.8.0rc100", human-readable version is "0.8.0". +// if the given version is "0.8.0-z", "0.8.0". +// if "0.8.0-rc1", "0.8.0-rc1". +fn human_readable(ver: &str) -> String { + if ver.is_empty() { + return String::new(); + } + // semver + if ver.ends_with("-z") { + return ver[..ver.len() - 2].to_string(); + } + // PEP 440 + if ver.ends_with("rc100") { + return ver[..ver.len() - 5].to_string(); + } + ver.to_string() +} + +/// Checks if the given version meets the specified constraint. +fn check_constraint(version: &Version, constraint: &str) -> error::Result<()> { + let binding = version.to_string(); + // extract the major.minor.patch version + let mmp_version = Version::parse(binding.split('-').next().unwrap_or_default()).map_err(|e| { + Error::ServerInfoError(format!("Error parsing version: {}, version string: {}", e, binding)) + })?; + let mmp_ver_str_constraint = trim_after_dash(constraint.trim_start_matches(">=")); + let mmp_ver_constraint = format!(">={}", mmp_ver_str_constraint); + + // "-z" is used to indicate the minimum supported version is a stable version + // the reason why we choose the letter z is that it can represent the largest pre-release version. + // e.g., 0.8.0-z means the minimum supported version is 0.8.0. + if constraint.contains("-z") { + if !version.to_string().starts_with(mmp_ver_str_constraint) { + // if the version is prefixed with a different mmp version, + // rust semver lib can't figure out the correct order. + // to work around, we compare the mmp version only. + // e.g., rust semver doesn't treat 0.9.0-rc* as larger than 0.8.0. + // to work around, instead of comparing 0.9.0-rc* with 0.8.0, + // we compare 0.9.0 with 0.8.0. + return check_constraint(&mmp_version, &mmp_ver_constraint); + } + return check_constraint(version, &mmp_ver_constraint); + } else if constraint.contains("-") { + // if the constraint doesn't contain "-z", but contains "-", it's a pre-release version. + if !version.to_string().starts_with(mmp_ver_str_constraint) { + // similar reason as above, we compare the mmp version only. + return check_constraint(&mmp_version, &mmp_ver_constraint); + } + } + + // TODO - remove all the extra check above once rust semver handles pre-release comparison the same way as golang. + // https://github.com/dtolnay/semver/issues/323 + + // Parse the given constraint as a semantic version requirement + let version_req = VersionReq::parse(constraint).map_err(|e| { + Error::ServerInfoError(format!( + "Error parsing constraint: {}, constraint string: {}", + e, constraint + )) + })?; + + // Check if the provided version satisfies the parsed constraint + if !version_req.matches(version) { + return Err(Error::ServerInfoError("invalid version".to_string())); + } + + Ok(()) +} + +fn trim_after_dash(input: &str) -> &str { + if let Some(pos) = input.find('-') { + &input[..pos] + } else { + input + } +} + /// Reads the server info file and returns the parsed ServerInfo struct. /// The cancellation token is used to stop ready-check of server_info file in case it is missing. /// This cancellation token is closed via the global shutdown handler. @@ -257,11 +322,12 @@ mod version { static MINIMUM_SUPPORTED_SDK_VERSIONS: Lazy = Lazy::new(|| { // TODO: populate this from a static file and make it part of the release process // the value of the map matches `minimumSupportedSDKVersions` in pkg/sdkclient/serverinfo/types.go + // please follow the instruction there to update the value let mut m = HashMap::new(); - m.insert("go".to_string(), "0.8.0".to_string()); - m.insert("python".to_string(), "0.8.0".to_string()); - m.insert("java".to_string(), "0.8.0".to_string()); - m.insert("rust".to_string(), "0.1.0".to_string()); + m.insert("go".to_string(), "0.8.0-z".to_string()); + m.insert("python".to_string(), "0.8.0rc100".to_string()); + m.insert("java".to_string(), "0.8.0-z".to_string()); + m.insert("rust".to_string(), "0.1.0-z".to_string()); m }); @@ -397,22 +463,32 @@ mod tests { Ok(()) } - // Helper function to create a SdkConstraints struct - fn create_sdk_constraints() -> version::SdkConstraints { + // Helper function to create a SdkConstraints struct with minimum supported SDK versions all being stable releases + fn create_sdk_constraints_stable_versions() -> SdkConstraints { let mut constraints = HashMap::new(); - constraints.insert("python".to_string(), "1.2.0".to_string()); - constraints.insert("java".to_string(), "2.0.0".to_string()); - constraints.insert("go".to_string(), "0.10.0".to_string()); - constraints.insert("rust".to_string(), "0.1.0".to_string()); + constraints.insert("python".to_string(), "1.2.0rc100".to_string()); + constraints.insert("java".to_string(), "2.0.0-z".to_string()); + constraints.insert("go".to_string(), "0.10.0-z".to_string()); + constraints.insert("rust".to_string(), "0.1.0-z".to_string()); + constraints + } + + // Helper function to create a SdkConstraints struct with minimum supported SDK versions all being pre-releases + fn create_sdk_constraints_pre_release_versions() -> SdkConstraints { + let mut constraints = HashMap::new(); + constraints.insert("python".to_string(), "1.2.0b2".to_string()); + constraints.insert("java".to_string(), "2.0.0-rc2".to_string()); + constraints.insert("go".to_string(), "0.10.0-rc2".to_string()); + constraints.insert("rust".to_string(), "0.1.0-rc3".to_string()); constraints } #[tokio::test] - async fn test_sdk_compatibility_python_valid() { - let sdk_version = "v1.3.0"; + async fn test_sdk_compatibility_min_stable_python_stable_release_valid() { + let sdk_version = "1.3.0"; let sdk_language = "python"; - let min_supported_sdk_versions = create_sdk_constraints(); + let min_supported_sdk_versions = create_sdk_constraints_stable_versions(); let result = check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); @@ -420,23 +496,53 @@ mod tests { } #[tokio::test] - async fn test_sdk_compatibility_python_invalid() { + async fn test_sdk_compatibility_min_stable_python_stable_release_invalid() { let sdk_version = "1.1.0"; let sdk_language = "python"; - let min_supported_sdk_versions = create_sdk_constraints(); + let min_supported_sdk_versions = create_sdk_constraints_stable_versions(); let result = check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); assert!(result.is_err()); + assert!( + result.unwrap_err().to_string().contains( + "SDK version 1.1.0 must be upgraded to at least 1.2.0, in order to work with the current numaflow version")); + } + + #[tokio::test] + async fn test_sdk_compatibility_min_stable_python_pre_release_valid() { + let sdk_version = "v1.3.0a1"; + let sdk_language = "python"; + + let min_supported_sdk_versions = create_sdk_constraints_stable_versions(); + let result = + check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); + + assert!(result.is_ok()); } #[tokio::test] - async fn test_sdk_compatibility_java_valid() { + async fn test_sdk_compatibility_min_stable_python_pre_release_invalid() { + let sdk_version = "1.1.0a1"; + let sdk_language = "python"; + + let min_supported_sdk_versions = create_sdk_constraints_stable_versions(); + let result = + check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); + + assert!(result.is_err()); + assert!( + result.unwrap_err().to_string().contains( + "SDK version 1.1.0a1 must be upgraded to at least 1.2.0, in order to work with the current numaflow version")); + } + + #[tokio::test] + async fn test_sdk_compatibility_min_stable_java_stable_release_valid() { let sdk_version = "v2.1.0"; let sdk_language = "java"; - let min_supported_sdk_versions = create_sdk_constraints(); + let min_supported_sdk_versions = create_sdk_constraints_stable_versions(); let result = check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); @@ -444,23 +550,26 @@ mod tests { } #[tokio::test] - async fn test_sdk_compatibility_java_invalid() { - let sdk_version = "1.5.0"; + async fn test_sdk_compatibility_min_stable_java_rc_release_invalid() { + let sdk_version = "2.0.0-rc1"; let sdk_language = "java"; - let min_supported_sdk_versions = create_sdk_constraints(); + let min_supported_sdk_versions = create_sdk_constraints_stable_versions(); let result = check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); assert!(result.is_err()); + assert!( + result.unwrap_err().to_string().contains( + "SDK version 2.0.0-rc1 must be upgraded to at least 2.0.0, in order to work with the current numaflow version")); } #[tokio::test] - async fn test_sdk_compatibility_go_valid() { - let sdk_version = "0.11.0"; + async fn test_sdk_compatibility_min_stable_go_rc_release_valid() { + let sdk_version = "0.11.0-rc2"; let sdk_language = "go"; - let min_supported_sdk_versions = create_sdk_constraints(); + let min_supported_sdk_versions = create_sdk_constraints_stable_versions(); let result = check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); @@ -468,23 +577,26 @@ mod tests { } #[tokio::test] - async fn test_sdk_compatibility_go_invalid() { - let sdk_version = "0.9.0"; + async fn test_sdk_compatibility_min_stable_go_pre_release_invalid() { + let sdk_version = "0.10.0-0.20240913163521-4910018031a7"; let sdk_language = "go"; - let min_supported_sdk_versions = create_sdk_constraints(); + let min_supported_sdk_versions = create_sdk_constraints_stable_versions(); let result = check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); assert!(result.is_err()); + assert!( + result.unwrap_err().to_string().contains( + "SDK version 0.10.0-0.20240913163521-4910018031a7 must be upgraded to at least 0.10.0, in order to work with the current numaflow version")); } #[tokio::test] - async fn test_sdk_compatibility_rust_valid() { - let sdk_version = "v0.1.0"; + async fn test_sdk_compatibility_min_stable_rust_pre_release_valid() { + let sdk_version = "v0.1.1-0.20240913163521-4910018031a7"; let sdk_language = "rust"; - let min_supported_sdk_versions = create_sdk_constraints(); + let min_supported_sdk_versions = create_sdk_constraints_stable_versions(); let result = check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); @@ -492,21 +604,227 @@ mod tests { } #[tokio::test] - async fn test_sdk_compatibility_rust_invalid() { + async fn test_sdk_compatibility_min_stable_rust_stable_release_invalid() { let sdk_version = "0.0.9"; let sdk_language = "rust"; - let min_supported_sdk_versions = create_sdk_constraints(); + let min_supported_sdk_versions = create_sdk_constraints_stable_versions(); + let result = + check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); + + assert!(result.is_err()); + assert!( + result.unwrap_err().to_string().contains( + "ServerInfoError Error - SDK version 0.0.9 must be upgraded to at least 0.1.0, in order to work with the current numaflow version")); + } + + #[tokio::test] + async fn test_sdk_compatibility_min_pre_release_python_stable_release_valid() { + let sdk_version = "1.3.0"; + let sdk_language = "python"; + + let min_supported_sdk_versions = create_sdk_constraints_pre_release_versions(); + let result = + check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); + + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_sdk_compatibility_min_pre_release_python_stable_release_invalid() { + let sdk_version = "1.1.0"; + let sdk_language = "python"; + + let min_supported_sdk_versions = create_sdk_constraints_pre_release_versions(); + let result = + check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); + + assert!(result.is_err()); + assert!( + result.unwrap_err().to_string().contains( + "SDK version 1.1.0 must be upgraded to at least 1.2.0b2, in order to work with the current numaflow version")); + } + + #[tokio::test] + async fn test_sdk_compatibility_min_pre_release_python_pre_release_valid() { + let sdk_version = "v1.3.0a1"; + let sdk_language = "python"; + + let min_supported_sdk_versions = create_sdk_constraints_pre_release_versions(); + let result = + check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); + + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_sdk_compatibility_min_pre_release_python_pre_release_invalid() { + let sdk_version = "1.2.0a1"; + let sdk_language = "python"; + + let min_supported_sdk_versions = create_sdk_constraints_pre_release_versions(); let result = check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); assert!(result.is_err()); + assert!( + result.unwrap_err().to_string().contains( + "SDK version 1.2.0a1 must be upgraded to at least 1.2.0b2, in order to work with the current numaflow version")); } #[tokio::test] - async fn test_numaflow_compatibility_valid() { - let numaflow_version = "1.4.0"; - let min_numaflow_version = "1.3.0"; + async fn test_sdk_compatibility_min_pre_release_java_stable_release_valid() { + let sdk_version = "v2.1.0"; + let sdk_language = "java"; + + let min_supported_sdk_versions = create_sdk_constraints_pre_release_versions(); + let result = + check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); + + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_sdk_compatibility_min_pre_release_java_rc_release_invalid() { + let sdk_version = "2.0.0-rc1"; + let sdk_language = "java"; + + let min_supported_sdk_versions = create_sdk_constraints_pre_release_versions(); + let result = + check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); + + assert!(result.is_err()); + assert!( + result.unwrap_err().to_string().contains( + "SDK version 2.0.0-rc1 must be upgraded to at least 2.0.0-rc2, in order to work with the current numaflow version")); + } + + #[tokio::test] + async fn test_sdk_compatibility_min_pre_release_go_rc_release_valid() { + let sdk_version = "0.11.0-rc2"; + let sdk_language = "go"; + + let min_supported_sdk_versions = create_sdk_constraints_pre_release_versions(); + let result = + check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); + + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_sdk_compatibility_min_pre_release_go_pre_release_invalid() { + let sdk_version = "0.10.0-0.20240913163521-4910018031a7"; + let sdk_language = "go"; + + let min_supported_sdk_versions = create_sdk_constraints_pre_release_versions(); + let result = + check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); + + assert!(result.is_err()); + assert!( + result.unwrap_err().to_string().contains( + "SDK version 0.10.0-0.20240913163521-4910018031a7 must be upgraded to at least 0.10.0-rc2, in order to work with the current numaflow version")); + } + + #[tokio::test] + async fn test_sdk_compatibility_min_pre_release_rust_pre_release_valid() { + let sdk_version = "v0.1.1-0.20240913163521-4910018031a7"; + let sdk_language = "rust"; + + let min_supported_sdk_versions = create_sdk_constraints_pre_release_versions(); + let result = + check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); + + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_sdk_compatibility_min_pre_release_rust_stable_release_invalid() { + let sdk_version = "0.0.9"; + let sdk_language = "rust"; + + let min_supported_sdk_versions = create_sdk_constraints_pre_release_versions(); + let result = + check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); + + assert!(result.is_err()); + assert!( + result.unwrap_err().to_string().contains( + "ServerInfoError Error - SDK version 0.0.9 must be upgraded to at least 0.1.0-rc3, in order to work with the current numaflow version")); + } + + #[tokio::test] + async fn test_numaflow_compatibility_invalid_version_string() { + let numaflow_version = "v1.abc.7"; + let min_numaflow_version = "1.1.6-z"; + + let result = check_numaflow_compatibility(numaflow_version, min_numaflow_version); + + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains( + "Error parsing Numaflow version: unexpected character 'a' while parsing minor version number")); + } + + #[tokio::test] + async fn test_numaflow_compatibility_min_stable_version_stable_valid() { + let numaflow_version = "v1.1.7"; + let min_numaflow_version = "1.1.6-z"; + + let result = check_numaflow_compatibility(numaflow_version, min_numaflow_version); + + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_numaflow_compatibility_min_stable_version_stable_invalid() { + let numaflow_version = "v1.1.6"; + let min_numaflow_version = "1.1.7-z"; + + let result = check_numaflow_compatibility(numaflow_version, min_numaflow_version); + + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains( + "numaflow version 1.1.6 must be upgraded to at least 1.1.7, in order to work with current SDK version")); + } + + #[tokio::test] + async fn test_numaflow_compatibility_min_stable_version_pre_release_valid() { + let numaflow_version = "1.1.7-rc1"; + let min_numaflow_version = "1.1.6-z"; + + let result = check_numaflow_compatibility(numaflow_version, min_numaflow_version); + + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_numaflow_compatibility_min_stable_version_pre_release_invalid() { + let numaflow_version = "v1.1.6-rc1"; + let min_numaflow_version = "1.1.6-z"; + + let result = check_numaflow_compatibility(numaflow_version, min_numaflow_version); + + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains( + "numaflow version 1.1.6-rc1 must be upgraded to at least 1.1.6, in order to work with current SDK version")); + } + + #[tokio::test] + async fn test_numaflow_compatibility_min_rc_version_stable_invalid() { + let numaflow_version = "v1.1.6"; + let min_numaflow_version = "1.1.7-rc1"; + + let result = check_numaflow_compatibility(numaflow_version, min_numaflow_version); + + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains( + "numaflow version 1.1.6 must be upgraded to at least 1.1.7-rc1, in order to work with current SDK version")); + } + + #[tokio::test] + async fn test_numaflow_compatibility_min_rc_version_stable_valid() { + let numaflow_version = "1.1.7"; + let min_numaflow_version = "1.1.6-rc1"; let result = check_numaflow_compatibility(numaflow_version, min_numaflow_version); @@ -514,13 +832,25 @@ mod tests { } #[tokio::test] - async fn test_numaflow_compatibility_invalid() { - let numaflow_version = "1.2.0"; - let min_numaflow_version = "1.3.0"; + async fn test_numaflow_compatibility_min_rc_version_pre_release_valid() { + let numaflow_version = "1.1.7-rc3"; + let min_numaflow_version = "1.1.7-rc2"; + + let result = check_numaflow_compatibility(numaflow_version, min_numaflow_version); + + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_numaflow_compatibility_min_rc_version_pre_release_invalid() { + let numaflow_version = "v1.1.6-rc1"; + let min_numaflow_version = "1.1.6-rc2"; let result = check_numaflow_compatibility(numaflow_version, min_numaflow_version); assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains( + "numaflow version 1.1.6-rc1 must be upgraded to at least 1.1.6-rc2, in order to work with current SDK version")); } #[tokio::test] @@ -591,7 +921,7 @@ mod tests { #[tokio::test] async fn test_read_server_info_success() { // Create a temporary directory - let dir = tempfile::tempdir().unwrap(); + let dir = tempdir().unwrap(); let file_path = dir.path().join("server_info.txt"); let cln_token = CancellationToken::new(); @@ -632,7 +962,7 @@ mod tests { #[tokio::test] async fn test_read_server_info_retry_limit() { // Create a temporary directory - let dir = tempfile::tempdir().unwrap(); + let dir = tempdir().unwrap(); let file_path = dir.path().join("server_info.txt"); // Write a partial test file not ending with END marker @@ -676,60 +1006,4 @@ mod tests { let _parsed_server_info: ServerInfo = serde_json::from_str(&json_data).expect("Failed to parse JSON"); } - - #[test] - fn test_sdk_compatibility_go_version_with_v_prefix() { - let sdk_version = "v0.11.0"; - let sdk_language = "go"; - - let mut min_supported_sdk_versions = HashMap::new(); - min_supported_sdk_versions.insert("go".to_string(), "0.10.0".to_string()); - - let result = - check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); - - assert!(result.is_ok()); - } - - #[test] - fn test_sdk_compatibility_go_version_without_v_prefix() { - let sdk_version = "0.11.0"; - let sdk_language = "go"; - - let mut min_supported_sdk_versions = HashMap::new(); - min_supported_sdk_versions.insert("go".to_string(), "0.10.0".to_string()); - - let result = - check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); - - assert!(result.is_ok()); - } - - #[test] - fn test_sdk_compatibility_go_version_with_v_prefix_invalid() { - let sdk_version = "v0.9.0"; - let sdk_language = "go"; - - let mut min_supported_sdk_versions = HashMap::new(); - min_supported_sdk_versions.insert("go".to_string(), "0.10.0".to_string()); - - let result = - check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); - - assert!(result.is_err()); - } - - #[test] - fn test_sdk_compatibility_go_version_without_v_prefix_invalid() { - let sdk_version = "0.9.0"; - let sdk_language = "go"; - - let mut min_supported_sdk_versions = HashMap::new(); - min_supported_sdk_versions.insert("go".to_string(), "0.10.0".to_string()); - - let result = - check_sdk_compatibility(sdk_version, sdk_language, &min_supported_sdk_versions); - - assert!(result.is_err()); - } }