diff --git a/core/model/model.go b/core/model/model.go index cbe1b0810ad..3cbd1d922a7 100644 --- a/core/model/model.go +++ b/core/model/model.go @@ -10,6 +10,7 @@ import ( "github.com/juju/version/v2" "github.com/juju/juju/core/credential" + "github.com/juju/juju/core/life" "github.com/juju/juju/core/user" "github.com/juju/juju/internal/uuid" ) @@ -47,6 +48,12 @@ type Model struct { // Name returns the human friendly name of the model. Name string + // Life is the current state of the model. + // Options are alive, dying, dead. Every model starts as alive, only + // during the destruction of the model it transitions to dying and then + // dead. + Life life.Value + // UUID is the universally unique identifier of the model. UUID UUID diff --git a/core/model/model_test.go b/core/model/model_test.go index 60fc92371fb..63fc4f9886c 100644 --- a/core/model/model_test.go +++ b/core/model/model_test.go @@ -23,9 +23,9 @@ func (*ModelSuite) TestValidateBranchName(c *gc.C) { branchName string valid bool }{ - {"", false}, - {GenerationMaster, false}, - {"something else", true}, + {branchName: "", valid: false}, + {branchName: GenerationMaster, valid: false}, + {branchName: "something else", valid: true}, } { err := ValidateBranchName(t.branchName) if t.valid { diff --git a/domain/credential/state/state_test.go b/domain/credential/state/state_test.go index ec368e3ffc8..abd53c8115a 100644 --- a/domain/credential/state/state_test.go +++ b/domain/credential/state/state_test.go @@ -573,8 +573,8 @@ func (s *credentialSuite) TestModelsUsingCloudCredential(c *gc.C) { return err } result, err := tx.ExecContext(ctx, fmt.Sprintf(` - INSERT INTO model_metadata (model_uuid, name, owner_uuid, model_type_id, cloud_uuid, cloud_credential_uuid) - SELECT %q, %q, %q, 0, + INSERT INTO model_metadata (model_uuid, name, owner_uuid, life_id, model_type_id, cloud_uuid, cloud_credential_uuid) + SELECT %q, %q, %q, 0, 0, (SELECT uuid FROM cloud WHERE cloud.name="stratus"), (SELECT uuid FROM cloud_credential cc WHERE cc.name="foobar")`, modelUUID, name, s.userUUID), diff --git a/domain/model/state/state.go b/domain/model/state/state.go index 5f136d66640..db3d66962b6 100644 --- a/domain/model/state/state.go +++ b/domain/model/state/state.go @@ -18,6 +18,7 @@ import ( "github.com/juju/juju/domain" usererrors "github.com/juju/juju/domain/access/errors" clouderrors "github.com/juju/juju/domain/cloud/errors" + "github.com/juju/juju/domain/life" "github.com/juju/juju/domain/model" modelerrors "github.com/juju/juju/domain/model/errors" jujudb "github.com/juju/juju/internal/database" @@ -152,7 +153,7 @@ func Get( uuid coremodel.UUID, ) (coremodel.Model, error) { modelStmt := ` -SELECT md.name, cl.name, cr.name, mt.type, u.uuid, cc.cloud_uuid, cc.name, o.name, ccn.name +SELECT md.name, cl.name, cr.name, mt.type, u.uuid, cc.cloud_uuid, cc.name, o.name, ccn.name, l.value FROM model_metadata AS md LEFT JOIN model_list ml ON ml.uuid = md.model_uuid LEFT JOIN cloud cl ON cl.uuid = md.cloud_uuid @@ -162,6 +163,7 @@ LEFT JOIN model_type mt ON mt.id = md.model_type_id LEFT JOIN user u ON u.uuid = md.owner_uuid LEFT JOIN user o ON o.uuid = cc.owner_uuid LEFT JOIN cloud ccn ON ccn.uuid = cc.cloud_uuid +LEFT JOIN life l ON l.id = md.life_id WHERE md.model_uuid = ? ` @@ -186,6 +188,7 @@ WHERE md.model_uuid = ? &credKey.Name, &credKey.Owner, &credKey.Cloud, + &model.Life, ) if errors.Is(err, sql.ErrNoRows) { @@ -370,15 +373,16 @@ AND removed = false INSERT INTO model_metadata (model_uuid, cloud_uuid, model_type_id, + life_id, name, owner_uuid) -SELECT ?, ?, model_type.id, ?, ? +SELECT ?, ?, model_type.id, ?, ?, ? FROM model_type WHERE model_type.type = ? ` res, err := tx.ExecContext(ctx, stmt, - uuid, cloudUUID, input.Name, input.Owner, modelType, + uuid, cloudUUID, life.Alive, input.Name, input.Owner, modelType, ) if jujudb.IsErrConstraintPrimaryKey(err) { return fmt.Errorf("%w for uuid %q", modelerrors.AlreadyExists, uuid) diff --git a/domain/model/state/state_test.go b/domain/model/state/state_test.go index d972e03599a..af3c10c6d04 100644 --- a/domain/model/state/state_test.go +++ b/domain/model/state/state_test.go @@ -14,6 +14,7 @@ import ( "github.com/juju/juju/cloud" corecredential "github.com/juju/juju/core/credential" + "github.com/juju/juju/core/life" coremodel "github.com/juju/juju/core/model" modeltesting "github.com/juju/juju/core/model/testing" "github.com/juju/juju/core/permission" @@ -132,6 +133,7 @@ func (m *stateSuite) TestGetModel(c *gc.C) { Name: "my-test-model", Owner: m.userUUID, ModelType: coremodel.IAAS, + Life: life.Alive, }) } diff --git a/domain/schema/controller.go b/domain/schema/controller.go index fd0b8839399..bbcc7097f6f 100644 --- a/domain/schema/controller.go +++ b/domain/schema/controller.go @@ -22,11 +22,13 @@ const ( tableUpgradeInfoControllerNode tableObjectStoreMetadata tableSecretBackendRotation + tableModelMetadata ) // ControllerDDL is used to create the controller database schema at bootstrap. func ControllerDDL() *schema.Schema { patches := []func() schema.Patch{ + lifeSchema, leaseSchema, changeLogSchema, changeLogControllerNamespacesSchema, @@ -59,6 +61,7 @@ func ControllerDDL() *schema.Schema { changeLogTriggersForTable("upgrade_info_controller_node", "upgrade_info_uuid", tableUpgradeInfoControllerNode), changeLogTriggersForTable("object_store_metadata_path", "path", tableObjectStoreMetadata), changeLogTriggersForTableOnColumn("secret_backend_rotation", "backend_uuid", "next_rotation_time", tableSecretBackendRotation), + changeLogTriggersForTable("model_metadata", "model_uuid", tableModelMetadata), // We need to ensure that the internal and kubernetes backends are immutable after // they are created by the controller during bootstrap time. @@ -142,7 +145,8 @@ INSERT INTO change_log_namespace VALUES (8, 'autocert_cache', 'autocert cache changes based on the UUID'), (9, 'upgrade_info_controller_node', 'upgrade info controller node changes based on the upgrade info UUID'), (10, 'object_store_metadata_path', 'object store metadata path changes based on the path'), - (11, 'secret_backend_rotation', 'secret backend rotation changes based on the backend UUID and next rotation time'); + (11, 'secret_backend_rotation', 'secret backend rotation changes based on the backend UUID and next rotation time'), + (12, 'model_metadata', 'model metadata changes based on the model UUID'); `) } @@ -390,6 +394,7 @@ CREATE TABLE model_metadata ( cloud_region_uuid TEXT, cloud_credential_uuid TEXT, model_type_id INT NOT NULL, + life_id INT NOT NULL, name TEXT NOT NULL, owner_uuid TEXT NOT NULL, CONSTRAINT fk_model_metadata_model @@ -410,6 +415,9 @@ CREATE TABLE model_metadata ( CONSTRAINT fk_model_metadata_owner_uuid FOREIGN KEY (owner_uuid) REFERENCES user(uuid) + CONSTRAINT fk_model_metadata_life_id + FOREIGN KEY (life_id) + REFERENCES life(id) ); CREATE UNIQUE INDEX idx_model_metadata_name_owner @@ -431,7 +439,8 @@ SELECT m.uuid, mt.type AS model_type_type, mm.name, mm.owner_uuid, - u.name AS owner_name + u.name AS owner_name, + l.value AS life FROM model_list m INNER JOIN model_metadata mm ON m.uuid = mm.model_uuid INNER JOIN cloud c ON mm.cloud_uuid = c.uuid @@ -440,7 +449,8 @@ LEFT JOIN cloud_credential cc ON mm.cloud_credential_uuid = cc.uuid INNER JOIN user cco ON cc.owner_uuid = cco.uuid LEFT JOIN cloud ccn ON cc.cloud_uuid = ccn.uuid INNER JOIN model_type mt ON mm.model_type_id = mt.id -INNER JOIN user u ON mm.owner_uuid = u.uuid; +INNER JOIN user u ON mm.owner_uuid = u.uuid +INNER JOIN life l ON mm.life_id = l.id; `) } diff --git a/domain/schema/life.go b/domain/schema/life.go new file mode 100644 index 00000000000..4482961cf6f --- /dev/null +++ b/domain/schema/life.go @@ -0,0 +1,20 @@ +// Copyright 2024 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package schema + +import "github.com/juju/juju/core/database/schema" + +func lifeSchema() schema.Patch { + return schema.MakePatch(` +CREATE TABLE life ( + id INT PRIMARY KEY, + value TEXT NOT NULL +); + +INSERT INTO life VALUES + (0, 'alive'), + (1, 'dying'), + (2, 'dead'); +`) +} diff --git a/domain/schema/model.go b/domain/schema/model.go index 9be6f86c8a7..954be48db62 100644 --- a/domain/schema/model.go +++ b/domain/schema/model.go @@ -122,20 +122,6 @@ CREATE TABLE annotation_%[1]s ( } } -func lifeSchema() schema.Patch { - return schema.MakePatch(` -CREATE TABLE life ( - id INT PRIMARY KEY, - value TEXT NOT NULL -); - -INSERT INTO life VALUES - (0, 'alive'), - (1, 'dying'), - (2, 'dead'); -`) -} - func changeLogModelNamespaceSchema() schema.Patch { // Note: These should match exactly the values of the tableNamespaceID // constants above. diff --git a/domain/schema/schema_test.go b/domain/schema/schema_test.go index beec56f020d..a2d4d0a1bf4 100644 --- a/domain/schema/schema_test.go +++ b/domain/schema/schema_test.go @@ -127,6 +127,9 @@ func (s *schemaSuite) TestControllerTables(c *gc.C) { "model_metadata", "model_type", + // Life + "life", + // Controller config "controller_config", @@ -352,6 +355,10 @@ func (s *schemaSuite) TestControllerTriggers(c *gc.C) { "trg_log_secret_backend_rotation_next_rotation_time_insert", "trg_log_secret_backend_rotation_next_rotation_time_update", "trg_log_secret_backend_rotation_next_rotation_time_delete", + + "trg_log_model_metadata_insert", + "trg_log_model_metadata_update", + "trg_log_model_metadata_delete", ) // These are additional triggers that are not change log triggers, but