Skip to content

Commit

Permalink
feat(upgrade): add upgrade path validation in upgrade
Browse files Browse the repository at this point in the history
Signed-off-by: sinhaashish <[email protected]>
  • Loading branch information
sinhaashish committed May 8, 2023
1 parent 0f6eb09 commit f2cc5f0
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 31 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion k8s/upgrade-job/src/common/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ pub(crate) const CHART_VERSION_LABEL_KEY: &str = "openebs.io/version";
pub(crate) const DRAIN_FOR_UPGRADE: &str = "mayastor-upgrade";

/// This is the allowed upgrade to-version/to-version-range for the Core chart.
pub(crate) const TO_CORE_SEMVER: &str = ">=2.1.0-rc.0, <=2.1.0";
pub(crate) const TO_CORE_SEMVER: &str = ">=2.2.0-rc.0, <=2.2.0";

/// This version range will be only allowed to upgrade to TO_CORE_SEMVER above. This range applies
/// to the Core chart.
pub(crate) const FROM_CORE_SEMVER: &str = ">=2.0.0, <=2.0.1";

/// This is the allowed upgrade to-version/to-version-range for the Core chart.
pub(crate) const TO_DEVELOP_SEMVER: &str = "0.0.0";

/// This is the allowed upgrade to-version/to-version-range for the Umbrella chart.
pub(crate) const TO_UMBRELLA_SEMVER: &str = "3.6.0";

Expand Down
11 changes: 9 additions & 2 deletions k8s/upgrade-job/src/upgrade/path.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::{
common::{
constants::{FROM_CORE_SEMVER, FROM_UMBRELLA_SEMVER, TO_CORE_SEMVER, TO_UMBRELLA_SEMVER},
constants::{
FROM_CORE_SEMVER, FROM_UMBRELLA_SEMVER, TO_CORE_SEMVER, TO_DEVELOP_SEMVER,
TO_UMBRELLA_SEMVER,
},
error::{HelmChartNameSplit, OpeningFile, Result, SemverParse, YamlParseFromFile},
},
helm::{chart::Chart, upgrade::HelmChart},
Expand Down Expand Up @@ -32,7 +35,11 @@ pub(crate) fn is_valid(chart_variant: HelmChart, from: &Version, to: &Version) -
version_string: TO_CORE_SEMVER.to_string(),
})?;

