Skip to content

Commit 14f81fd

Browse files
committed
1 parent b796ab4 commit 14f81fd

10 files changed

Lines changed: 227 additions & 44 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Log aggregation added ([#326]).
88
- Deploy default and support custom affinities ([#337]).
99
- Extend cluster resources for status and cluster operation (paused, stopped) ([#348])
10+
- Cluster status conditions ([#349])
1011

1112
### Changed
1213

@@ -26,6 +27,7 @@
2627
[#337]: https://github.com/stackabletech/superset-operator/pull/337
2728
[#344]: https://github.com/stackabletech/superset-operator/pull/344
2829
[#348]: https://github.com/stackabletech/superset-operator/pull/348
30+
[#349]: https://github.com/stackabletech/superset-operator/pull/349
2931
[#350]: https://github.com/stackabletech/superset-operator/pull/350
3032

3133
## [23.1.0] - 2023-01-23

deploy/helm/superset-operator/crds/crds.yaml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1517,6 +1517,51 @@ spec:
15171517
type: object
15181518
status:
15191519
nullable: true
1520+
properties:
1521+
conditions:
1522+
items:
1523+
properties:
1524+
lastTransitionTime:
1525+
description: Last time the condition transitioned from one status to another.
1526+
format: date-time
1527+
nullable: true
1528+
type: string
1529+
lastUpdateTime:
1530+
description: The last time this condition was updated.
1531+
format: date-time
1532+
nullable: true
1533+
type: string
1534+
message:
1535+
description: A human readable message indicating details about the transition.
1536+
nullable: true
1537+
type: string
1538+
reason:
1539+
description: The reason for the condition's last transition.
1540+
nullable: true
1541+
type: string
1542+
status:
1543+
description: Status of the condition, one of True, False, Unknown.
1544+
enum:
1545+
- 'True'
1546+
- 'False'
1547+
- Unknown
1548+
type: string
1549+
type:
1550+
description: Type of deployment condition.
1551+
enum:
1552+
- Available
1553+
- Degraded
1554+
- Progressing
1555+
- ReconciliationPaused
1556+
- Stopped
1557+
type: string
1558+
required:
1559+
- status
1560+
- type
1561+
type: object
1562+
type: array
1563+
required:
1564+
- conditions
15201565
type: object
15211566
required:
15221567
- spec

deploy/helm/superset-operator/templates/roles.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ rules:
4646
resources:
4747
- statefulsets
4848
verbs:
49+
- get
4950
- create
5051
- delete
5152
- list

rust/crd/src/lib.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use stackable_operator::commons::cluster_operation::ClusterOperation;
1010
use stackable_operator::commons::product_image_selection::ProductImage;
1111
use stackable_operator::kube::ResourceExt;
1212
use stackable_operator::role_utils::RoleGroup;
13+
use stackable_operator::status::condition::{ClusterCondition, HasStatusCondition};
1314
use stackable_operator::{
1415
commons::resources::{
1516
CpuLimitsFragment, MemoryLimitsFragment, NoRuntimeLimits, NoRuntimeLimitsFragment,
@@ -431,7 +432,18 @@ impl Configuration for SupersetConfigFragment {
431432

432433
#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
433434
#[serde(rename_all = "camelCase")]
434-
pub struct SupersetClusterStatus {}
435+
pub struct SupersetClusterStatus {
436+
pub conditions: Vec<ClusterCondition>,
437+
}
438+
439+
impl HasStatusCondition for SupersetCluster {
440+
fn conditions(&self) -> Vec<ClusterCondition> {
441+
match &self.status {
442+
Some(status) => status.conditions.clone(),
443+
None => vec![],
444+
}
445+
}
446+
}
435447

436448
impl SupersetCluster {
437449
/// The name of the role-level load-balanced Kubernetes `Service`

rust/operator-binary/src/superset_controller.rs

Lines changed: 156 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,14 @@ use stackable_operator::{
3939
product_config_utils::{transform_all_roles_to_config, validate_all_roles_and_groups_config},
4040
product_logging::{self, spec::Logging},
4141
role_utils::RoleGroupRef,
42+
status::condition::{
43+
compute_conditions, operations::ClusterOperationsConditionBuilder,
44+
statefulset::StatefulSetConditionBuilder, ClusterCondition, ClusterConditionSet,
45+
ClusterConditionStatus, ClusterConditionType, ConditionBuilder,
46+
},
4247
};
48+
use stackable_superset_crd::supersetdb::SupersetDBStatus;
49+
use stackable_superset_crd::SupersetClusterStatus;
4350
use stackable_superset_crd::{
4451
supersetdb::{SupersetDB, SupersetDBStatusCondition},
4552
Container, SupersetCluster, SupersetConfig, SupersetConfigOptions, SupersetRole, APP_NAME,
@@ -52,7 +59,6 @@ use std::{
5259
time::Duration,
5360
};
5461
use strum::{EnumDiscriminants, IntoStaticStr};
55-
use tracing::log::debug;
5662

5763
pub const SUPERSET_CONTROLLER_NAME: &str = "supersetcluster";
5864
pub const DOCKER_IMAGE_BASE_NAME: &str = "superset";
@@ -172,6 +178,10 @@ pub enum Error {
172178
source: crate::product_logging::Error,
173179
cm_name: String,
174180
},
181+
#[snafu(display("failed to update status"))]
182+
ApplyStatus {
183+
source: stackable_operator::error::Error,
184+
},
175185
}
176186

177187
type Result<T, E = Error> = std::result::Result<T, E>;
@@ -189,43 +199,17 @@ pub async fn reconcile_superset(superset: Arc<SupersetCluster>, ctx: Arc<Ctx>) -
189199
let resolved_product_image: ResolvedProductImage =
190200
superset.spec.image.resolve(DOCKER_IMAGE_BASE_NAME);
191201

192-
// Ensure DB Schema exists
193-
let superset_db = SupersetDB::for_superset(&superset, &resolved_product_image)
194-
.context(CreateSupersetObjectSnafu)?;
195-
client
196-
.apply_patch(SUPERSET_CONTROLLER_NAME, &superset_db, &superset_db)
197-
.await
198-
.context(ApplySupersetDBSnafu)?;
202+
let cluster_operation_cond_builder =
203+
ClusterOperationsConditionBuilder::new(&superset.spec.cluster_operation);
199204

200-
let superset_db = client
201-
.get::<SupersetDB>(
202-
&superset.name_unchecked(),
203-
superset
204-
.namespace()
205-
.as_deref()
206-
.context(ObjectHasNoNamespaceSnafu)?,
207-
)
208-
.await
209-
.context(SupersetDBRetrievalSnafu)?;
210-
211-
if let Some(ref status) = superset_db.status {
212-
match status.condition {
213-
SupersetDBStatusCondition::Pending | SupersetDBStatusCondition::Initializing => {
214-
debug!(
215-
"Waiting for SupersetDB initialization to complete, not starting Superset yet"
216-
);
217-
return Ok(Action::await_change());
218-
}
219-
SupersetDBStatusCondition::Failed => {
220-
return SupersetDBFailedSnafu {
221-
superset_db: ObjectRef::from_obj(&superset_db),
222-
}
223-
.fail();
224-
}
225-
SupersetDBStatusCondition::Ready => (), // Continue starting Superset
226-
}
227-
} else {
228-
debug!("Waiting for SupersetDBStatus to be reported, not starting Superset yet");
205+
if wait_for_db_and_update_status(
206+
client,
207+
&superset,
208+
&resolved_product_image,
209+
&cluster_operation_cond_builder,
210+
)
211+
.await?
212+
{
229213
return Ok(Action::await_change());
230214
}
231215

@@ -300,6 +284,8 @@ pub async fn reconcile_superset(superset: Arc<SupersetCluster>, ctx: Arc<Ctx>) -
300284
None => None,
301285
};
302286

287+
let mut ss_cond_builder = StatefulSetConditionBuilder::default();
288+
303289
for (rolegroup_name, rolegroup_config) in role_node_config.iter() {
304290
let rolegroup = superset.node_rolegroup_ref(rolegroup_name);
305291

@@ -338,19 +324,32 @@ pub async fn reconcile_superset(superset: Arc<SupersetCluster>, ctx: Arc<Ctx>) -
338324
.with_context(|_| ApplyRoleGroupConfigSnafu {
339325
rolegroup: rolegroup.clone(),
340326
})?;
341-
cluster_resources
342-
.add(client, rg_statefulset)
343-
.await
344-
.with_context(|_| ApplyRoleGroupStatefulSetSnafu {
345-
rolegroup: rolegroup.clone(),
346-
})?;
327+
ss_cond_builder.add(
328+
cluster_resources
329+
.add(client, rg_statefulset.clone())
330+
.await
331+
.with_context(|_| ApplyRoleGroupStatefulSetSnafu {
332+
rolegroup: rolegroup.clone(),
333+
})?,
334+
);
347335
}
348336

349337
cluster_resources
350338
.delete_orphaned_resources(client)
351339
.await
352340
.context(DeleteOrphanedResourcesSnafu)?;
353341

342+
let status = SupersetClusterStatus {
343+
conditions: compute_conditions(
344+
superset.as_ref(),
345+
&[&ss_cond_builder, &cluster_operation_cond_builder],
346+
),
347+
};
348+
client
349+
.apply_patch_status(OPERATOR_NAME, &*superset, &status)
350+
.await
351+
.context(ApplyStatusSnafu)?;
352+
354353
Ok(Action::await_change())
355354
}
356355

@@ -714,3 +713,117 @@ fn add_authentication_volumes_and_volume_mounts(
714713
pub fn error_policy(_obj: Arc<SupersetCluster>, _error: &Error, _ctx: Arc<Ctx>) -> Action {
715714
Action::requeue(Duration::from_secs(5))
716715
}
716+
717+
/// Return true if the controller should wait for the DB to be set up.
718+
///
719+
/// As a side-effect, the Superset cluster status is updated as long as the controller waits
720+
/// for the DB to come up.
721+
///
722+
/// Having the DB set up by a Job managed by a different controller has it's own
723+
/// set of problems as described here: <https://github.com/stackabletech/superset-operator/issues/351>.
724+
/// The Airflow operator uses the same pattern as implemented here for setting up the DB.
725+
///
726+
/// When the ticket above is implemented, this function will most likely be removed completely.
727+
async fn wait_for_db_and_update_status(
728+
client: &stackable_operator::client::Client,
729+
superset: &SupersetCluster,
730+
resolved_product_image: &ResolvedProductImage,
731+
cluster_operation_condition_builder: &ClusterOperationsConditionBuilder<'_>,
732+
) -> Result<bool> {
733+
// Ensure DB Schema exists
734+
let superset_db = SupersetDB::for_superset(superset, resolved_product_image)
735+
.context(CreateSupersetObjectSnafu)?;
736+
client
737+
.apply_patch(SUPERSET_CONTROLLER_NAME, &superset_db, &superset_db)
738+
.await
739+
.context(ApplySupersetDBSnafu)?;
740+
741+
let superset_db = client
742+
.get::<SupersetDB>(
743+
&superset.name_unchecked(),
744+
superset
745+
.namespace()
746+
.as_deref()
747+
.context(ObjectHasNoNamespaceSnafu)?,
748+
)
749+
.await
750+
.context(SupersetDBRetrievalSnafu)?;
751+
752+
tracing::debug!("{}", format!("Checking status: {:#?}", superset_db.status));
753+
754+
// Update the Superset cluster status, only if the controller needs to wait.
755+
// This avoids updating the status twice per reconcile call. when the DB
756+
// has a ready condition.
757+
let db_cond_builder = DbConditionBuilder(superset_db.status);
758+
if bool::from(&db_cond_builder) {
759+
let status = SupersetClusterStatus {
760+
conditions: compute_conditions(
761+
superset,
762+
&[&db_cond_builder, cluster_operation_condition_builder],
763+
),
764+
};
765+
766+
client
767+
.apply_patch_status(OPERATOR_NAME, superset, &status)
768+
.await
769+
.context(ApplyStatusSnafu)?;
770+
}
771+
772+
Ok(bool::from(&db_cond_builder))
773+
}
774+
775+
struct DbConditionBuilder(Option<SupersetDBStatus>);
776+
impl ConditionBuilder for DbConditionBuilder {
777+
fn build_conditions(&self) -> ClusterConditionSet {
778+
let (status, message) = if let Some(ref status) = self.0 {
779+
match status.condition {
780+
SupersetDBStatusCondition::Pending | SupersetDBStatusCondition::Initializing => (
781+
ClusterConditionStatus::False,
782+
"Waiting for SupersetDB initialization to complete",
783+
),
784+
SupersetDBStatusCondition::Failed => (
785+
ClusterConditionStatus::False,
786+
"Superset database initialization failed.",
787+
),
788+
SupersetDBStatusCondition::Ready => (
789+
ClusterConditionStatus::True,
790+
"Superset database initialization ready.",
791+
),
792+
}
793+
} else {
794+
(
795+
ClusterConditionStatus::Unknown,
796+
"Waiting for Superset database initialization to start.",
797+
)
798+
};
799+
800+
let cond = ClusterCondition {
801+
reason: None,
802+
message: Some(String::from(message)),
803+
status,
804+
type_: ClusterConditionType::Available,
805+
last_transition_time: None,
806+
last_update_time: None,
807+
};
808+
809+
vec![cond].into()
810+
}
811+
}
812+
813+
/// Evaluates to true if the DB is not ready yet (the controller needs to wait).
814+
/// Otherwise false.
815+
impl From<&DbConditionBuilder> for bool {
816+
fn from(cond_builder: &DbConditionBuilder) -> bool {
817+
if let Some(ref status) = cond_builder.0 {
818+
match status.condition {
819+
SupersetDBStatusCondition::Pending | SupersetDBStatusCondition::Initializing => {
820+
true
821+
}
822+
SupersetDBStatusCondition::Failed => true,
823+
SupersetDBStatusCondition::Ready => false,
824+
}
825+
} else {
826+
true
827+
}
828+
}
829+
}

tests/templates/kuttl/cluster-operation/02-assert.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ kind: TestAssert
44
metadata:
55
name: install-superset
66
timeout: 300
7+
commands:
8+
- script: kubectl -n $NAMESPACE wait --for=condition=available=true supersetclusters.superset.stackable.tech/superset --timeout 301s
79
---
810
apiVersion: apps/v1
911
kind: StatefulSet

tests/templates/kuttl/cluster-operation/03-assert.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ kind: TestAssert
44
metadata:
55
name: install-superset
66
timeout: 300
7+
commands:
8+
- script: kubectl -n $NAMESPACE wait --for=condition=reconciliationPaused=true supersetclusters.superset.stackable.tech/superset --timeout 301s
79
---
810
apiVersion: apps/v1
911
kind: StatefulSet

tests/templates/kuttl/cluster-operation/04-assert.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ kind: TestAssert
44
metadata:
55
name: install-superset
66
timeout: 300
7+
commands:
8+
- script: kubectl -n $NAMESPACE wait --for=condition=stopped=true supersetclusters.superset.stackable.tech/superset --timeout 301s
79
---
810
apiVersion: apps/v1
911
kind: StatefulSet

tests/templates/kuttl/cluster-operation/05-assert.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ kind: TestAssert
44
metadata:
55
name: install-superset
66
timeout: 300
7+
commands:
8+
- script: kubectl -n $NAMESPACE wait --for=condition=available=true supersetclusters.superset.stackable.tech/superset --timeout 301s
79
---
810
apiVersion: apps/v1
911
kind: StatefulSet

0 commit comments

Comments
 (0)