From 1f7f17bd3aa9f26245c94cb8794cc06a06f2d18a Mon Sep 17 00:00:00 2001 From: Thomas Miller Date: Tue, 11 Jun 2024 14:51:48 +0100 Subject: [PATCH] Introduces agent version as a top level key. Moving to DQlite in Juju 4.0 we are removing agent version from model config and making it a first class attribute of a model. To support migrating from 3.6 and 4.0 we need the agent version to be available at the root of model migration. To do this new uses of v11 will have to set the key and for older versions of the description package an attempt will be made to pull this information from the model config. --- model.go | 45 +++++++++++++++++++++++++++++++++++++++++++-- model_test.go | 25 ++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/model.go b/model.go index 5729d19..d3994ad 100644 --- a/model.go +++ b/model.go @@ -33,6 +33,9 @@ type Model interface { HasStatus HasStatusHistory + // AgentVersion returns the version currently in use by the model. + AgentVersion() string + Type() string Cloud() string CloudRegion() string @@ -144,6 +147,9 @@ type Model interface { // ModelArgs represent the bare minimum information that is needed // to represent a model. type ModelArgs struct { + // AgentVersion defines the current version in use by the model. + AgentVersion string + Type string Owner names.UserTag Config map[string]interface{} @@ -159,7 +165,8 @@ type ModelArgs struct { // NewModel returns a Model based on the args specified. func NewModel(args ModelArgs) Model { m := &model{ - Version: 10, + Version: 11, + AgentVersion_: args.AgentVersion, Type_: args.Type, Owner_: args.Owner.Id(), Config_: args.Config, @@ -254,6 +261,9 @@ func parentId(machineId string) string { type model struct { Version int `yaml:"version"` + // AgentVersion_ defines the agent version in use by the model. + AgentVersion_ string `yaml:"agent-version"` + Type_ string `yaml:"type"` Owner_ string `yaml:"owner"` Config_ map[string]interface{} `yaml:"config"` @@ -314,6 +324,11 @@ type model struct { PasswordHash_ string `yaml:"password-hash,omitempty"` } +// AgentVersion returns the current agent version in use the by the model. +func (m *model) AgentVersion() string { + return m.AgentVersion_ +} + func (m *model) Type() string { return m.Type_ } @@ -1092,6 +1107,15 @@ func (m *model) Validate() error { return errors.NotValidf("missing status") } + if m.AgentVersion_ != "" { + agentVersion, err := version.Parse(m.AgentVersion_) + if err != nil { + return errors.Annotate(err, "agent version not parsable") + } else if agentVersion == version.Zero { + return errors.NotValidf("agent version cannot be zero") + } + } + validationCtx := newValidationContext() for _, machine := range m.Machines_.Machines_ { if err := m.validateMachine(validationCtx, machine); err != nil { @@ -1522,6 +1546,7 @@ var modelDeserializationFuncs = map[int]modelDeserializationFunc{ 8: newModelImporter(8, schema.FieldMap(modelV8Fields())), 9: newModelImporter(9, schema.FieldMap(modelV9Fields())), 10: newModelImporter(10, schema.FieldMap(modelV10Fields())), + 11: newModelImporter(11, schema.FieldMap(modelV11Fields())), } func modelV1Fields() (schema.Fields, schema.Defaults) { @@ -1635,11 +1660,17 @@ func modelV10Fields() (schema.Fields, schema.Defaults) { return fields, defaults } +func modelV11Fields() (schema.Fields, schema.Defaults) { + fields, defaults := modelV10Fields() + fields["agent-version"] = schema.String() + return fields, defaults +} + func newModelFromValid(valid map[string]interface{}, importVersion int) (*model, error) { // We're always making a version 8 model, no matter what we got on // the way in. result := &model{ - Version: 10, + Version: 11, Type_: IAAS, Owner_: valid["owner"].(string), Config_: valid["config"].(map[string]interface{}), @@ -1903,6 +1934,16 @@ func newModelFromValid(valid map[string]interface{}, importVersion int) (*model, result.SecretBackendID_ = valid["secret-backend-id"].(string) } + + // When we are importing v11 onwards agent version will be a first class + // citizen on the model. Before this we can attempt to get the value from + // config. + if importVersion >= 11 { + result.AgentVersion_ = valid["agent-version"].(string) + } else if result.Config_ != nil && result.Config_["agent-version"] != nil { + result.AgentVersion_ = result.Config_["agent-version"].(string) + } + return result, nil } diff --git a/model_test.go b/model_test.go index 241b827..8e9fb3a 100644 --- a/model_test.go +++ b/model_test.go @@ -171,8 +171,9 @@ func (s *ModelSerializationSuite) TestParsingYAMLWithMissingModificationStatus(c func (s *ModelSerializationSuite) testParsingYAMLWithMachine(c *gc.C, machineFn func(Model)) { args := ModelArgs{ - Type: IAAS, - Owner: names.NewUserTag("magic"), + AgentVersion: "3.1.1", + Type: IAAS, + Owner: names.NewUserTag("magic"), Config: map[string]interface{}{ "name": "awesome", "uuid": "some-uuid", @@ -202,6 +203,7 @@ func (s *ModelSerializationSuite) testParsingYAMLWithMachine(c *gc.C, machineFn addMinimalApplication(initial) model := s.exportImport(c, initial) + c.Check(model.AgentVersion(), gc.Equals, "3.1.1") c.Assert(model.Type(), gc.Equals, IAAS) c.Assert(model.Owner(), gc.Equals, args.Owner) c.Assert(model.Tag().Id(), gc.Equals, "some-uuid") @@ -1165,7 +1167,7 @@ func (s *ModelSerializationSuite) TestSerializesToLatestVersion(c *gc.C) { c.Assert(ok, jc.IsTrue) version, ok := versionValue.(int) c.Assert(ok, jc.IsTrue) - c.Assert(version, gc.Equals, 10) + c.Assert(version, gc.Equals, 11) } func (s *ModelSerializationSuite) TestVersion1Works(c *gc.C) { @@ -1634,6 +1636,23 @@ func (s *ModelSerializationSuite) TestRemoteSecretsValidate(c *gc.C) { c.Assert(err, gc.ErrorMatches, `remote secret\[0\] consumer \(foo\) not valid`) } +func (s *ModelSerializationSuite) TestAgentVersionPre11Import(c *gc.C) { + initial := s.newModel(ModelArgs{ + Config: map[string]any{ + "agent-version": "3.3.3", + }, + }) + data := asStringMap(c, initial) + data["version"] = 10 + bytes, err := yaml.Marshal(data) + c.Assert(err, jc.ErrorIsNil) + + model, err := Deserialize(bytes) + c.Check(err, jc.ErrorIsNil) + + c.Check(model.AgentVersion(), gc.Equals, "3.3.3") +} + // modelV1example was taken from a Juju 2.1 model dump, which is version // 1, and among other things is missing model status, which version 2 makes // manditory.