github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/core/charm/baseselector_test.go (about) 1 // Copyright 2023 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package charm 5 6 import ( 7 "github.com/juju/collections/set" 8 "github.com/juju/testing" 9 jc "github.com/juju/testing/checkers" 10 "go.uber.org/mock/gomock" 11 gc "gopkg.in/check.v1" 12 13 "github.com/juju/juju/core/base" 14 "github.com/juju/juju/version" 15 ) 16 17 type baseSelectorSuite struct { 18 testing.IsolationSuite 19 20 logger *MockSelectorLogger 21 cfg *MockSelectorModelConfig 22 } 23 24 var _ = gc.Suite(&baseSelectorSuite{}) 25 26 var ( 27 bionic = base.MustParseBaseFromString("ubuntu@18.04/stable") 28 cosmic = base.MustParseBaseFromString("ubuntu@18.10/stable") 29 disco = base.MustParseBaseFromString("ubuntu@19.04") 30 jammy = base.MustParseBaseFromString("ubuntu@22.04") 31 focal = base.MustParseBaseFromString("ubuntu@20.04") 32 precise = base.MustParseBaseFromString("ubuntu@14.04") 33 utopic = base.MustParseBaseFromString("ubuntu@16.10") 34 vivid = base.MustParseBaseFromString("ubuntu@17.04") 35 latest = base.LatestLTSBase() 36 jujuDefault = version.DefaultSupportedLTSBase() 37 ) 38 39 func (s *baseSelectorSuite) setup(c *gc.C) *gomock.Controller { 40 ctrl := gomock.NewController(c) 41 s.logger = NewMockSelectorLogger(ctrl) 42 s.logger.EXPECT().Infof(gomock.Any(), gomock.Any()).AnyTimes() 43 s.logger.EXPECT().Tracef(gomock.Any(), gomock.Any()).AnyTimes() 44 45 s.cfg = NewMockSelectorModelConfig(ctrl) 46 47 return ctrl 48 } 49 50 func (s *baseSelectorSuite) TestCharmBase(c *gc.C) { 51 defer s.setup(c).Finish() 52 53 deployBasesTests := []struct { 54 title string 55 selector BaseSelector 56 expectedBase base.Base 57 err string 58 }{ 59 { 60 title: "juju deploy simple # default base set", 61 selector: BaseSelector{ 62 defaultBase: focal, 63 explicitDefaultBase: true, 64 supportedBases: []base.Base{bionic, focal}, 65 }, 66 expectedBase: focal, 67 }, 68 { 69 title: "juju deploy simple --base=ubuntu@14.04 # no supported base", 70 selector: BaseSelector{ 71 requestedBase: precise, 72 supportedBases: []base.Base{bionic, cosmic}, 73 }, 74 err: `base: ubuntu@14.04/stable`, 75 }, 76 { 77 title: "juju deploy simple --base=ubuntu@18.04 # user provided base takes precedence over default base ", 78 selector: BaseSelector{ 79 requestedBase: bionic, 80 defaultBase: focal, 81 explicitDefaultBase: true, 82 supportedBases: []base.Base{bionic, focal, cosmic}, 83 }, 84 expectedBase: bionic, 85 }, 86 87 // Now charms with supported base. 88 89 { 90 title: "juju deploy multiseries # use charm default, nothing specified, no default base", 91 selector: BaseSelector{ 92 supportedBases: []base.Base{bionic, cosmic}, 93 }, 94 expectedBase: bionic, 95 }, 96 { 97 title: "juju deploy multiseries # use charm defaults used if default base doesn't match, nothing specified", 98 selector: BaseSelector{ 99 supportedBases: []base.Base{bionic, cosmic}, 100 defaultBase: precise, 101 explicitDefaultBase: true, 102 }, 103 err: `base: ubuntu@14.04/stable`, 104 }, 105 { 106 title: "juju deploy multiseries # use model base defaults if supported by charm", 107 selector: BaseSelector{ 108 supportedBases: []base.Base{bionic, cosmic, disco}, 109 defaultBase: disco, 110 explicitDefaultBase: true, 111 }, 112 expectedBase: disco, 113 }, 114 { 115 title: "juju deploy multiseries --base=ubuntu@18.04 # use supported requested", 116 selector: BaseSelector{ 117 requestedBase: bionic, 118 supportedBases: []base.Base{cosmic, bionic}, 119 }, 120 expectedBase: bionic, 121 }, 122 { 123 title: "juju deploy multiseries --base=ubuntu@18.04 # unsupported requested", 124 selector: BaseSelector{ 125 requestedBase: bionic, 126 supportedBases: []base.Base{utopic, vivid}, 127 }, 128 err: `base: ubuntu@18.04/stable`, 129 }, 130 { 131 title: "juju deploy multiseries # fallback to base.LatestLTSBase()", 132 selector: BaseSelector{ 133 supportedBases: []base.Base{utopic, vivid, latest}, 134 }, 135 expectedBase: latest, 136 }, 137 { 138 title: "juju deploy multiseries # fallback to version.DefaultSupportedLTSBase()", 139 selector: BaseSelector{ 140 supportedBases: []base.Base{utopic, vivid, jujuDefault}, 141 }, 142 expectedBase: jujuDefault, 143 }, 144 { 145 title: "juju deploy multiseries # prefer base.LatestLTSBase() to version.DefaultSupportedLTSBase()", 146 selector: BaseSelector{ 147 supportedBases: []base.Base{utopic, vivid, jujuDefault, latest}, 148 }, 149 expectedBase: latest, 150 }, 151 } 152 153 // Use bionic for LTS for all calls. 154 previous := base.SetLatestLtsForTesting("focal") 155 defer base.SetLatestLtsForTesting(previous) 156 157 for i, test := range deployBasesTests { 158 c.Logf("test %d [%s]", i, test.title) 159 test.selector.logger = s.logger 160 base, err := test.selector.CharmBase() 161 if test.err != "" { 162 c.Check(err, gc.ErrorMatches, test.err) 163 } else { 164 c.Check(err, jc.ErrorIsNil) 165 c.Check(base.IsCompatible(test.expectedBase), jc.IsTrue, 166 gc.Commentf("%q compatible with %q", base, test.expectedBase)) 167 } 168 } 169 } 170 171 func (s *baseSelectorSuite) TestValidate(c *gc.C) { 172 defer s.setup(c).Finish() 173 174 deploySeriesTests := []struct { 175 title string 176 selector BaseSelector 177 supportedCharmBases []base.Base 178 supportedJujuBases []base.Base 179 err string 180 }{{ 181 // Simple selectors first, no supported bases, check we're validating 182 title: "juju deploy simple # no default base, no supported base", 183 selector: BaseSelector{}, 184 err: "charm does not define any bases, not valid", 185 }, { 186 title: "should fail when image-id constraint is used and no base is explicitly set", 187 selector: BaseSelector{ 188 usingImageID: true, 189 }, 190 err: "base must be explicitly provided when image-id constraint is used", 191 }, { 192 title: "should return no errors when using image-id and base flag", 193 selector: BaseSelector{ 194 requestedBase: jammy, 195 usingImageID: true, 196 }, 197 supportedJujuBases: []base.Base{jammy, bionic}, 198 supportedCharmBases: []base.Base{jammy, bionic}, 199 }, { 200 title: "should return no errors when using image-id and explicit base from Config", 201 selector: BaseSelector{ 202 explicitDefaultBase: true, 203 usingImageID: true, 204 }, 205 supportedJujuBases: []base.Base{jammy, bionic}, 206 supportedCharmBases: []base.Base{jammy, bionic}, 207 }, { 208 title: "juju deploy multiseries with invalid series # use charm default, nothing specified, no default base", 209 selector: BaseSelector{}, 210 supportedCharmBases: []base.Base{precise}, 211 supportedJujuBases: []base.Base{jammy}, 212 err: `the charm defined bases "ubuntu@14.04" not supported`, 213 }} 214 215 for i, test := range deploySeriesTests { 216 c.Logf("test %d [%s]", i, test.title) 217 test.selector.logger = s.logger 218 test.selector.jujuSupportedBases = set.NewStrings() 219 _, err := test.selector.validate(test.supportedCharmBases, test.supportedJujuBases) 220 if test.err != "" { 221 c.Check(err, gc.ErrorMatches, test.err) 222 } else { 223 c.Check(err, jc.ErrorIsNil) 224 } 225 } 226 } 227 228 func (s *baseSelectorSuite) TestConfigureBaseSelector(c *gc.C) { 229 defer s.setup(c).Finish() 230 231 s.cfg.EXPECT().DefaultBase() 232 cfg := SelectorConfig{ 233 Config: s.cfg, 234 Force: false, 235 Logger: s.logger, 236 RequestedBase: base.Base{}, 237 SupportedCharmBases: []base.Base{jammy, focal, bionic}, 238 WorkloadBases: []base.Base{jammy, focal}, 239 UsingImageID: false, 240 } 241 242 obtained, err := ConfigureBaseSelector(cfg) 243 c.Assert(err, jc.ErrorIsNil) 244 c.Check(obtained.supportedBases, gc.DeepEquals, []base.Base{jammy, focal}) 245 } 246 247 func (s *baseSelectorSuite) TestConfigureBaseSelectorCentos(c *gc.C) { 248 defer s.setup(c).Finish() 249 250 s.cfg.EXPECT().DefaultBase() 251 c7 := base.MustParseBaseFromString("centos@7/stable") 252 c8 := base.MustParseBaseFromString("centos@8/stable") 253 c6 := base.MustParseBaseFromString("centos@6/stable") 254 cfg := SelectorConfig{ 255 Config: s.cfg, 256 Force: false, 257 Logger: s.logger, 258 RequestedBase: base.Base{}, 259 SupportedCharmBases: []base.Base{c6, c7, c8}, 260 WorkloadBases: []base.Base{c7, c8}, 261 UsingImageID: false, 262 } 263 264 obtained, err := ConfigureBaseSelector(cfg) 265 c.Assert(err, jc.ErrorIsNil) 266 c.Check(obtained.supportedBases, gc.DeepEquals, []base.Base{c7, c8}) 267 } 268 269 func (s *baseSelectorSuite) TestConfigureBaseSelectorDefaultBase(c *gc.C) { 270 defer s.setup(c).Finish() 271 272 s.cfg.EXPECT().DefaultBase().Return("ubuntu@20.04", true) 273 cfg := SelectorConfig{ 274 Config: s.cfg, 275 Force: false, 276 Logger: s.logger, 277 RequestedBase: base.Base{}, 278 SupportedCharmBases: []base.Base{jammy, focal, bionic}, 279 WorkloadBases: []base.Base{jammy, focal}, 280 UsingImageID: false, 281 } 282 283 baseSelector, err := ConfigureBaseSelector(cfg) 284 c.Assert(err, jc.ErrorIsNil) 285 c.Check(baseSelector.supportedBases, jc.SameContents, []base.Base{jammy, focal}) 286 c.Check(baseSelector.defaultBase, gc.DeepEquals, focal) 287 c.Check(baseSelector.explicitDefaultBase, jc.IsTrue) 288 289 obtained, err := baseSelector.CharmBase() 290 c.Assert(err, jc.ErrorIsNil) 291 expectedBase := base.MustParseBaseFromString("ubuntu@20.04") 292 c.Check(obtained.IsCompatible(expectedBase), jc.IsTrue, gc.Commentf("obtained: %q, expected %q", obtained, expectedBase)) 293 } 294 295 func (s *baseSelectorSuite) TestConfigureBaseSelectorDefaultBaseFail(c *gc.C) { 296 defer s.setup(c).Finish() 297 298 s.cfg.EXPECT().DefaultBase().Return("ubuntu@18.04", true) 299 cfg := SelectorConfig{ 300 Config: s.cfg, 301 Force: false, 302 Logger: s.logger, 303 RequestedBase: base.Base{}, 304 SupportedCharmBases: []base.Base{jammy, focal, bionic}, 305 WorkloadBases: []base.Base{jammy, focal}, 306 UsingImageID: false, 307 } 308 309 baseSelector, err := ConfigureBaseSelector(cfg) 310 c.Assert(err, jc.ErrorIsNil) 311 _, err = baseSelector.CharmBase() 312 c.Assert(err, gc.ErrorMatches, `base: ubuntu@18.04/stable`) 313 }