1mod builtin_schema_migration;
13
14use std::collections::{BTreeMap, BTreeSet};
15use std::num::NonZeroU32;
16use std::sync::Arc;
17use std::sync::atomic::AtomicU64;
18use std::time::{Duration, Instant};
19
20use futures::future::{BoxFuture, FutureExt};
21use itertools::{Either, Itertools};
22use mz_adapter_types::bootstrap_builtin_cluster_config::BootstrapBuiltinClusterConfig;
23use mz_adapter_types::dyncfgs::ENABLE_EXPRESSION_CACHE;
24use mz_audit_log::{
25 CreateOrDropClusterReplicaReasonV1, EventDetails, EventType, ObjectType, VersionedEvent,
26};
27use mz_auth::hash::scram256_hash;
28use mz_catalog::builtin::{
29 BUILTIN_CLUSTERS, BUILTIN_PREFIXES, BUILTIN_ROLES, BUILTINS, Builtin, Fingerprint,
30 MZ_CATALOG_RAW, RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL,
31};
32use mz_catalog::config::StateConfig;
33use mz_catalog::durable::objects::{
34 SystemObjectDescription, SystemObjectMapping, SystemObjectUniqueIdentifier,
35};
36use mz_catalog::durable::{
37 ClusterReplica, ClusterVariant, ClusterVariantManaged, ReplicaConfig, ReplicaLocation,
38 Transaction, managed_cluster_replica_name,
39};
40use mz_catalog::expr_cache::{
41 ExpressionCacheConfig, ExpressionCacheHandle, GlobalExpressions, LocalExpressions,
42 latest_item_version,
43};
44use mz_catalog::memory::error::{Error, ErrorKind};
45use mz_catalog::memory::objects::{
46 CommentsMap, DefaultPrivileges, RoleAuth, StateUpdate, StateUpdateKind,
47};
48use mz_controller::clusters::ReplicaLogging;
49use mz_controller_types::ClusterId;
50use mz_ore::cast::usize_to_u64;
51use mz_ore::collections::HashSet;
52use mz_ore::now::{SYSTEM_TIME, to_datetime};
53use mz_ore::{instrument, soft_assert_no_log};
54use mz_repr::adt::mz_acl_item::PrivilegeMap;
55use mz_repr::namespaces::is_unstable_schema;
56use mz_repr::{CatalogItemId, Diff, GlobalId, RelationVersion, Timestamp};
57use mz_sql::catalog::{CatalogError as SqlCatalogError, CatalogItemType, RoleMembership, RoleVars};
58use mz_sql::func::OP_IMPLS;
59use mz_sql::names::CommentObjectId;
60use mz_sql::rbac;
61use mz_sql::session::user::{MZ_SYSTEM_ROLE_ID, SYSTEM_USER};
62use mz_sql::session::vars::{SessionVars, SystemVars, VarError, VarInput};
63use mz_storage_client::controller::{StorageMetadata, StorageTxn};
64use mz_storage_client::storage_collections::StorageCollections;
65use semver::Version;
66use tracing::{Instrument, info, warn};
67use uuid::Uuid;
68
69use crate::AdapterError;
71use crate::catalog::migrate::{self, get_migration_version, set_migration_version};
72use crate::catalog::state::LocalExpressionCache;
73use crate::catalog::{BuiltinTableUpdate, Catalog, CatalogState, Config, is_reserved_name};
74
75pub struct InitializeStateResult {
76 pub state: CatalogState,
78 pub migrated_storage_collections_0dt: BTreeSet<CatalogItemId>,
87 pub new_builtin_collections: BTreeSet<GlobalId>,
89 pub builtin_table_updates: Vec<BuiltinTableUpdate>,
91 pub last_seen_version: Option<Version>,
98 pub expr_cache_handle: Option<ExpressionCacheHandle>,
100 pub cached_global_exprs: BTreeMap<GlobalId, GlobalExpressions>,
102 pub uncached_local_exprs: BTreeMap<GlobalId, LocalExpressions>,
104}
105
106pub struct OpenCatalogResult {
107 pub catalog: Catalog,
109 pub last_seen_version: Option<Version>,
111 pub migrated_storage_collections_0dt: BTreeSet<CatalogItemId>,
114 pub new_builtin_collections: BTreeSet<GlobalId>,
116 pub builtin_table_updates: Vec<BuiltinTableUpdate>,
118 pub cached_global_exprs: BTreeMap<GlobalId, GlobalExpressions>,
120 pub uncached_local_exprs: BTreeMap<GlobalId, LocalExpressions>,
122}
123
124impl Catalog {
125 pub async fn initialize_state<'a>(
129 config: StateConfig,
130 storage: &'a mut Box<dyn mz_catalog::durable::DurableCatalogState>,
131 ) -> Result<InitializeStateResult, AdapterError> {
132 for builtin_role in BUILTIN_ROLES {
133 assert!(
134 is_reserved_name(builtin_role.name),
135 "builtin role {builtin_role:?} must start with one of the following prefixes {}",
136 BUILTIN_PREFIXES.join(", ")
137 );
138 }
139 for builtin_cluster in BUILTIN_CLUSTERS {
140 assert!(
141 is_reserved_name(builtin_cluster.name),
142 "builtin cluster {builtin_cluster:?} must start with one of the following prefixes {}",
143 BUILTIN_PREFIXES.join(", ")
144 );
145 }
146
147 let mut system_configuration = SystemVars::new().set_unsafe(config.unsafe_mode);
148 if config.all_features {
149 system_configuration.enable_all_feature_flags_by_default();
150 }
151
152 let mut state = CatalogState {
153 database_by_name: imbl::OrdMap::new(),
154 database_by_id: imbl::OrdMap::new(),
155 entry_by_id: imbl::OrdMap::new(),
156 entry_by_global_id: imbl::OrdMap::new(),
157 notices_by_dep_id: imbl::OrdMap::new(),
158 ambient_schemas_by_name: imbl::OrdMap::new(),
159 ambient_schemas_by_id: imbl::OrdMap::new(),
160 clusters_by_name: imbl::OrdMap::new(),
161 clusters_by_id: imbl::OrdMap::new(),
162 roles_by_name: imbl::OrdMap::new(),
163 roles_by_id: imbl::OrdMap::new(),
164 network_policies_by_id: imbl::OrdMap::new(),
165 role_auth_by_id: imbl::OrdMap::new(),
166 network_policies_by_name: imbl::OrdMap::new(),
167 system_configuration: Arc::new(system_configuration),
168 scoped_system_parameters: Default::default(),
169 default_privileges: Arc::new(DefaultPrivileges::default()),
170 system_privileges: Arc::new(PrivilegeMap::default()),
171 comments: Arc::new(CommentsMap::default()),
172 source_references: imbl::OrdMap::new(),
173 storage_metadata: Arc::new(StorageMetadata::default()),
174 temporary_namespaces: Default::default(),
175 mock_authentication_nonce: Default::default(),
176 config: mz_sql::catalog::CatalogConfig {
177 start_time: to_datetime((config.now)()),
178 start_instant: Instant::now(),
179 nonce: rand::random(),
180 environment_id: config.environment_id,
181 session_id: Uuid::new_v4(),
182 build_info: config.build_info,
183 now: config.now.clone(),
184 connection_context: config.connection_context,
185 aws_account_id: config
186 .aws_principal_context
187 .as_ref()
188 .map(|c| c.aws_account_id.clone()),
189 helm_chart_version: config.helm_chart_version,
190 },
191 cluster_replica_sizes: config.cluster_replica_sizes,
192 availability_zones: config.availability_zones,
193 egress_addresses: config.egress_addresses,
194 aws_principal_context: config.aws_principal_context,
195 aws_privatelink_availability_zones: config.aws_privatelink_availability_zones,
196 http_host_name: config.http_host_name,
197 license_key: config.license_key,
198 };
199
200 let deploy_generation = storage.get_deployment_generation().await?;
201
202 let mut updates: Vec<_> = storage.sync_to_current_updates().await?;
203 assert!(!updates.is_empty(), "initial catalog snapshot is missing");
204 let mut txn = storage.transaction().await?;
205
206 let new_builtin_collections = {
208 migrate::durable_migrate(
209 &mut txn,
210 state.config.environment_id.organization_id(),
211 config.boot_ts,
212 )?;
213 if let Some(remote_system_parameters) = config.remote_system_parameters {
216 for (name, value) in remote_system_parameters {
217 txn.upsert_system_config(&name, value)?;
218 }
219 txn.set_system_config_synced_once()?;
220 }
221 let new_builtin_collections = add_new_remove_old_builtin_items_migration(&mut txn)?;
223 let builtin_bootstrap_cluster_config_map = BuiltinBootstrapClusterConfigMap {
224 system_cluster: config.builtin_system_cluster_config,
225 catalog_server_cluster: config.builtin_catalog_server_cluster_config,
226 probe_cluster: config.builtin_probe_cluster_config,
227 support_cluster: config.builtin_support_cluster_config,
228 analytics_cluster: config.builtin_analytics_cluster_config,
229 };
230 add_new_remove_old_builtin_clusters_migration(
231 &mut txn,
232 &builtin_bootstrap_cluster_config_map,
233 config.boot_ts,
234 )?;
235 add_new_remove_old_builtin_introspection_source_migration(&mut txn)?;
236 reconcile_builtin_cluster_replicas(
237 &mut txn,
238 &builtin_bootstrap_cluster_config_map,
239 config.boot_ts,
240 )?;
241 add_new_remove_old_builtin_roles_migration(&mut txn)?;
242 remove_invalid_config_param_role_defaults_migration(&mut txn)?;
243 remove_pending_cluster_replicas_migration(&mut txn, config.boot_ts)?;
244
245 new_builtin_collections
246 };
247
248 let op_updates = txn.get_and_commit_op_updates();
249 updates.extend(op_updates);
250
251 let mut builtin_table_updates = Vec::new();
252
253 {
255 for (name, value) in config.system_parameter_defaults {
258 match state.set_system_configuration_default(&name, VarInput::Flat(&value)) {
259 Ok(_) => (),
260 Err(Error {
261 kind: ErrorKind::VarError(VarError::UnknownParameter(name)),
262 }) => {
263 warn!(%name, "cannot load unknown system parameter from catalog storage to set default parameter");
264 }
265 Err(e) => return Err(e.into()),
266 };
267 }
268 }
269
270 let mut updates = into_consolidatable_updates_startup(updates, config.boot_ts);
273 differential_dataflow::consolidation::consolidate_updates(&mut updates);
274 soft_assert_no_log!(
275 updates.iter().all(|(_, _, diff)| *diff == Diff::ONE),
276 "consolidated updates should be positive during startup: {updates:?}"
277 );
278
279 let mut pre_item_updates = Vec::new();
280 let mut system_item_updates = Vec::new();
281 let mut item_updates = Vec::new();
282 let mut post_item_updates = Vec::new();
283 let mut audit_log_updates = Vec::new();
284 for (kind, ts, diff) in updates {
285 match kind {
286 StateUpdateKind::Role(_)
287 | StateUpdateKind::RoleAuth(_)
288 | StateUpdateKind::Database(_)
289 | StateUpdateKind::Schema(_)
290 | StateUpdateKind::DefaultPrivilege(_)
291 | StateUpdateKind::SystemPrivilege(_)
292 | StateUpdateKind::SystemConfiguration(_)
293 | StateUpdateKind::ClusterSystemConfiguration(_)
294 | StateUpdateKind::ReplicaSystemConfiguration(_)
295 | StateUpdateKind::Cluster(_)
296 | StateUpdateKind::NetworkPolicy(_)
297 | StateUpdateKind::ClusterReplica(_) => pre_item_updates.push(StateUpdate {
298 kind,
299 ts,
300 diff: diff.try_into().expect("valid diff"),
301 }),
302 StateUpdateKind::IntrospectionSourceIndex(_)
303 | StateUpdateKind::SystemObjectMapping(_) => {
304 system_item_updates.push(StateUpdate {
305 kind,
306 ts,
307 diff: diff.try_into().expect("valid diff"),
308 })
309 }
310 StateUpdateKind::Item(_) => item_updates.push(StateUpdate {
311 kind,
312 ts,
313 diff: diff.try_into().expect("valid diff"),
314 }),
315 StateUpdateKind::Comment(_)
316 | StateUpdateKind::StorageCollectionMetadata(_)
317 | StateUpdateKind::SourceReferences(_)
318 | StateUpdateKind::UnfinalizedShard(_) => {
319 post_item_updates.push((kind, ts, diff));
320 }
321 StateUpdateKind::AuditLog(_) => {
322 audit_log_updates.push(StateUpdate {
323 kind,
324 ts,
325 diff: diff.try_into().expect("valid diff"),
326 });
327 }
328 }
329 }
330
331 let (builtin_table_update, _catalog_updates) = state
332 .apply_updates(pre_item_updates, &mut LocalExpressionCache::Closed)
333 .await;
334 builtin_table_updates.extend(builtin_table_update);
335
336 state.system_config().sync_dyncfgs();
346
347 {
351 if let Some(password) = config.external_login_password_mz_system {
352 let role_auth = RoleAuth {
353 role_id: MZ_SYSTEM_ROLE_ID,
354 password_hash: Some(
357 scram256_hash(&password, &NonZeroU32::new(600_000).expect("known valid"))
358 .map_err(|_| {
359 AdapterError::Internal("Failed to hash mz_system password.".to_owned())
360 })?,
361 ),
362 updated_at: SYSTEM_TIME(),
363 };
364 state
365 .role_auth_by_id
366 .insert(MZ_SYSTEM_ROLE_ID, role_auth.clone());
367 let builtin_table_update = state.generate_builtin_table_update(
368 mz_catalog::memory::objects::StateUpdateKind::RoleAuth(role_auth.into()),
369 mz_catalog::memory::objects::StateDiff::Addition,
370 );
371 builtin_table_updates.extend(builtin_table_update);
372 }
373 }
374
375 let expr_cache_start = Instant::now();
376 info!("startup: coordinator init: catalog open: expr cache open beginning");
377 let enable_expr_cache_dyncfg = ENABLE_EXPRESSION_CACHE.get(state.system_config().dyncfgs());
380 let expr_cache_enabled = config
381 .enable_expression_cache_override
382 .unwrap_or(enable_expr_cache_dyncfg);
383 let (expr_cache_handle, cached_local_exprs, cached_global_exprs) = if expr_cache_enabled {
384 info!(
385 ?config.enable_expression_cache_override,
386 ?enable_expr_cache_dyncfg,
387 "using expression cache for startup"
388 );
389 let current_items = txn
390 .get_items()
391 .flat_map(|item| {
392 let item_version = latest_item_version(&item.extra_versions);
393 std::iter::once(item.global_id)
394 .chain(item.extra_versions.into_values())
395 .map(move |gid| (gid, item_version))
396 })
397 .chain(
398 txn.get_system_object_mappings()
399 .map(|som| (som.unique_identifier.global_id, RelationVersion::root())),
400 )
401 .collect();
402 let dyncfgs = config.persist_client.dyncfgs().clone();
403 let build_version = if config.build_info.is_dev() {
404 config
407 .build_info
408 .semver_version_build()
409 .expect("build ID is not available on your platform!")
410 } else {
411 config.build_info.semver_version()
412 };
413 let expr_cache_config = ExpressionCacheConfig {
414 build_version,
415 shard_id: txn
416 .get_expression_cache_shard()
417 .expect("expression cache shard should exist for opened catalogs"),
418 persist: config.persist_client,
419 current_items,
420 remove_prior_versions: !config.read_only,
421 compact_shard: config.read_only,
422 dyncfgs,
423 };
424 let (expr_cache_handle, cached_local_exprs, cached_global_exprs) =
425 ExpressionCacheHandle::spawn_expression_cache(expr_cache_config).await;
426 (
427 Some(expr_cache_handle),
428 cached_local_exprs,
429 cached_global_exprs,
430 )
431 } else {
432 (None, BTreeMap::new(), BTreeMap::new())
433 };
434 let mut local_expr_cache = LocalExpressionCache::new(cached_local_exprs);
435 info!(
436 "startup: coordinator init: catalog open: expr cache open complete in {:?}",
437 expr_cache_start.elapsed()
438 );
439
440 let (builtin_table_update, _catalog_updates) = state
446 .apply_updates(system_item_updates, &mut local_expr_cache)
447 .await;
448 builtin_table_updates.extend(builtin_table_update);
449
450 let last_seen_version = get_migration_version(&txn);
451
452 let mz_authentication_mock_nonce =
453 txn.get_authentication_mock_nonce().ok_or_else(|| {
454 Error::new(ErrorKind::SettingError("authentication nonce".to_string()))
455 })?;
456
457 state.mock_authentication_nonce = Some(mz_authentication_mock_nonce);
458
459 let (builtin_table_update, _catalog_updates) = if !config.skip_migrations {
461 let migrate_result = migrate::migrate(
462 &mut state,
463 &mut txn,
464 &mut local_expr_cache,
465 item_updates,
466 config.now,
467 config.boot_ts,
468 )
469 .await
470 .map_err(|e| {
471 Error::new(ErrorKind::FailedCatalogMigration {
472 last_seen_version: last_seen_version
473 .as_ref()
474 .map_or_else(|| "new".to_string(), |v| v.to_string()),
475 this_version: config.build_info.version,
476 cause: e.to_string(),
477 })
478 })?;
479 if !migrate_result.post_item_updates.is_empty() {
480 post_item_updates.extend(migrate_result.post_item_updates);
483 if let Some(max_ts) = post_item_updates.iter().map(|(_, ts, _)| ts).max().cloned() {
485 for (_, ts, _) in &mut post_item_updates {
486 *ts = max_ts;
487 }
488 }
489 differential_dataflow::consolidation::consolidate_updates(&mut post_item_updates);
490 }
491
492 (
493 migrate_result.builtin_table_updates,
494 migrate_result.catalog_updates,
495 )
496 } else {
497 state
498 .apply_updates(item_updates, &mut local_expr_cache)
499 .await
500 };
501 builtin_table_updates.extend(builtin_table_update);
502
503 let post_item_updates = post_item_updates
504 .into_iter()
505 .map(|(kind, ts, diff)| StateUpdate {
506 kind,
507 ts,
508 diff: diff.try_into().expect("valid diff"),
509 })
510 .collect();
511 let (builtin_table_update, _catalog_updates) = state
512 .apply_updates(post_item_updates, &mut local_expr_cache)
513 .await;
514 builtin_table_updates.extend(builtin_table_update);
515
516 for audit_log_update in audit_log_updates {
520 builtin_table_updates.extend(
521 state.generate_builtin_table_update(audit_log_update.kind, audit_log_update.diff),
522 );
523 }
524
525 let schema_migration_result = builtin_schema_migration::run(
527 config.build_info,
528 deploy_generation,
529 &mut txn,
530 config.builtin_item_migration_config,
531 )
532 .await?;
533
534 let state_updates = txn.get_and_commit_op_updates();
535
536 let (table_updates, _catalog_updates) = state
542 .apply_updates(state_updates, &mut local_expr_cache)
543 .await;
544 builtin_table_updates.extend(table_updates);
545 let builtin_table_updates = state.resolve_builtin_table_updates(builtin_table_updates);
546
547 set_migration_version(&mut txn, config.build_info.semver_version())?;
549
550 txn.commit(config.boot_ts).await?;
551
552 schema_migration_result.cleanup_action.await;
554
555 Ok(InitializeStateResult {
556 state,
557 migrated_storage_collections_0dt: schema_migration_result.replaced_items,
558 new_builtin_collections: new_builtin_collections.into_iter().collect(),
559 builtin_table_updates,
560 last_seen_version,
561 expr_cache_handle,
562 cached_global_exprs,
563 uncached_local_exprs: local_expr_cache.into_uncached_exprs(),
564 })
565 }
566
567 #[instrument(name = "catalog::open")]
578 pub fn open(config: Config<'_>) -> BoxFuture<'static, Result<OpenCatalogResult, AdapterError>> {
579 async move {
580 let mut storage = config.storage;
581
582 let InitializeStateResult {
583 state,
584 migrated_storage_collections_0dt,
585 new_builtin_collections,
586 mut builtin_table_updates,
587 last_seen_version,
588 expr_cache_handle,
589 cached_global_exprs,
590 uncached_local_exprs,
591 } =
592 Self::initialize_state(config.state, &mut storage)
596 .instrument(tracing::info_span!("catalog::initialize_state"))
597 .boxed()
598 .await?;
599
600 let catalog = Catalog {
601 state,
602 expr_cache_handle,
603 transient_revision: 1,
604 shared_transient_revision: Arc::new(AtomicU64::new(1)),
605 storage: Arc::new(tokio::sync::Mutex::new(storage)),
606 };
607
608 for (op, func) in OP_IMPLS.iter() {
611 match func {
612 mz_sql::func::Func::Scalar(impls) => {
613 for imp in impls {
614 builtin_table_updates.push(catalog.state.resolve_builtin_table_update(
615 catalog.state.pack_op_update(op, imp.details(), Diff::ONE),
616 ));
617 }
618 }
619 _ => unreachable!("all operators must be scalar functions"),
620 }
621 }
622
623 for ip in &catalog.state.egress_addresses {
624 builtin_table_updates.push(
625 catalog
626 .state
627 .resolve_builtin_table_update(catalog.state.pack_egress_ip_update(ip)?),
628 );
629 }
630
631 if !catalog.state.license_key.id.is_empty() {
632 builtin_table_updates.push(
633 catalog.state.resolve_builtin_table_update(
634 catalog
635 .state
636 .pack_license_key_update(&catalog.state.license_key)?,
637 ),
638 );
639 }
640
641 catalog.storage().await.mark_bootstrap_complete().await;
642
643 Ok(OpenCatalogResult {
644 catalog,
645 last_seen_version,
646 migrated_storage_collections_0dt,
647 new_builtin_collections,
648 builtin_table_updates,
649 cached_global_exprs,
650 uncached_local_exprs,
651 })
652 }
653 .instrument(tracing::info_span!("catalog::open"))
654 .boxed()
655 }
656
657 async fn initialize_storage_state(
664 &mut self,
665 storage_collections: &Arc<dyn StorageCollections + Send + Sync>,
666 ) -> Result<(), mz_catalog::durable::CatalogError> {
667 let collections = self
668 .entries()
669 .filter(|entry| entry.item().is_storage_collection())
670 .flat_map(|entry| entry.global_ids())
671 .collect();
672
673 let mut state = self.state.clone();
676
677 let mut storage = self.storage().await;
678 let shard_id = storage.shard_id();
679 let mut txn = storage.transaction().await?;
680
681 let item_id = self.resolve_builtin_storage_collection(&MZ_CATALOG_RAW);
684 let global_id = self.get_entry(&item_id).latest_global_id();
685 match txn.get_collection_metadata().get(&global_id) {
686 None => {
687 txn.insert_collection_metadata([(global_id, shard_id)].into())
688 .map_err(mz_catalog::durable::DurableCatalogError::from)?;
689 }
690 Some(id) => assert_eq!(*id, shard_id),
691 }
692
693 storage_collections
694 .initialize_state(&mut txn, collections)
695 .await
696 .map_err(mz_catalog::durable::DurableCatalogError::from)?;
697
698 let updates = txn.get_and_commit_op_updates();
699 let (builtin_updates, catalog_updates) = state
700 .apply_updates(updates, &mut LocalExpressionCache::Closed)
701 .await;
702 assert!(
703 builtin_updates.is_empty(),
704 "storage is not allowed to generate catalog changes that would cause changes to builtin tables"
705 );
706 assert!(
707 catalog_updates.is_empty(),
708 "storage is not allowed to generate catalog changes that would change the catalog or controller state"
709 );
710 let commit_ts = txn.upper();
711 txn.commit(commit_ts).await?;
712 drop(storage);
713
714 self.state = state;
716 Ok(())
717 }
718
719 pub async fn initialize_controller(
722 &mut self,
723 config: mz_controller::ControllerConfig,
724 envd_epoch: core::num::NonZeroI64,
725 read_only: bool,
726 ) -> Result<mz_controller::Controller, mz_catalog::durable::CatalogError> {
727 let controller_start = Instant::now();
728 info!("startup: controller init: beginning");
729
730 let controller = {
731 let mut storage = self.storage().await;
732 let mut tx = storage.transaction().await?;
733 mz_controller::prepare_initialization(&mut tx)
734 .map_err(mz_catalog::durable::DurableCatalogError::from)?;
735 let updates = tx.get_and_commit_op_updates();
736 assert!(
737 updates.is_empty(),
738 "initializing controller should not produce updates: {updates:?}"
739 );
740 let commit_ts = tx.upper();
741 tx.commit(commit_ts).await?;
742
743 let read_only_tx = storage.transaction().await?;
744
745 mz_controller::Controller::new(config, envd_epoch, read_only, &read_only_tx).await
746 };
747
748 self.initialize_storage_state(&controller.storage_collections)
749 .await?;
750
751 info!(
752 "startup: controller init: complete in {:?}",
753 controller_start.elapsed()
754 );
755
756 Ok(controller)
757 }
758
759 pub async fn expire(self) {
761 if let Some(storage) = Arc::into_inner(self.storage) {
764 let storage = storage.into_inner();
765 storage.expire().await;
766 }
767 }
768}
769
770impl CatalogState {
771 fn set_system_configuration_default(
773 &mut self,
774 name: &str,
775 value: VarInput,
776 ) -> Result<(), Error> {
777 Ok(Arc::make_mut(&mut self.system_configuration).set_default(name, value)?)
778 }
779}
780
781fn add_new_remove_old_builtin_items_migration(
785 txn: &mut mz_catalog::durable::Transaction<'_>,
786) -> Result<Vec<GlobalId>, mz_catalog::durable::CatalogError> {
787 let mut new_builtin_mappings = Vec::new();
788 let mut builtin_descs = HashSet::new();
790
791 let mut builtins = Vec::new();
794 for builtin in BUILTINS::iter() {
795 let desc = SystemObjectDescription {
796 schema_name: builtin.schema().to_string(),
797 object_type: builtin.catalog_item_type(),
798 object_name: builtin.name().to_string(),
799 };
800 if !builtin_descs.insert(desc.clone()) {
802 panic!(
803 "duplicate builtin description: {:?}, {:?}",
804 SystemObjectDescription {
805 schema_name: builtin.schema().to_string(),
806 object_type: builtin.catalog_item_type(),
807 object_name: builtin.name().to_string(),
808 },
809 builtin
810 );
811 }
812 builtins.push((desc, builtin));
813 }
814
815 let mut system_object_mappings: BTreeMap<_, _> = txn
816 .get_system_object_mappings()
817 .map(|system_object_mapping| {
818 (
819 system_object_mapping.description.clone(),
820 system_object_mapping,
821 )
822 })
823 .collect();
824
825 let (existing_builtins, new_builtins): (Vec<_>, Vec<_>) =
826 builtins.into_iter().partition_map(|(desc, builtin)| {
827 let fingerprint = match builtin.runtime_alterable() {
828 false => builtin.fingerprint(),
829 true => RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL.into(),
830 };
831 match system_object_mappings.remove(&desc) {
832 Some(system_object_mapping) => {
833 Either::Left((builtin, system_object_mapping, fingerprint))
834 }
835 None => Either::Right((builtin, fingerprint)),
836 }
837 });
838 let new_builtin_ids = txn.allocate_system_item_ids(usize_to_u64(new_builtins.len()))?;
839 let new_builtins: Vec<_> = new_builtins
840 .into_iter()
841 .zip_eq(new_builtin_ids.clone())
842 .collect();
843
844 for ((builtin, fingerprint), (catalog_id, global_id)) in new_builtins.iter().cloned() {
846 new_builtin_mappings.push(SystemObjectMapping {
847 description: SystemObjectDescription {
848 schema_name: builtin.schema().to_string(),
849 object_type: builtin.catalog_item_type(),
850 object_name: builtin.name().to_string(),
851 },
852 unique_identifier: SystemObjectUniqueIdentifier {
853 catalog_id,
854 global_id,
855 fingerprint,
856 },
857 });
858
859 let handled_runtime_alterable = match builtin {
865 Builtin::Connection(c) if c.runtime_alterable => {
866 let mut acl_items = vec![rbac::owner_privilege(
867 mz_sql::catalog::ObjectType::Connection,
868 c.owner_id.clone(),
869 )];
870 acl_items.extend_from_slice(c.access);
871 let versions = BTreeMap::new();
873
874 txn.insert_item(
875 catalog_id,
876 c.oid,
877 global_id,
878 mz_catalog::durable::initialize::resolve_system_schema(c.schema).id,
879 c.name,
880 c.sql.into(),
881 *c.owner_id,
882 acl_items,
883 versions,
884 None,
885 )?;
886 true
887 }
888 _ => false,
889 };
890 assert_eq!(
891 builtin.runtime_alterable(),
892 handled_runtime_alterable,
893 "runtime alterable object was not handled by migration",
894 );
895 }
896 txn.set_system_object_mappings(new_builtin_mappings)?;
897
898 let builtins_with_catalog_ids = existing_builtins
900 .iter()
901 .map(|(b, m, _)| (*b, m.unique_identifier.catalog_id))
902 .chain(
903 new_builtins
904 .into_iter()
905 .map(|((b, _), (catalog_id, _))| (b, catalog_id)),
906 );
907
908 for (builtin, id) in builtins_with_catalog_ids {
909 let (comment_id, desc, comments) = match builtin {
910 Builtin::Source(s) => (CommentObjectId::Source(id), &s.desc, &s.column_comments),
911 Builtin::View(v) => (CommentObjectId::View(id), &v.desc, &v.column_comments),
912 Builtin::Table(t) => (CommentObjectId::Table(id), &t.desc, &t.column_comments),
913 Builtin::MaterializedView(mv) => (
914 CommentObjectId::MaterializedView(id),
915 &mv.desc,
916 &mv.column_comments,
917 ),
918 Builtin::Log(_)
919 | Builtin::Type(_)
920 | Builtin::Func(_)
921 | Builtin::Index(_)
922 | Builtin::Connection(_) => continue,
923 };
924 txn.drop_comments(&BTreeSet::from_iter([
929 CommentObjectId::Table(id),
930 CommentObjectId::View(id),
931 CommentObjectId::MaterializedView(id),
932 CommentObjectId::Source(id),
933 ]))?;
934
935 let mut comments = comments.clone();
936 for (col_idx, name) in desc.iter_names().enumerate() {
937 if let Some(comment) = comments.remove(name.as_str()) {
938 txn.update_comment(comment_id, Some(col_idx + 1), Some(comment.to_owned()))?;
940 }
941 }
942 assert!(
943 comments.is_empty(),
944 "builtin object contains dangling comments that don't correspond to columns {comments:?}"
945 );
946 }
947
948 let mut deleted_system_objects = BTreeSet::new();
951 let mut deleted_runtime_alterable_system_ids = BTreeSet::new();
952 let mut deleted_comments = BTreeSet::new();
953 for (desc, mapping) in system_object_mappings {
954 deleted_system_objects.insert(mapping.description);
955 if mapping.unique_identifier.fingerprint == RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL {
956 deleted_runtime_alterable_system_ids.insert(mapping.unique_identifier.catalog_id);
957 }
958
959 let id = mapping.unique_identifier.catalog_id;
960 let comment_id = match desc.object_type {
961 CatalogItemType::Table => CommentObjectId::Table(id),
962 CatalogItemType::Source => CommentObjectId::Source(id),
963 CatalogItemType::View => CommentObjectId::View(id),
964 CatalogItemType::MaterializedView => CommentObjectId::MaterializedView(id),
965 CatalogItemType::Sink
966 | CatalogItemType::MetricSink
967 | CatalogItemType::Index
968 | CatalogItemType::Type
969 | CatalogItemType::Func
970 | CatalogItemType::Secret
971 | CatalogItemType::Connection => continue,
972 };
973 deleted_comments.insert(comment_id);
974 }
975 let delete_exceptions: HashSet<SystemObjectDescription> = [].into();
981 assert!(
985 deleted_system_objects
986 .iter()
987 .filter(|object| object.object_type != CatalogItemType::Index)
989 .all(
990 |deleted_object| is_unstable_schema(&deleted_object.schema_name)
991 || delete_exceptions.contains(deleted_object)
992 ),
993 "only objects in unstable schemas can be deleted, deleted objects: {:?}",
994 deleted_system_objects
995 );
996 txn.drop_comments(&deleted_comments)?;
997 txn.remove_items(&deleted_runtime_alterable_system_ids)?;
998 txn.remove_system_object_mappings(deleted_system_objects)?;
999
1000 let new_builtin_collections = new_builtin_ids
1002 .into_iter()
1003 .map(|(_catalog_id, global_id)| global_id)
1004 .collect();
1005
1006 Ok(new_builtin_collections)
1007}
1008
1009fn add_new_remove_old_builtin_clusters_migration(
1010 txn: &mut mz_catalog::durable::Transaction<'_>,
1011 builtin_cluster_config_map: &BuiltinBootstrapClusterConfigMap,
1012 boot_ts: Timestamp,
1013) -> Result<(), mz_catalog::durable::CatalogError> {
1014 let mut durable_clusters: BTreeMap<_, _> = txn
1015 .get_clusters()
1016 .filter(|cluster| cluster.id.is_system())
1017 .map(|cluster| (cluster.name.to_string(), cluster))
1018 .collect();
1019
1020 for builtin_cluster in BUILTIN_CLUSTERS {
1022 if durable_clusters.remove(builtin_cluster.name).is_none() {
1023 let cluster_config = builtin_cluster_config_map.get_config(builtin_cluster.name)?;
1024
1025 let cluster_id = txn.insert_system_cluster(
1026 builtin_cluster.name,
1027 vec![],
1028 builtin_cluster.privileges.to_vec(),
1029 builtin_cluster.owner_id.to_owned(),
1030 mz_catalog::durable::ClusterConfig {
1031 variant: mz_catalog::durable::ClusterVariant::Managed(ClusterVariantManaged {
1032 size: cluster_config.size,
1033 availability_zones: vec![],
1034 replication_factor: cluster_config.replication_factor,
1035 logging: default_logging_config(),
1036 arrangement_compression: false,
1037 optimizer_feature_overrides: Default::default(),
1038 schedule: Default::default(),
1039 auto_scaling_strategy: None,
1040 reconfiguration: None,
1041 burst: None,
1042 }),
1043 workload_class: None,
1044 },
1045 &HashSet::new(),
1046 )?;
1047
1048 let audit_id = txn.allocate_audit_log_id()?;
1049 txn.insert_audit_log_event(VersionedEvent::new(
1050 audit_id,
1051 EventType::Create,
1052 ObjectType::Cluster,
1053 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1054 id: cluster_id.to_string(),
1055 name: builtin_cluster.name.to_string(),
1056 }),
1057 None,
1058 boot_ts.into(),
1059 ));
1060 }
1061 }
1062
1063 let old_clusters = durable_clusters
1065 .values()
1066 .map(|cluster| cluster.id)
1067 .collect();
1068 txn.remove_clusters(&old_clusters)?;
1069
1070 for (_name, cluster) in &durable_clusters {
1071 let audit_id = txn.allocate_audit_log_id()?;
1072 txn.insert_audit_log_event(VersionedEvent::new(
1073 audit_id,
1074 EventType::Drop,
1075 ObjectType::Cluster,
1076 EventDetails::IdNameV1(mz_audit_log::IdNameV1 {
1077 id: cluster.id.to_string(),
1078 name: cluster.name.clone(),
1079 }),
1080 None,
1081 boot_ts.into(),
1082 ));
1083 }
1084
1085 Ok(())
1086}
1087
1088fn add_new_remove_old_builtin_introspection_source_migration(
1089 txn: &mut mz_catalog::durable::Transaction<'_>,
1090) -> Result<(), AdapterError> {
1091 let mut new_indexes = Vec::new();
1092 let mut removed_indexes = BTreeSet::new();
1093 for cluster in txn.get_clusters() {
1094 let mut introspection_source_index_ids = txn.get_introspection_source_indexes(cluster.id);
1095
1096 let mut new_logs = Vec::new();
1097
1098 for log in BUILTINS::logs() {
1099 if introspection_source_index_ids.remove(log.name).is_none() {
1100 new_logs.push(log);
1101 }
1102 }
1103
1104 for log in new_logs {
1105 let (item_id, gid) =
1106 Transaction::allocate_introspection_source_index_id(&cluster.id, log.variant);
1107 new_indexes.push((cluster.id, log.name.to_string(), item_id, gid));
1108 }
1109
1110 removed_indexes.extend(
1113 introspection_source_index_ids
1114 .into_keys()
1115 .map(|name| (cluster.id, name.to_string())),
1116 );
1117 }
1118 txn.insert_introspection_source_indexes(new_indexes, &HashSet::new())?;
1119 txn.remove_introspection_source_indexes(removed_indexes)?;
1120 Ok(())
1121}
1122
1123fn add_new_remove_old_builtin_roles_migration(
1124 txn: &mut mz_catalog::durable::Transaction<'_>,
1125) -> Result<(), mz_catalog::durable::CatalogError> {
1126 let mut durable_roles: BTreeMap<_, _> = txn
1127 .get_roles()
1128 .filter(|role| role.id.is_system() || role.id.is_predefined())
1129 .map(|role| (role.name.to_string(), role))
1130 .collect();
1131
1132 for builtin_role in BUILTIN_ROLES {
1134 if durable_roles.remove(builtin_role.name).is_none() {
1135 txn.insert_builtin_role(
1136 builtin_role.id,
1137 builtin_role.name.to_string(),
1138 builtin_role.attributes.clone(),
1139 RoleMembership::new(),
1140 RoleVars::default(),
1141 builtin_role.oid,
1142 )?;
1143 }
1144 }
1145
1146 let old_roles = durable_roles.values().map(|role| role.id).collect();
1148 txn.remove_roles(&old_roles)?;
1149
1150 Ok(())
1151}
1152
1153fn reconcile_builtin_cluster_replicas(
1185 txn: &mut Transaction<'_>,
1186 builtin_cluster_config_map: &BuiltinBootstrapClusterConfigMap,
1187 boot_ts: Timestamp,
1188) -> Result<(), AdapterError> {
1189 let builtin_cluster_names: BTreeSet<&str> = BUILTIN_CLUSTERS
1190 .iter()
1191 .map(|cluster| cluster.name)
1192 .collect();
1193
1194 let clusters: Vec<_> = txn
1198 .get_clusters()
1199 .filter(|cluster| {
1200 cluster.id.is_system() && builtin_cluster_names.contains(cluster.name.as_str())
1201 })
1202 .collect();
1203
1204 let builtin_cluster_ids: BTreeSet<ClusterId> =
1205 clusters.iter().map(|cluster| cluster.id).collect();
1206
1207 let mut replicas_by_cluster: BTreeMap<ClusterId, BTreeMap<String, ClusterReplica>> =
1216 BTreeMap::new();
1217 for replica in txn.get_cluster_replicas().filter(|replica| {
1218 builtin_cluster_ids.contains(&replica.cluster_id)
1219 && !matches!(
1220 replica.config.location,
1221 ReplicaLocation::Managed { internal: true, .. }
1222 )
1223 }) {
1224 replicas_by_cluster
1225 .entry(replica.cluster_id)
1226 .or_default()
1227 .insert(replica.name.clone(), replica);
1228 }
1229
1230 let mut to_drop: Vec<(String, ClusterReplica)> = Vec::new();
1231
1232 for cluster in clusters {
1233 let ClusterVariant::Managed(managed) = &cluster.config.variant else {
1237 continue;
1238 };
1239
1240 let bootstrap_config = builtin_cluster_config_map.get_config(&cluster.name)?;
1245 if bootstrap_config.replication_factor != managed.replication_factor {
1246 warn!(
1247 cluster = %cluster.name,
1248 configured_replication_factor = managed.replication_factor,
1249 bootstrap_replication_factor = bootstrap_config.replication_factor,
1250 "bootstrap replication factor is not applied to an already-existing \
1251 builtin cluster. Use ALTER CLUSTER ... SET (REPLICATION FACTOR ...) \
1252 to change it",
1253 );
1254 }
1255
1256 let mut surplus = replicas_by_cluster.remove(&cluster.id).unwrap_or_default();
1261 for index in 0..managed.replication_factor {
1262 let replica_name = managed_cluster_replica_name(index);
1263 if surplus.remove(&replica_name).is_some() {
1264 continue;
1265 }
1266
1267 let replica_id = txn.allocate_system_replica_id()?;
1271 txn.insert_cluster_replica_with_id(
1272 cluster.id,
1273 replica_id,
1274 &replica_name,
1275 managed_replica_config(managed),
1276 cluster.owner_id,
1282 )?;
1283 info!(
1284 cluster = %cluster.name, replica = %replica_name, %replica_id,
1285 "creating builtin cluster replica to match the cluster's replication factor"
1286 );
1287
1288 let audit_id = txn.allocate_audit_log_id()?;
1289 txn.insert_audit_log_event(VersionedEvent::new(
1290 audit_id,
1291 EventType::Create,
1292 ObjectType::ClusterReplica,
1293 EventDetails::CreateClusterReplicaV4(mz_audit_log::CreateClusterReplicaV4 {
1294 cluster_id: cluster.id.to_string(),
1295 cluster_name: cluster.name.clone(),
1296 replica_id: Some(replica_id.to_string()),
1297 replica_name,
1298 logical_size: managed.size.clone(),
1299 billed_as: None,
1300 internal: false,
1301 reason: CreateOrDropClusterReplicaReasonV1::System,
1302 scheduling_policies: None,
1303 }),
1304 None,
1305 boot_ts.into(),
1306 ));
1307 }
1308
1309 to_drop.extend(
1315 surplus
1316 .into_values()
1317 .map(|replica| (cluster.name.clone(), replica)),
1318 );
1319 }
1320
1321 let drop_ids = to_drop
1324 .iter()
1325 .map(|(_cluster_name, replica)| replica.replica_id)
1326 .collect();
1327 txn.remove_cluster_replicas(&drop_ids)?;
1328
1329 for (cluster_name, replica) in to_drop {
1330 info!(
1331 cluster = %cluster_name, replica = %replica.name, replica_id = %replica.replica_id,
1332 "dropping builtin cluster replica not called for by the cluster's replication factor"
1333 );
1334
1335 let audit_id = txn.allocate_audit_log_id()?;
1336 txn.insert_audit_log_event(VersionedEvent::new(
1337 audit_id,
1338 EventType::Drop,
1339 ObjectType::ClusterReplica,
1340 EventDetails::DropClusterReplicaV3(mz_audit_log::DropClusterReplicaV3 {
1341 cluster_id: replica.cluster_id.to_string(),
1342 cluster_name,
1343 replica_id: Some(replica.replica_id.to_string()),
1344 replica_name: replica.name,
1345 reason: CreateOrDropClusterReplicaReasonV1::System,
1346 scheduling_policies: None,
1347 }),
1348 None,
1349 boot_ts.into(),
1350 ));
1351 }
1352
1353 Ok(())
1354}
1355
1356fn managed_replica_config(managed: &ClusterVariantManaged) -> ReplicaConfig {
1370 let ClusterVariantManaged {
1373 size,
1374 availability_zones,
1375 logging,
1376 arrangement_compression,
1377 replication_factor: _,
1378 optimizer_feature_overrides: _,
1379 schedule: _,
1380 auto_scaling_strategy: _,
1381 reconfiguration: _,
1382 burst: _,
1383 } = managed;
1384 ReplicaConfig {
1385 location: ReplicaLocation::Managed {
1386 size: size.clone(),
1387 availability_zones: availability_zones.clone(),
1388 billed_as: None,
1389 internal: false,
1390 pending: false,
1391 },
1392 logging: logging.clone(),
1393 arrangement_compression: *arrangement_compression,
1394 }
1395}
1396
1397fn remove_invalid_config_param_role_defaults_migration(
1404 txn: &mut Transaction<'_>,
1405) -> Result<(), AdapterError> {
1406 static BUILD_INFO: mz_build_info::BuildInfo = mz_build_info::build_info!();
1407
1408 let roles_to_migrate: BTreeMap<_, _> = txn
1409 .get_roles()
1410 .filter_map(|mut role| {
1411 let session_vars = SessionVars::new_unchecked(&BUILD_INFO, SYSTEM_USER.clone(), None);
1416
1417 let mut invalid_roles_vars = BTreeMap::new();
1419 for (name, value) in &role.vars.map {
1420 let Ok(session_var) = session_vars.inspect(name) else {
1422 invalid_roles_vars.insert(name.clone(), value.clone());
1423 continue;
1424 };
1425 if session_var.check(value.borrow()).is_err() {
1426 invalid_roles_vars.insert(name.clone(), value.clone());
1427 }
1428 }
1429
1430 if invalid_roles_vars.is_empty() {
1432 return None;
1433 }
1434
1435 tracing::warn!(?role, ?invalid_roles_vars, "removing invalid role vars");
1436
1437 for (name, _value) in invalid_roles_vars {
1439 role.vars.map.remove(&name);
1440 }
1441 Some(role)
1442 })
1443 .map(|role| (role.id, role))
1444 .collect();
1445
1446 txn.update_roles_without_auth(roles_to_migrate)?;
1447
1448 Ok(())
1449}
1450
1451fn remove_pending_cluster_replicas_migration(
1460 tx: &mut Transaction,
1461 boot_ts: mz_repr::Timestamp,
1462) -> Result<(), anyhow::Error> {
1463 let cluster_names: BTreeMap<_, _> = tx.get_clusters().map(|c| (c.id, c.name)).collect();
1465
1466 let occurred_at = boot_ts.into();
1467
1468 for replica in tx.get_cluster_replicas().collect::<Vec<_>>() {
1469 if let mz_catalog::durable::ReplicaLocation::Managed { pending: true, .. } =
1470 replica.config.location
1471 {
1472 let cluster_name = cluster_names
1473 .get(&replica.cluster_id)
1474 .cloned()
1475 .unwrap_or_else(|| "<unknown>".to_string());
1476
1477 info!(
1478 "removing pending cluster replica '{}' from cluster '{}'",
1479 replica.name, cluster_name,
1480 );
1481
1482 tx.remove_cluster_replica(replica.replica_id)?;
1483
1484 let audit_id = tx.allocate_audit_log_id()?;
1488 tx.insert_audit_log_event(VersionedEvent::new(
1489 audit_id,
1490 EventType::Drop,
1491 ObjectType::ClusterReplica,
1492 EventDetails::DropClusterReplicaV3(mz_audit_log::DropClusterReplicaV3 {
1493 cluster_id: replica.cluster_id.to_string(),
1494 cluster_name,
1495 replica_id: Some(replica.replica_id.to_string()),
1496 replica_name: replica.name,
1497 reason: CreateOrDropClusterReplicaReasonV1::System,
1498 scheduling_policies: None,
1499 }),
1500 None,
1501 occurred_at,
1502 ));
1503 }
1504 }
1505 Ok(())
1506}
1507
1508fn default_logging_config() -> ReplicaLogging {
1509 ReplicaLogging {
1510 log_logging: false,
1511 interval: Some(Duration::from_secs(1)),
1512 }
1513}
1514
1515#[derive(Debug)]
1516pub struct BuiltinBootstrapClusterConfigMap {
1517 pub system_cluster: BootstrapBuiltinClusterConfig,
1519 pub catalog_server_cluster: BootstrapBuiltinClusterConfig,
1521 pub probe_cluster: BootstrapBuiltinClusterConfig,
1523 pub support_cluster: BootstrapBuiltinClusterConfig,
1525 pub analytics_cluster: BootstrapBuiltinClusterConfig,
1527}
1528
1529impl BuiltinBootstrapClusterConfigMap {
1530 fn get_config(
1532 &self,
1533 cluster_name: &str,
1534 ) -> Result<BootstrapBuiltinClusterConfig, mz_catalog::durable::CatalogError> {
1535 let cluster_config = if cluster_name == mz_catalog::builtin::MZ_SYSTEM_CLUSTER.name {
1536 &self.system_cluster
1537 } else if cluster_name == mz_catalog::builtin::MZ_CATALOG_SERVER_CLUSTER.name {
1538 &self.catalog_server_cluster
1539 } else if cluster_name == mz_catalog::builtin::MZ_PROBE_CLUSTER.name {
1540 &self.probe_cluster
1541 } else if cluster_name == mz_catalog::builtin::MZ_SUPPORT_CLUSTER.name {
1542 &self.support_cluster
1543 } else if cluster_name == mz_catalog::builtin::MZ_ANALYTICS_CLUSTER.name {
1544 &self.analytics_cluster
1545 } else {
1546 return Err(mz_catalog::durable::CatalogError::Catalog(
1547 SqlCatalogError::UnexpectedBuiltinCluster(cluster_name.to_owned()),
1548 ));
1549 };
1550 Ok(cluster_config.clone())
1551 }
1552}
1553
1554pub(crate) fn into_consolidatable_updates_startup(
1562 updates: Vec<StateUpdate>,
1563 ts: Timestamp,
1564) -> Vec<(StateUpdateKind, Timestamp, Diff)> {
1565 updates
1566 .into_iter()
1567 .map(|StateUpdate { kind, ts: _, diff }| (kind, ts, Diff::from(diff)))
1568 .collect()
1569}
1570
1571#[cfg(test)]
1572mod tests {
1573 use mz_catalog::durable::ClusterVariantManaged;
1574
1575 use super::*;
1576
1577 #[mz_ore::test]
1581 fn test_managed_replica_config_derives_every_shared_field() {
1582 let managed = ClusterVariantManaged {
1583 size: "somesize".into(),
1584 availability_zones: vec!["az1".into(), "az2".into()],
1585 logging: ReplicaLogging {
1586 log_logging: true,
1587 interval: Some(Duration::from_millis(10)),
1588 },
1589 arrangement_compression: true,
1590 replication_factor: 3,
1591 optimizer_feature_overrides: Default::default(),
1592 schedule: Default::default(),
1593 auto_scaling_strategy: None,
1594 reconfiguration: None,
1595 burst: None,
1596 };
1597
1598 let config = managed_replica_config(&managed);
1599
1600 let ReplicaConfig {
1603 location,
1604 logging,
1605 arrangement_compression,
1606 } = config;
1607
1608 assert_eq!(logging, managed.logging);
1609 assert_eq!(arrangement_compression, managed.arrangement_compression);
1610 match location {
1611 ReplicaLocation::Managed {
1612 size,
1613 availability_zones,
1614 billed_as,
1615 internal,
1616 pending,
1617 } => {
1618 assert_eq!(size, managed.size);
1619 assert_eq!(availability_zones, managed.availability_zones);
1620 assert_eq!(billed_as, None);
1624 assert!(!internal);
1625 assert!(!pending);
1626 }
1627 ReplicaLocation::Unmanaged { .. } => panic!("expected a managed location"),
1628 }
1629 }
1630}