1212 DependencyModel ,
1313 UpgradeGrantedEvent ,
1414)
15- from ops .model import ActiveStatus , MaintenanceStatus , WaitingStatus
15+ from ops .model import MaintenanceStatus , RelationDataContent , WaitingStatus
1616from pydantic import BaseModel
1717from tenacity import RetryError , Retrying , stop_after_attempt , wait_fixed
1818from typing_extensions import override
1919
20- from constants import SNAP_PACKAGES
20+ from constants import APP_SCOPE , MONITORING_PASSWORD_KEY , MONITORING_USER , SNAP_PACKAGES
21+ from utils import new_password
2122
2223logger = logging .getLogger (__name__ )
2324
@@ -26,6 +27,7 @@ class PostgreSQLDependencyModel(BaseModel):
2627 """PostgreSQL dependencies model."""
2728
2829 charm : DependencyModel
30+ snap : DependencyModel
2931
3032
3133def get_postgresql_dependencies_model () -> PostgreSQLDependencyModel :
@@ -42,6 +44,7 @@ def __init__(self, charm, model: BaseModel, **kwargs) -> None:
4244 """Initialize the class."""
4345 super ().__init__ (charm , model , ** kwargs )
4446 self .charm = charm
47+ self ._on_upgrade_charm_check_legacy ()
4548
4649 @override
4750 def build_upgrade_stack (self ) -> List [int ]:
@@ -77,9 +80,56 @@ def log_rollback_instructions(self) -> None:
7780 "Run `juju refresh --revision <previous-revision> postgresql` to initiate the rollback"
7881 )
7982
83+ def _on_upgrade_charm_check_legacy (self ) -> None :
84+ if not self .peer_relation or len (self .app_units ) < len (self .charm .app_units ):
85+ logger .debug ("Wait all units join the upgrade relation" )
86+ return
87+
88+ if self .state :
89+ # If state set, upgrade is supported. Just set the snap information
90+ # in the dependencies, as it's missing in the first revisions that
91+ # support upgrades.
92+ dependencies = self .peer_relation .data [self .charm .app ].get ("dependencies" )
93+ if (
94+ self .charm .unit .is_leader ()
95+ and dependencies is not None
96+ and "snap" not in json .loads (dependencies )
97+ ):
98+ fixed_dependencies = json .loads (dependencies )
99+ fixed_dependencies ["snap" ] = {
100+ "dependencies" : {},
101+ "name" : "charmed-postgresql" ,
102+ "upgrade_supported" : "^14" ,
103+ "version" : "14.9" ,
104+ }
105+ self .peer_relation .data [self .charm .app ].update (
106+ {"dependencies" : json .dumps (fixed_dependencies )}
107+ )
108+ return
109+
110+ if not self .charm .unit .is_leader ():
111+ # set ready state on non-leader units
112+ self .unit_upgrade_data .update ({"state" : "ready" })
113+ return
114+
115+ peers_state = list (filter (lambda state : state != "" , self .unit_states ))
116+
117+ if len (peers_state ) == len (self .peer_relation .units ) and (
118+ set (peers_state ) == {"ready" } or len (peers_state ) == 0
119+ ):
120+ if self .charm ._patroni .member_started :
121+ # All peers have set the state to ready
122+ self .unit_upgrade_data .update ({"state" : "ready" })
123+ self ._prepare_upgrade_from_legacy ()
124+ getattr (self .on , "upgrade_charm" ).emit ()
125+
80126 @override
81127 def _on_upgrade_granted (self , event : UpgradeGrantedEvent ) -> None :
82128 # Refresh the charmed PostgreSQL snap and restart the database.
129+ # Update the configuration.
130+ self .charm .unit .status = MaintenanceStatus ("updating configuration" )
131+ self .charm .update_config ()
132+
83133 self .charm .unit .status = MaintenanceStatus ("refreshing the snap" )
84134 self .charm ._install_snap_packages (packages = SNAP_PACKAGES , refresh = True )
85135
@@ -91,6 +141,14 @@ def _on_upgrade_granted(self, event: UpgradeGrantedEvent) -> None:
91141 self .charm ._setup_exporter ()
92142 self .charm .backup .start_stop_pgbackrest_service ()
93143
144+ try :
145+ self .charm .unit .set_workload_version (
146+ self .charm ._patroni .get_postgresql_version () or "unset"
147+ )
148+ except TypeError :
149+ # Don't fail on this, just log it.
150+ logger .warning ("Failed to get PostgreSQL version" )
151+
94152 # Wait until the database initialise.
95153 self .charm .unit .status = WaitingStatus ("waiting for database initialisation" )
96154 try :
@@ -110,7 +168,6 @@ def _on_upgrade_granted(self, event: UpgradeGrantedEvent) -> None:
110168 raise Exception ()
111169
112170 self .set_unit_completed ()
113- self .charm .unit .status = ActiveStatus ()
114171
115172 # Ensures leader gets its own relation-changed when it upgrades
116173 if self .charm .unit .is_leader ():
@@ -144,3 +201,36 @@ def pre_upgrade_check(self) -> None:
144201 "a backup is being created" ,
145202 "wait for the backup creation to finish before starting the upgrade" ,
146203 )
204+
205+ def _prepare_upgrade_from_legacy (self ) -> None :
206+ """Prepare upgrade from legacy charm without upgrade support.
207+
208+ Assumes run on leader unit only.
209+ """
210+ logger .warning ("Upgrading from unsupported version" )
211+
212+ # Populate app upgrade databag to allow upgrade procedure
213+ logger .debug ("Building upgrade stack" )
214+ upgrade_stack = self .build_upgrade_stack ()
215+ logger .debug (f"Upgrade stack: { upgrade_stack } " )
216+ self .upgrade_stack = upgrade_stack
217+ logger .debug ("Persisting dependencies to upgrade relation data..." )
218+ self .peer_relation .data [self .charm .app ].update (
219+ {"dependencies" : json .dumps (self .dependency_model .dict ())}
220+ )
221+ if self .charm .get_secret (APP_SCOPE , MONITORING_PASSWORD_KEY ) is None :
222+ self .charm .set_secret (APP_SCOPE , MONITORING_PASSWORD_KEY , new_password ())
223+ users = self .charm .postgresql .list_users ()
224+ if MONITORING_USER not in users :
225+ # Create the monitoring user.
226+ self .charm .postgresql .create_user (
227+ MONITORING_USER ,
228+ self .charm .get_secret (APP_SCOPE , MONITORING_PASSWORD_KEY ),
229+ extra_user_roles = "pg_monitor" ,
230+ )
231+ self .charm .postgresql .set_up_database ()
232+
233+ @property
234+ def unit_upgrade_data (self ) -> RelationDataContent :
235+ """Return the application upgrade data."""
236+ return self .peer_relation .data [self .charm .unit ]
0 commit comments