github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/environs/testing/tools.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package testing 5 6 import ( 7 "bytes" 8 "context" 9 "fmt" 10 "io" 11 "os" 12 "path/filepath" 13 "strings" 14 15 "github.com/juju/collections/set" 16 "github.com/juju/http/v2" 17 jc "github.com/juju/testing/checkers" 18 "github.com/juju/version/v2" 19 gc "gopkg.in/check.v1" 20 21 agenttools "github.com/juju/juju/agent/tools" 22 agenterrors "github.com/juju/juju/cmd/jujud/agent/errors" 23 "github.com/juju/juju/core/arch" 24 coreos "github.com/juju/juju/core/os" 25 "github.com/juju/juju/environs/filestorage" 26 "github.com/juju/juju/environs/simplestreams" 27 sstesting "github.com/juju/juju/environs/simplestreams/testing" 28 "github.com/juju/juju/environs/storage" 29 envtools "github.com/juju/juju/environs/tools" 30 "github.com/juju/juju/juju/names" 31 coretesting "github.com/juju/juju/testing" 32 coretools "github.com/juju/juju/tools" 33 jujuversion "github.com/juju/juju/version" 34 ) 35 36 // ToolsFixture is used as a fixture to stub out the default tools URL so we 37 // don't hit the real internet during tests. 38 type ToolsFixture struct { 39 origDefaultURL string 40 DefaultBaseURL string 41 42 // UploadArches holds the architectures of tools to 43 // upload in UploadFakeTools. If empty, it will default 44 // to just arch.HostArch() 45 UploadArches []string 46 } 47 48 func (s *ToolsFixture) SetUpTest(c *gc.C) { 49 s.origDefaultURL = envtools.DefaultBaseURL 50 envtools.DefaultBaseURL = s.DefaultBaseURL 51 } 52 53 func (s *ToolsFixture) TearDownTest(c *gc.C) { 54 envtools.DefaultBaseURL = s.origDefaultURL 55 } 56 57 // UploadFakeToolsToDirectory uploads fake tools of the architectures in 58 // s.UploadArches for each LTS release to the specified directory. 59 func (s *ToolsFixture) UploadFakeToolsToDirectory(c *gc.C, dir, toolsDir, stream string) { 60 stor, err := filestorage.NewFileStorageWriter(dir) 61 c.Assert(err, jc.ErrorIsNil) 62 s.UploadFakeTools(c, stor, toolsDir, stream) 63 } 64 65 // UploadFakeTools uploads fake tools of the architectures in 66 // s.UploadArches for each LTS release to the specified storage. 67 func (s *ToolsFixture) UploadFakeTools(c *gc.C, stor storage.Storage, toolsDir, stream string) { 68 UploadFakeTools(c, stor, toolsDir, stream, s.UploadArches...) 69 } 70 71 // RemoveFakeToolsMetadata deletes the fake simplestreams tools metadata from the supplied storage. 72 func RemoveFakeToolsMetadata(c *gc.C, stor storage.Storage) { 73 files, err := stor.List("tools/streams") 74 c.Assert(err, jc.ErrorIsNil) 75 for _, file := range files { 76 err = stor.Remove(file) 77 c.Check(err, jc.ErrorIsNil) 78 } 79 } 80 81 // CheckTools ensures the obtained and expected tools are equal, allowing for the fact that 82 // the obtained tools may not have size and checksum set. 83 func CheckTools(c *gc.C, obtained, expected *coretools.Tools) { 84 c.Assert(obtained.Version, gc.Equals, expected.Version) 85 // TODO(dimitern) 2013-10-02 bug #1234217 86 // Are these used at at all? If not we should drop them. 87 if obtained.URL != "" { 88 c.Assert(obtained.URL, gc.Equals, expected.URL) 89 } 90 if obtained.Size > 0 { 91 c.Assert(obtained.Size, gc.Equals, expected.Size) 92 c.Assert(obtained.SHA256, gc.Equals, expected.SHA256) 93 } 94 } 95 96 // CheckUpgraderReadyError ensures the obtained and expected errors are equal. 97 func CheckUpgraderReadyError(c *gc.C, obtained error, expected *agenterrors.UpgradeReadyError) { 98 c.Assert(obtained, gc.FitsTypeOf, &agenterrors.UpgradeReadyError{}) 99 err := obtained.(*agenterrors.UpgradeReadyError) 100 c.Assert(err.AgentName, gc.Equals, expected.AgentName) 101 c.Assert(err.DataDir, gc.Equals, expected.DataDir) 102 c.Assert(err.OldTools, gc.Equals, expected.OldTools) 103 c.Assert(err.NewTools, gc.Equals, expected.NewTools) 104 } 105 106 // PrimeTools sets up the current version of the tools to vers and 107 // makes sure that they're available in the dataDir. 108 func PrimeTools(c *gc.C, stor storage.Storage, dataDir, toolsDir string, vers version.Binary) *coretools.Tools { 109 err := os.RemoveAll(filepath.Join(dataDir, "tools")) 110 c.Assert(err, jc.ErrorIsNil) 111 agentTools, err := uploadFakeToolsVersion(stor, toolsDir, vers) 112 c.Assert(err, jc.ErrorIsNil) 113 client := http.NewClient() 114 resp, err := client.Get(context.TODO(), agentTools.URL) 115 c.Assert(err, jc.ErrorIsNil) 116 defer resp.Body.Close() 117 err = agenttools.UnpackTools(dataDir, agentTools, resp.Body) 118 c.Assert(err, jc.ErrorIsNil) 119 return agentTools 120 } 121 122 func uploadFakeToolsVersion(stor storage.Storage, toolsDir string, vers version.Binary) (*coretools.Tools, error) { 123 logger.Infof("uploading FAKE tools %s", vers) 124 tgz, checksum := makeFakeTools(vers) 125 size := int64(len(tgz)) 126 name := envtools.StorageName(vers, toolsDir) 127 if err := stor.Put(name, bytes.NewReader(tgz), size); err != nil { 128 return nil, err 129 } 130 url, err := stor.URL(name) 131 if err != nil { 132 return nil, err 133 } 134 return &coretools.Tools{URL: url, Version: vers, Size: size, SHA256: checksum}, nil 135 } 136 137 // InstallFakeDownloadedTools creates and unpacks fake tools of the 138 // given version into the data directory specified. 139 func InstallFakeDownloadedTools(c *gc.C, dataDir string, vers version.Binary) *coretools.Tools { 140 tgz, checksum := makeFakeTools(vers) 141 agentTools := &coretools.Tools{ 142 Version: vers, 143 Size: int64(len(tgz)), 144 SHA256: checksum, 145 } 146 err := agenttools.UnpackTools(dataDir, agentTools, bytes.NewReader(tgz)) 147 c.Assert(err, jc.ErrorIsNil) 148 return agentTools 149 } 150 151 func makeFakeTools(vers version.Binary) ([]byte, string) { 152 return coretesting.TarGz( 153 coretesting.NewTarFile(names.Jujud, 0777, "jujud contents "+vers.String())) 154 } 155 156 // UploadFakeToolsVersions puts fake tools in the supplied storage for the supplied versions. 157 func UploadFakeToolsVersions(store storage.Storage, toolsDir, stream string, versions ...version.Binary) ([]*coretools.Tools, error) { 158 // Leave existing tools alone. 159 existingTools := make(map[version.Binary]*coretools.Tools) 160 existing, _ := envtools.ReadList(store, toolsDir, 1, -1) 161 for _, tools := range existing { 162 existingTools[tools.Version] = tools 163 } 164 var agentTools = make(coretools.List, len(versions)) 165 for i, version := range versions { 166 if tools, ok := existingTools[version]; ok { 167 agentTools[i] = tools 168 } else { 169 t, err := uploadFakeToolsVersion(store, toolsDir, version) 170 if err != nil { 171 return nil, err 172 } 173 agentTools[i] = t 174 } 175 } 176 ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory()) 177 if err := envtools.MergeAndWriteMetadata(ss, store, toolsDir, stream, agentTools, envtools.DoNotWriteMirrors); err != nil { 178 return nil, err 179 } 180 err := SignTestTools(store) 181 if err != nil { 182 return nil, err 183 } 184 return agentTools, nil 185 } 186 187 func SignTestTools(store storage.Storage) error { 188 files, err := store.List("") 189 if err != nil { 190 return err 191 } 192 for _, file := range files { 193 if strings.HasSuffix(file, sstesting.UnsignedJsonSuffix) { 194 // only sign .json files and data 195 if err := SignFileData(store, file); err != nil { 196 return err 197 } 198 } 199 } 200 return nil 201 } 202 203 func SignFileData(stor storage.Storage, fileName string) error { 204 r, err := stor.Get(fileName) 205 if err != nil { 206 return err 207 } 208 defer r.Close() 209 210 fileData, err := io.ReadAll(r) 211 if err != nil { 212 return err 213 } 214 215 signedName, signedContent, err := sstesting.SignMetadata(fileName, fileData) 216 if err != nil { 217 return err 218 } 219 220 err = stor.Put(signedName, strings.NewReader(string(signedContent)), int64(len(string(signedContent)))) 221 if err != nil { 222 return err 223 } 224 return nil 225 } 226 227 // AssertUploadFakeToolsVersions puts fake tools in the supplied storage for the supplied versions. 228 func AssertUploadFakeToolsVersions(c *gc.C, stor storage.Storage, toolsDir, stream string, versions ...version.Binary) []*coretools.Tools { 229 agentTools, err := UploadFakeToolsVersions(stor, toolsDir, stream, versions...) 230 c.Assert(err, jc.ErrorIsNil) 231 return agentTools 232 } 233 234 // MustUploadFakeToolsVersions acts as UploadFakeToolsVersions, but panics on failure. 235 func MustUploadFakeToolsVersions(store storage.Storage, stream string, versions ...version.Binary) []*coretools.Tools { 236 var agentTools = make(coretools.List, len(versions)) 237 for i, version := range versions { 238 t, err := uploadFakeToolsVersion(store, stream, version) 239 if err != nil { 240 panic(err) 241 } 242 agentTools[i] = t 243 } 244 ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory()) 245 err := envtools.MergeAndWriteMetadata(ss, store, stream, stream, agentTools, envtools.DoNotWriteMirrors) 246 if err != nil { 247 panic(err) 248 } 249 return agentTools 250 } 251 252 // UploadFakeTools puts fake tools into the supplied storage with a binary 253 // version matching jujuversion.Current; if jujuversion.Current's os type is different 254 // to the host os type, matching fake tools will be uploaded for that host os type. 255 func UploadFakeTools(c *gc.C, stor storage.Storage, toolsDir, stream string, arches ...string) { 256 if len(arches) == 0 { 257 arches = []string{arch.HostArch()} 258 } 259 toolsOS := set.NewStrings("ubuntu") 260 toolsOS.Add(coreos.HostOSTypeName()) 261 var versions []version.Binary 262 for _, arch := range arches { 263 for _, osType := range toolsOS.Values() { 264 v := version.Binary{ 265 Number: jujuversion.Current, 266 Arch: arch, 267 Release: osType, 268 } 269 versions = append(versions, v) 270 } 271 } 272 c.Logf("uploading fake tool versions: %v", versions) 273 _, err := UploadFakeToolsVersions(stor, toolsDir, stream, versions...) 274 c.Assert(err, jc.ErrorIsNil) 275 } 276 277 // RemoveFakeTools deletes the fake tools from the supplied storage. 278 func RemoveFakeTools(c *gc.C, stor storage.Storage, toolsDir string) { 279 c.Logf("removing fake tools") 280 toolsVersion := coretesting.CurrentVersion() 281 name := envtools.StorageName(toolsVersion, toolsDir) 282 err := stor.Remove(name) 283 c.Check(err, jc.ErrorIsNil) 284 defaultBase := jujuversion.DefaultSupportedLTSBase() 285 if !defaultBase.IsCompatible(coretesting.HostBase(c)) { 286 toolsVersion.Release = "ubuntu" 287 name := envtools.StorageName(toolsVersion, toolsDir) 288 err := stor.Remove(name) 289 c.Check(err, jc.ErrorIsNil) 290 } 291 RemoveFakeToolsMetadata(c, stor) 292 } 293 294 // RemoveTools deletes all tools from the supplied storage. 295 func RemoveTools(c *gc.C, stor storage.Storage, toolsDir string) { 296 names, err := storage.List(stor, fmt.Sprintf("tools/%s/juju-", toolsDir)) 297 c.Assert(err, jc.ErrorIsNil) 298 c.Logf("removing files: %v", names) 299 for _, name := range names { 300 err = stor.Remove(name) 301 c.Check(err, jc.ErrorIsNil) 302 } 303 RemoveFakeToolsMetadata(c, stor) 304 } 305 306 var ( 307 V100 = version.MustParse("1.0.0") 308 V100u64 = version.MustParseBinary("1.0.0-ubuntu-amd64") 309 V100u32 = version.MustParseBinary("1.0.0-ubuntu-arm64") 310 V100p = []version.Binary{V100u64, V100u32} 311 312 V100c64 = version.MustParseBinary("1.0.0-centos-amd64") 313 V100c32 = version.MustParseBinary("1.0.0-centos-arm64") 314 V100q = []version.Binary{V100c64, V100c32} 315 V100all = append(V100p, V100q...) 316 317 V1001 = version.MustParse("1.0.0.1") 318 V1001u64 = version.MustParseBinary("1.0.0.1-ubuntu-amd64") 319 V100Xall = append(V100all, V1001u64) 320 321 V110 = version.MustParse("1.1.0") 322 V110u64 = version.MustParseBinary("1.1.0-ubuntu-amd64") 323 V110u32 = version.MustParseBinary("1.1.0-ubuntu-arm64") 324 V110p = []version.Binary{V110u64, V110u32} 325 326 V110c64 = version.MustParseBinary("1.1.0-centos-amd64") 327 V110c32 = version.MustParseBinary("1.1.0-centos-arm64") 328 V110c = []version.Binary{V110c64, V110c32} 329 V110all = append(V110p, V110c...) 330 331 V120 = version.MustParse("1.2.0") 332 V120u64 = version.MustParseBinary("1.2.0-ubuntu-amd64") 333 V120u32 = version.MustParseBinary("1.2.0-ubuntu-arm64") 334 V120all = []version.Binary{V120u64, V120u32} 335 336 V1all = append(V100Xall, append(V110all, V120all...)...) 337 338 V220 = version.MustParse("2.2.0") 339 V220u32 = version.MustParseBinary("2.2.0-ubuntu-arm64") 340 V220u64 = version.MustParseBinary("2.2.0-ubuntu-amd64") 341 V220all = []version.Binary{V220u64, V220u32} 342 VAll = append(V1all, V220all...) 343 ) 344 345 type BootstrapToolsTest struct { 346 Info string 347 Available []version.Binary 348 CliVersion version.Binary 349 DefaultSeries string 350 AgentVersion version.Number 351 Development bool 352 Arch string 353 Expect []version.Binary 354 Err string 355 } 356 357 var noToolsMessage = "Juju cannot bootstrap because no agent binaries are available for your model.*" 358 359 var BootstrapToolsTests = []BootstrapToolsTest{ 360 { 361 Info: "no tools at all", 362 CliVersion: V100u64, 363 DefaultSeries: "precise", 364 Err: noToolsMessage, 365 }, { 366 Info: "released cli: use newest compatible release version", 367 Available: VAll, 368 CliVersion: V100u64, 369 DefaultSeries: "precise", 370 Expect: V100p, 371 }, { 372 Info: "released cli: cli Arch ignored", 373 Available: VAll, 374 CliVersion: V100u32, 375 DefaultSeries: "precise", 376 Expect: V100p, 377 }, { 378 Info: "released cli: cli series ignored", 379 Available: VAll, 380 CliVersion: V100c64, 381 DefaultSeries: "precise", 382 Expect: V100p, 383 }, { 384 Info: "released cli: series taken from default-series", 385 Available: V120all, 386 CliVersion: V120u64, 387 DefaultSeries: "quantal", 388 Expect: V120all, 389 }, { 390 Info: "released cli: ignore close dev match", 391 Available: V100Xall, 392 CliVersion: V100u64, 393 DefaultSeries: "precise", 394 Expect: V100p, 395 }, { 396 Info: "released cli: filter by arch constraints", 397 Available: V120all, 398 CliVersion: V120u64, 399 DefaultSeries: "precise", 400 Arch: "i386", 401 Expect: []version.Binary{V120u32}, 402 }, { 403 Info: "released cli: specific released version", 404 Available: VAll, 405 CliVersion: V100u64, 406 AgentVersion: V100, 407 DefaultSeries: "precise", 408 Expect: V100p, 409 }, { 410 Info: "released cli: specific dev version", 411 Available: VAll, 412 CliVersion: V110u64, 413 AgentVersion: V110, 414 DefaultSeries: "precise", 415 Expect: V110p, 416 }, { 417 Info: "released cli: major upgrades bad", 418 Available: V220all, 419 CliVersion: V100u64, 420 DefaultSeries: "precise", 421 Err: noToolsMessage, 422 }, { 423 Info: "released cli: minor upgrades bad", 424 Available: V120all, 425 CliVersion: V100u64, 426 DefaultSeries: "precise", 427 Err: noToolsMessage, 428 }, { 429 Info: "released cli: major downgrades bad", 430 Available: V100Xall, 431 CliVersion: V220u64, 432 DefaultSeries: "precise", 433 Err: noToolsMessage, 434 }, { 435 Info: "released cli: minor downgrades bad", 436 Available: V100Xall, 437 CliVersion: V120u64, 438 DefaultSeries: "quantal", 439 Err: noToolsMessage, 440 }, { 441 Info: "released cli: no matching series", 442 Available: VAll, 443 CliVersion: V100u64, 444 DefaultSeries: "raring", 445 Err: noToolsMessage, 446 }, { 447 Info: "released cli: no matching arches", 448 Available: VAll, 449 CliVersion: V100u64, 450 DefaultSeries: "precise", 451 Arch: "armhf", 452 Err: noToolsMessage, 453 }, { 454 Info: "released cli: specific bad major 1", 455 Available: VAll, 456 CliVersion: V220u64, 457 AgentVersion: V120, 458 DefaultSeries: "precise", 459 Err: noToolsMessage, 460 }, { 461 Info: "released cli: specific bad major 2", 462 Available: VAll, 463 CliVersion: V120u64, 464 AgentVersion: V220, 465 DefaultSeries: "precise", 466 Err: noToolsMessage, 467 }, { 468 Info: "released cli: ignore dev tools 1", 469 Available: V110all, 470 CliVersion: V100u64, 471 DefaultSeries: "precise", 472 Err: noToolsMessage, 473 }, { 474 Info: "released cli: ignore dev tools 2", 475 Available: V110all, 476 CliVersion: V120u64, 477 DefaultSeries: "precise", 478 Err: noToolsMessage, 479 }, { 480 Info: "released cli: ignore dev tools 3", 481 Available: []version.Binary{V1001u64}, 482 CliVersion: V100u64, 483 DefaultSeries: "precise", 484 Err: noToolsMessage, 485 }, { 486 Info: "released cli with dev setting respects agent-version", 487 Available: VAll, 488 CliVersion: V100c32, 489 AgentVersion: V1001, 490 DefaultSeries: "precise", 491 Development: true, 492 Expect: []version.Binary{V1001u64}, 493 }, { 494 Info: "dev cli respects agent-version", 495 Available: VAll, 496 CliVersion: V100c32, 497 AgentVersion: V1001, 498 DefaultSeries: "precise", 499 Expect: []version.Binary{V1001u64}, 500 }, { 501 Info: "released cli with dev setting respects agent-version", 502 Available: V1all, 503 CliVersion: V100c32, 504 AgentVersion: V1001, 505 DefaultSeries: "precise", 506 Development: true, 507 Expect: []version.Binary{V1001u64}, 508 }, { 509 Info: "dev cli respects agent-version", 510 Available: V1all, 511 CliVersion: V100c32, 512 AgentVersion: V1001, 513 DefaultSeries: "precise", 514 Expect: []version.Binary{V1001u64}, 515 }}