if to_req.matches(to) {
let to_develop = VersionReq::parse(TO_DEVELOP_SEMVER).context(SemverParse {
version_string: TO_DEVELOP_SEMVER.to_string(),
})?;

if to_req.matches(to) || to_develop.matches(to) {
let from_req = VersionReq::parse(FROM_CORE_SEMVER).context(SemverParse {
version_string: FROM_CORE_SEMVER.to_string(),
})?;
Expand Down
1 change: 1 addition & 0 deletions k8s/upgrade/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ async-trait = "0.1.64"
serde = "1.0.152"
serde_json = "1.0.93"
snafu = "0.7.4"
semver = { version="1.0.17", features = ["serde"] }
# Tracing
tracing = "0.1.37"
17 changes: 15 additions & 2 deletions k8s/upgrade/src/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub(crate) fn get_image_version_tag() -> String {
/// Returns the git tag version (if tag is found) or simply returns the commit hash (12 characters).
pub(crate) fn release_version() -> Option<String> {
let version_info = version_info!();
println!("version_info {:#?}", version_info);
version_info.version_tag
}

Expand All @@ -69,9 +70,11 @@ pub(crate) fn upgrade_event_selector(release_name: &str, component_name: &str) -
let name_value = format!("{release_name}-{component_name}-{tag}");
format!("{kind},{name_key}={name_value}")
}

/// Installed release name.
pub(crate) const HELM_RELEASE_NAME_LABEL: &str = "openebs.io/release";

/// Installed release version.
pub(crate) const HELM_RELEASE_VERSION_LABEL: &str = "openebs.io/version";
/// Default image repository.
pub(crate) const DEFAULT_IMAGE_REGISTRY: &str = "docker.io";
/// The upgrade job will use the UPGRADE_JOB_IMAGE_NAME image (below) with this tag.
pub(crate) const UPGRADE_JOB_IMAGE_TAG: &str = "develop";
Expand Down Expand Up @@ -105,3 +108,13 @@ pub(crate) const AGENT_CORE_POD_LABEL: &str = "app=agent-core";
pub(crate) const API_REST_POD_LABEL: &str = "app=api-rest";
/// UPGRADE_EVENT_REASON is the reason field in upgrade job.
pub(crate) const UPGRADE_EVENT_REASON: &str = "MayastorUpgrade";

/// This is the allowed upgrade to-version/to-version-range for the Core chart.
pub(crate) const TO_CORE_SEMVER: &str = ">=2.2.0-rc.0, <=2.2.0";

/// This is the allowed upgrade to-version/to-version-range for the Core chart.
pub(crate) const TO_DEVELOP_SEMVER: &str = "0.0.0";

/// This version range will be only allowed to upgrade to TO_CORE_SEMVER above. This range applies
/// to the Core chart.
pub(crate) const FROM_CORE_SEMVER: &str = ">=2.0.0, <=2.1.0";
17 changes: 16 additions & 1 deletion k8s/upgrade/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ pub enum Error {
#[snafu(display("No Message present in event."))]
MessageInEventNotPresent,

/// Source and target version are same.
#[snafu(display("Source and target version are same for upgrade."))]
SourceTargetVersionSame,

/// Nodes are in cordoned state.
#[snafu(display("Nodes are in cordoned state."))]
NodesInCordonedState,
Expand Down Expand Up @@ -132,7 +136,7 @@ pub enum Error {
namespace,
source
))]
ListDeploymantsWithLabel {
ListDeploymentsWithLabel {
source: kube::Error,
label: String,
namespace: String,
Expand Down Expand Up @@ -189,6 +193,17 @@ pub enum Error {
/// Openapi configuration error.
#[snafu(display("openapi configuration Error: {}", source))]
OpenapiClientConfiguration { source: anyhow::Error },

/// Error for failures in generating semver::Value from a &str input.
#[snafu(display("Failed to parse {} as a valid semver: {}", version_string, source))]
SemverParse {
source: semver::Error,
version_string: String,
},

/// Error for when the detected upgrade path for PRODUCT is not supported.
#[snafu(display("The upgrade path is invalid"))]
InvalidUpgradePath,
}

/// A wrapper type to remove repeated Result<T, Error> returns.
Expand Down
105 changes: 85 additions & 20 deletions k8s/upgrade/src/preflight_validations.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
use crate::{
constant::SINGLE_REPLICA_VOLUME,
error::{
ListStorageNodes, ListVolumes, NodeSpecNotPresent, NodesInCordonedState,
OpenapiClientConfiguration, Result, SingleReplicaVolumeErr, VolumeRebuildInProgress,
constant::{
get_image_version_tag, FROM_CORE_SEMVER, SINGLE_REPLICA_VOLUME, TO_CORE_SEMVER,
TO_DEVELOP_SEMVER,
},
upgrade_lib,
upgrade_resources::upgrade::get_pvc_from_uuid,
error, upgrade_lib,
upgrade_resources::upgrade::{get_pvc_from_uuid, get_source_version},
user_prompt,
};

use openapi::models::CordonDrainState;
use semver::{Version, VersionReq};
use snafu::ResultExt;
use std::{collections::HashSet, path::PathBuf};

Expand All @@ -21,7 +20,7 @@ pub async fn preflight_check(
skip_single_replica_volume_validation: bool,
skip_replica_rebuild: bool,
skip_cordoned_node_validation: bool,
) -> Result<()> {
) -> error::Result<()> {
console_logger::info(user_prompt::UPGRADE_WARNING, "");
// Initialise the REST client.
let config = kube_proxy::ConfigBuilder::default_api_rest()
Expand All @@ -30,9 +29,12 @@ pub async fn preflight_check(
.with_target_mod(|t| t.with_namespace(namespace))
.build()
.await
.context(OpenapiClientConfiguration)?;
.context(error::OpenapiClientConfiguration)?;
let rest_client = upgrade_lib::RestClient::new_with_config(config);

// upgrade path validation
upgrade_path_validation(namespace).await?;

if !skip_replica_rebuild {
rebuild_in_progress_validation(&rest_client).await?;
}
Expand All @@ -48,17 +50,19 @@ pub async fn preflight_check(
}

/// Prompt to user and error out if some nodes are already in cordoned state.
pub async fn already_cordoned_nodes_validation(client: &upgrade_lib::RestClient) -> Result<()> {
pub async fn already_cordoned_nodes_validation(
client: &upgrade_lib::RestClient,
) -> error::Result<()> {
let mut cordoned_nodes_list = Vec::new();
let nodes = client
.nodes_api()
.get_nodes()
.await
.context(ListStorageNodes)?;
.context(error::ListStorageNodes)?;
let nodelist = nodes.into_body();
for node in nodelist {
let node_spec = node.spec.ok_or(
NodeSpecNotPresent {
error::NodeSpecNotPresent {
node: node.id.to_string(),
}
.build(),
Expand All @@ -76,13 +80,15 @@ pub async fn already_cordoned_nodes_validation(client: &upgrade_lib::RestClient)
user_prompt::CORDONED_NODE_WARNING,
&cordoned_nodes_list.join("\n"),
);
return NodesInCordonedState.fail();
return error::NodesInCordonedState.fail();
}
Ok(())
}

/// Prompt to user and error out if the cluster has single replica volume.
pub async fn single_volume_replica_validation(client: &upgrade_lib::RestClient) -> Result<()> {
pub async fn single_volume_replica_validation(
client: &upgrade_lib::RestClient,
) -> error::Result<()> {
// let mut single_replica_volumes = Vec::new();
// The number of volumes to get per request.
let max_entries = 200;
Expand All @@ -95,7 +101,7 @@ pub async fn single_volume_replica_validation(client: &upgrade_lib::RestClient)
.volumes_api()
.get_volumes(max_entries, None, starting_token)
.await
.context(ListVolumes)?;
.context(error::ListVolumes)?;

let v = vols.into_body();
let single_rep_vol_ids: Vec<String> = v
Expand All @@ -114,22 +120,22 @@ pub async fn single_volume_replica_validation(client: &upgrade_lib::RestClient)
.join("\n");

console_logger::error(user_prompt::SINGLE_REPLICA_VOLUME_WARNING, &data);
return SingleReplicaVolumeErr.fail();
return error::SingleReplicaVolumeErr.fail();
}
Ok(())
}

/// Prompt to user and error out if any rebuild in progress.
pub async fn rebuild_in_progress_validation(client: &upgrade_lib::RestClient) -> Result<()> {
pub async fn rebuild_in_progress_validation(client: &upgrade_lib::RestClient) -> error::Result<()> {
if is_rebuild_in_progress(client).await? {
console_logger::error(user_prompt::REBUILD_WARNING, "");
return VolumeRebuildInProgress.fail();
return error::VolumeRebuildInProgress.fail();
}
Ok(())
}

/// Check for rebuild in progress.
pub async fn is_rebuild_in_progress(client: &upgrade_lib::RestClient) -> Result<bool> {
pub async fn is_rebuild_in_progress(client: &upgrade_lib::RestClient) -> error::Result<bool> {
// The number of volumes to get per request.
let max_entries = 200;
let mut starting_token = Some(0_isize);
Expand All @@ -140,7 +146,7 @@ pub async fn is_rebuild_in_progress(client: &upgrade_lib::RestClient) -> Result<
.volumes_api()
.get_volumes(max_entries, None, starting_token)
.await
.context(ListVolumes)?;
.context(error::ListVolumes)?;
let volumes = vols.into_body();
starting_token = volumes.next_token;
for volume in volumes.entries {
Expand All @@ -157,3 +163,62 @@ pub async fn is_rebuild_in_progress(client: &upgrade_lib::RestClient) -> Result<
}
Ok(false)
}

/// Upgrade path validation.
pub async fn upgrade_path_validation(ns: &str) -> error::Result<()> {
let source_version = get_source_version(ns).await?;
let mut destination_version = get_image_version_tag();

// if the tag contains develop as substring
// then treat the destination version as 0.0.0.
if destination_version.contains("develop") {
destination_version = "0.0.0".to_string();
} else {
// removes the first character v from git tag
destination_version.remove(0);
}

if source_version.eq(&destination_version) {
console_logger::error(
user_prompt::SOURCE_TARGET_VERSION_SAME,
&destination_version,
);
return error::SourceTargetVersionSame.fail();
}
let upgrade_path_is_valid = is_valid_upgrade_path(source_version, destination_version)?;
if !upgrade_path_is_valid {
console_logger::error(user_prompt::UPGRADE_PATH_NOT_VALID, "");
return error::InvalidUpgradePath.fail();
}
Ok(())
}

/// Validates the upgrade path from 'from' Version to 'to' Version for 'chart_variant' helm chart.
pub(crate) fn is_valid_upgrade_path(
source_version: String,
destination_version: String,
) -> error::Result<bool> {
let source = Version::parse(source_version.as_str()).context(error::SemverParse {
version_string: source_version,
})?;

let destination = Version::parse(destination_version.as_str()).context(error::SemverParse {
version_string: destination_version.to_string(),
})?;

let to_req = VersionReq::parse(TO_CORE_SEMVER).context(error::SemverParse {
version_string: TO_CORE_SEMVER,
})?;

let to_develop = VersionReq::parse(TO_DEVELOP_SEMVER).context(error::SemverParse {
version_string: TO_DEVELOP_SEMVER.to_string(),
})?;

if to_req.matches(&destination) || to_develop.matches(&destination) {
let from_req = VersionReq::parse(FROM_CORE_SEMVER).context(error::SemverParse {
version_string: FROM_CORE_SEMVER.to_string(),
})?;
return Ok(from_req.matches(&source));
}
Ok(false)
}
23 changes: 18 additions & 5 deletions k8s/upgrade/src/upgrade_resources/upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use crate::{
constant::{
get_image_version_tag, upgrade_event_selector, upgrade_image_concat, upgrade_name_concat,
AGENT_CORE_POD_LABEL, API_REST_LABEL_SELECTOR, API_REST_POD_LABEL, DEFAULT_IMAGE_REGISTRY,
DEFAULT_RELEASE_NAME, HELM_RELEASE_NAME_LABEL, IO_ENGINE_POD_LABEL, UPGRADE_EVENT_REASON,
UPGRADE_JOB_CLUSTERROLEBINDING_NAME_SUFFIX, UPGRADE_JOB_CLUSTERROLE_NAME_SUFFIX,
UPGRADE_JOB_IMAGE_NAME, UPGRADE_JOB_IMAGE_REPO, UPGRADE_JOB_NAME_SUFFIX,
UPGRADE_JOB_SERVICEACCOUNT_NAME_SUFFIX,
DEFAULT_RELEASE_NAME, HELM_RELEASE_NAME_LABEL, HELM_RELEASE_VERSION_LABEL,
IO_ENGINE_POD_LABEL, UPGRADE_EVENT_REASON, UPGRADE_JOB_CLUSTERROLEBINDING_NAME_SUFFIX,
UPGRADE_JOB_CLUSTERROLE_NAME_SUFFIX, UPGRADE_JOB_IMAGE_NAME, UPGRADE_JOB_IMAGE_REPO,
UPGRADE_JOB_NAME_SUFFIX, UPGRADE_JOB_SERVICEACCOUNT_NAME_SUFFIX,
},
error,
upgrade_resources::objects,
Expand Down Expand Up @@ -695,7 +695,7 @@ pub async fn get_deployment_for_rest(ns: &str) -> error::Result<Deployment> {
let deployment_list = deployment
.list(&lp)
.await
.context(error::ListDeploymantsWithLabel {
.context(error::ListDeploymentsWithLabel {
label: API_REST_LABEL_SELECTOR.to_string(),
namespace: ns.to_string(),
})?;
Expand Down Expand Up @@ -825,3 +825,16 @@ impl ImageProperties {
self.pull_policy.clone()
}
}

/// Return the installed version.
pub async fn get_source_version(ns: &str) -> error::Result<String> {
let deployment = get_deployment_for_rest(ns).await?;
let value = &deployment
.metadata
.labels
.ok_or(error::NoDeploymentPresent.build())?
.get(HELM_RELEASE_VERSION_LABEL)
.ok_or(error::NoDeploymentPresent.build())?
.to_string();
Ok(value.to_string())
}
7 changes: 7 additions & 0 deletions k8s/upgrade/src/user_prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,10 @@ pub const UPGRADE_DRY_RUN_SUMMARY: &str =
/// Information about successful start of upgrade process.
pub const UPGRADE_JOB_STARTED: &str =
"\nThe upgrade has started. You can see the recent upgrade status using 'get upgrade-status` command.";

/// Source and target version are same.
pub const SOURCE_TARGET_VERSION_SAME: &str =
"\nThe upgrade cannot proceed as target version is same as source version.\nVersion:";

/// Info about the data plane pods.
pub const UPGRADE_PATH_NOT_VALID: &str = "\nThe upgrade path is not valid.";

0 comments on commit f2cc5f0

Please sign in to comment.