@@ -37,6 +37,11 @@ use omicron_common::api::external::Error;
37
37
use omicron_common:: api:: external:: IdentityMetadataCreateParams ;
38
38
use omicron_common:: api:: external:: LookupType ;
39
39
use omicron_common:: api:: external:: ResourceType ;
40
+ use omicron_common:: api:: external:: SemverVersion ;
41
+ use omicron_common:: backoff:: {
42
+ retry_notify, retry_policy_internal_service, BackoffError ,
43
+ } ;
44
+ use slog:: Logger ;
40
45
use std:: net:: Ipv6Addr ;
41
46
use std:: sync:: Arc ;
42
47
use uuid:: Uuid ;
@@ -45,6 +50,7 @@ mod address_lot;
45
50
mod certificate;
46
51
mod console_session;
47
52
mod dataset;
53
+ mod db_metadata;
48
54
mod device_auth;
49
55
mod disk;
50
56
mod dns;
@@ -131,12 +137,40 @@ pub struct DataStore {
131
137
// to compilation times; changing a query only requires incremental
132
138
// recompilation of that query's module instead of all queries on `DataStore`.
133
139
impl DataStore {
134
- pub fn new ( pool : Arc < Pool > ) -> Self {
135
- DataStore {
140
+ /// Constructs a new Datastore object.
141
+ ///
142
+ /// Only returns if the database schema is compatible with Nexus's known
143
+ /// schema version.
144
+ pub async fn new ( log : & Logger , pool : Arc < Pool > ) -> Result < Self , String > {
145
+ let datastore = DataStore {
136
146
pool,
137
147
virtual_provisioning_collection_producer :
138
148
crate :: provisioning:: Producer :: new ( ) ,
139
- }
149
+ } ;
150
+
151
+ // Keep looping until we find that the schema matches our expectation.
152
+ const EXPECTED_VERSION : SemverVersion = SemverVersion :: new ( 1 , 0 , 0 ) ;
153
+ retry_notify (
154
+ retry_policy_internal_service ( ) ,
155
+ || async {
156
+ match datastore. database_schema_version ( ) . await {
157
+ Ok ( version) => {
158
+ if version == nexus_db_model:: schema:: SCHEMA_VERSION {
159
+ return Ok ( ( ) ) ;
160
+ }
161
+ let observed = version. 0 ;
162
+ warn ! ( log, "Incompatible database schema: Saw {observed}, expected {EXPECTED_VERSION}" ) ;
163
+ }
164
+ Err ( e) => {
165
+ warn ! ( log, "Cannot read database schema version: {e}" ) ;
166
+ }
167
+ } ;
168
+ return Err ( BackoffError :: transient ( ( ) ) ) ;
169
+ } ,
170
+ |_, _| { } ,
171
+ ) . await . map_err ( |_| "Failed to read valid DB schema" . to_string ( ) ) ?;
172
+
173
+ Ok ( datastore)
140
174
}
141
175
142
176
pub fn register_producers ( & self , registry : & ProducerRegistry ) {
@@ -248,7 +282,7 @@ pub async fn datastore_test(
248
282
249
283
let cfg = db:: Config { url : db. pg_config ( ) . clone ( ) } ;
250
284
let pool = Arc :: new ( db:: Pool :: new ( & logctx. log , & cfg) ) ;
251
- let datastore = Arc :: new ( DataStore :: new ( pool) ) ;
285
+ let datastore = Arc :: new ( DataStore :: new ( & logctx . log , pool) . await . unwrap ( ) ) ;
252
286
253
287
// Create an OpContext with the credentials of "db-init" just for the
254
288
// purpose of loading the built-in users, roles, and assignments.
@@ -907,7 +941,8 @@ mod test {
907
941
let mut db = test_setup_database ( & logctx. log ) . await ;
908
942
let cfg = db:: Config { url : db. pg_config ( ) . clone ( ) } ;
909
943
let pool = db:: Pool :: new ( & logctx. log , & cfg) ;
910
- let datastore = DataStore :: new ( Arc :: new ( pool) ) ;
944
+ let datastore =
945
+ DataStore :: new ( & logctx. log , Arc :: new ( pool) ) . await . unwrap ( ) ;
911
946
912
947
let explanation = DataStore :: get_allocated_regions_query ( Uuid :: nil ( ) )
913
948
. explain_async ( datastore. pool ( ) )
@@ -956,7 +991,8 @@ mod test {
956
991
let mut db = test_setup_database ( & logctx. log ) . await ;
957
992
let cfg = db:: Config { url : db. pg_config ( ) . clone ( ) } ;
958
993
let pool = Arc :: new ( db:: Pool :: new ( & logctx. log , & cfg) ) ;
959
- let datastore = Arc :: new ( DataStore :: new ( Arc :: clone ( & pool) ) ) ;
994
+ let datastore =
995
+ Arc :: new ( DataStore :: new ( & logctx. log , pool) . await . unwrap ( ) ) ;
960
996
let opctx =
961
997
OpContext :: for_tests ( logctx. log . new ( o ! ( ) ) , datastore. clone ( ) ) ;
962
998
0 commit comments