@@ -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 ;
4350use 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} ;
5461use strum:: { EnumDiscriminants , IntoStaticStr } ;
55- use tracing:: log:: debug;
5662
5763pub const SUPERSET_CONTROLLER_NAME : & str = "supersetcluster" ;
5864pub 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
177187type 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(
714713pub 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+ }
0 commit comments