launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/environs/tools/tools_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package tools_test 5 6 import ( 7 "bytes" 8 "fmt" 9 "io/ioutil" 10 "os" 11 "path" 12 "path/filepath" 13 "strings" 14 "time" 15 16 "github.com/loggo/loggo" 17 gc "launchpad.net/gocheck" 18 19 "launchpad.net/juju-core/environs" 20 "launchpad.net/juju-core/environs/config" 21 "launchpad.net/juju-core/environs/configstore" 22 "launchpad.net/juju-core/environs/simplestreams" 23 "launchpad.net/juju-core/environs/storage" 24 envtesting "launchpad.net/juju-core/environs/testing" 25 envtools "launchpad.net/juju-core/environs/tools" 26 "launchpad.net/juju-core/errors" 27 "launchpad.net/juju-core/provider/dummy" 28 jc "launchpad.net/juju-core/testing/checkers" 29 "launchpad.net/juju-core/testing/testbase" 30 coretools "launchpad.net/juju-core/tools" 31 "launchpad.net/juju-core/version" 32 ) 33 34 type SimpleStreamsToolsSuite struct { 35 env environs.Environ 36 testbase.LoggingSuite 37 envtesting.ToolsFixture 38 origCurrentVersion version.Binary 39 customToolsDir string 40 publicToolsDir string 41 } 42 43 func setupToolsTests() { 44 gc.Suite(&SimpleStreamsToolsSuite{}) 45 } 46 47 func (s *SimpleStreamsToolsSuite) SetUpSuite(c *gc.C) { 48 s.LoggingSuite.SetUpSuite(c) 49 s.customToolsDir = c.MkDir() 50 s.publicToolsDir = c.MkDir() 51 } 52 53 func (s *SimpleStreamsToolsSuite) SetUpTest(c *gc.C) { 54 s.ToolsFixture.DefaultBaseURL = "file://" + s.publicToolsDir 55 s.LoggingSuite.SetUpTest(c) 56 s.ToolsFixture.SetUpTest(c) 57 s.origCurrentVersion = version.Current 58 s.reset(c, nil) 59 } 60 61 func (s *SimpleStreamsToolsSuite) TearDownTest(c *gc.C) { 62 dummy.Reset() 63 version.Current = s.origCurrentVersion 64 s.ToolsFixture.TearDownTest(c) 65 s.LoggingSuite.TearDownTest(c) 66 } 67 68 func (s *SimpleStreamsToolsSuite) reset(c *gc.C, attrs map[string]interface{}) { 69 final := map[string]interface{}{ 70 "tools-metadata-url": "file://" + s.customToolsDir, 71 } 72 for k, v := range attrs { 73 final[k] = v 74 } 75 s.resetEnv(c, final) 76 } 77 78 func (s *SimpleStreamsToolsSuite) removeTools(c *gc.C) { 79 for _, dir := range []string{s.customToolsDir, s.publicToolsDir} { 80 files, err := ioutil.ReadDir(dir) 81 c.Assert(err, gc.IsNil) 82 for _, f := range files { 83 err := os.RemoveAll(filepath.Join(dir, f.Name())) 84 c.Assert(err, gc.IsNil) 85 } 86 } 87 } 88 89 type metadataFile struct { 90 path string 91 data []byte 92 } 93 94 func (s *SimpleStreamsToolsSuite) generateMetadata(c *gc.C, verses ...version.Binary) []metadataFile { 95 var metadata = make([]*envtools.ToolsMetadata, len(verses)) 96 for i, vers := range verses { 97 basePath := fmt.Sprintf("releases/tools-%s.tar.gz", vers.String()) 98 metadata[i] = &envtools.ToolsMetadata{ 99 Release: vers.Series, 100 Version: vers.Number.String(), 101 Arch: vers.Arch, 102 Path: basePath, 103 } 104 } 105 index, products, err := envtools.MarshalToolsMetadataJSON(metadata, time.Now()) 106 c.Assert(err, gc.IsNil) 107 objects := []metadataFile{ 108 {simplestreams.UnsignedIndex, index}, 109 {envtools.ProductMetadataPath, products}, 110 } 111 return objects 112 } 113 114 func (s *SimpleStreamsToolsSuite) uploadToStorage(c *gc.C, stor storage.Storage, verses ...version.Binary) map[version.Binary]string { 115 uploaded := map[version.Binary]string{} 116 if len(verses) == 0 { 117 return uploaded 118 } 119 var err error 120 for _, vers := range verses { 121 filename := fmt.Sprintf("tools/releases/tools-%s.tar.gz", vers.String()) 122 // Put a file in images since the dummy storage provider requires a 123 // file to exist before the URL can be found. This is to ensure it behaves 124 // the same way as MAAS. 125 err = stor.Put(filename, strings.NewReader("dummy"), 5) 126 c.Assert(err, gc.IsNil) 127 uploaded[vers], err = stor.URL(filename) 128 c.Assert(err, gc.IsNil) 129 } 130 objects := s.generateMetadata(c, verses...) 131 for _, object := range objects { 132 toolspath := path.Join("tools", object.path) 133 err = stor.Put(toolspath, bytes.NewReader(object.data), int64(len(object.data))) 134 c.Assert(err, gc.IsNil) 135 } 136 return uploaded 137 } 138 139 func (s *SimpleStreamsToolsSuite) uploadVersions(c *gc.C, dir string, verses ...version.Binary) map[version.Binary]string { 140 uploaded := map[version.Binary]string{} 141 if len(verses) == 0 { 142 return uploaded 143 } 144 for _, vers := range verses { 145 basePath := fmt.Sprintf("releases/tools-%s.tar.gz", vers.String()) 146 uploaded[vers] = fmt.Sprintf("file://%s/%s", dir, basePath) 147 } 148 objects := s.generateMetadata(c, verses...) 149 for _, object := range objects { 150 path := filepath.Join(dir, object.path) 151 dir := filepath.Dir(path) 152 if err := os.MkdirAll(dir, 0755); err != nil && !os.IsExist(err) { 153 c.Assert(err, gc.IsNil) 154 } 155 err := ioutil.WriteFile(path, object.data, 0644) 156 c.Assert(err, gc.IsNil) 157 } 158 return uploaded 159 } 160 161 func (s *SimpleStreamsToolsSuite) uploadCustom(c *gc.C, verses ...version.Binary) map[version.Binary]string { 162 return s.uploadVersions(c, s.customToolsDir, verses...) 163 } 164 165 func (s *SimpleStreamsToolsSuite) uploadPublic(c *gc.C, verses ...version.Binary) map[version.Binary]string { 166 return s.uploadVersions(c, s.publicToolsDir, verses...) 167 } 168 169 func (s *SimpleStreamsToolsSuite) resetEnv(c *gc.C, attrs map[string]interface{}) { 170 version.Current = s.origCurrentVersion 171 dummy.Reset() 172 cfg, err := config.New(config.NoDefaults, dummy.SampleConfig().Merge(attrs)) 173 c.Assert(err, gc.IsNil) 174 env, err := environs.Prepare(cfg, configstore.NewMem()) 175 c.Assert(err, gc.IsNil) 176 s.env = env 177 s.removeTools(c) 178 } 179 180 var findToolsTests = []struct { 181 info string 182 major int 183 minor int 184 custom []version.Binary 185 public []version.Binary 186 expect []version.Binary 187 err error 188 }{{ 189 info: "none available anywhere", 190 major: 1, 191 err: envtools.ErrNoTools, 192 }, { 193 info: "custom/private tools only, none matching", 194 major: 1, 195 minor: 2, 196 custom: envtesting.V220all, 197 err: coretools.ErrNoMatches, 198 }, { 199 info: "custom tools found", 200 major: 1, 201 minor: 2, 202 custom: envtesting.VAll, 203 expect: envtesting.V120all, 204 }, { 205 info: "public tools found", 206 major: 1, 207 minor: 1, 208 public: envtesting.VAll, 209 expect: envtesting.V110all, 210 }, { 211 info: "public and custom tools found, only taken from custom", 212 major: 1, 213 minor: 1, 214 custom: envtesting.V110p, 215 public: envtesting.VAll, 216 expect: envtesting.V110p, 217 }, { 218 info: "custom tools completely block public ones", 219 major: 1, 220 minor: -1, 221 custom: envtesting.V220all, 222 public: envtesting.VAll, 223 expect: envtesting.V1all, 224 }, { 225 info: "tools matching major version only", 226 major: 1, 227 minor: -1, 228 public: envtesting.VAll, 229 expect: envtesting.V1all, 230 }} 231 232 func (s *SimpleStreamsToolsSuite) TestFindTools(c *gc.C) { 233 for i, test := range findToolsTests { 234 c.Logf("\ntest %d: %s", i, test.info) 235 s.reset(c, nil) 236 custom := s.uploadCustom(c, test.custom...) 237 public := s.uploadPublic(c, test.public...) 238 actual, err := envtools.FindTools(s.env, test.major, test.minor, coretools.Filter{}, envtools.DoNotAllowRetry) 239 if test.err != nil { 240 if len(actual) > 0 { 241 c.Logf(actual.String()) 242 } 243 c.Check(err, jc.Satisfies, errors.IsNotFoundError) 244 continue 245 } 246 expect := map[version.Binary]string{} 247 for _, expected := range test.expect { 248 // If the tools exist in custom, that's preferred. 249 var ok bool 250 if expect[expected], ok = custom[expected]; !ok { 251 expect[expected] = public[expected] 252 } 253 } 254 c.Check(actual.URLs(), gc.DeepEquals, expect) 255 } 256 } 257 258 func (s *SimpleStreamsToolsSuite) TestFindToolsInControlBucket(c *gc.C) { 259 s.reset(c, nil) 260 custom := s.uploadToStorage(c, s.env.Storage(), envtesting.V110p...) 261 s.uploadPublic(c, envtesting.VAll...) 262 actual, err := envtools.FindTools(s.env, 1, 1, coretools.Filter{}, envtools.DoNotAllowRetry) 263 c.Assert(err, gc.IsNil) 264 expect := map[version.Binary]string{} 265 for _, expected := range envtesting.V110p { 266 expect[expected] = custom[expected] 267 } 268 c.Assert(actual.URLs(), gc.DeepEquals, expect) 269 } 270 271 func (s *SimpleStreamsToolsSuite) TestFindToolsFiltering(c *gc.C) { 272 tw := &loggo.TestWriter{} 273 c.Assert(loggo.RegisterWriter("filter-tester", tw, loggo.DEBUG), gc.IsNil) 274 defer loggo.RemoveWriter("filter-tester") 275 _, err := envtools.FindTools( 276 s.env, 1, -1, coretools.Filter{Number: version.Number{Major: 1, Minor: 2, Patch: 3}}, envtools.DoNotAllowRetry) 277 c.Assert(err, jc.Satisfies, errors.IsNotFoundError) 278 // This is slightly overly prescriptive, but feel free to change or add 279 // messages. This still helps to ensure that all log messages are 280 // properly formed. 281 messages := []jc.SimpleMessage{ 282 {loggo.INFO, "reading tools with major version 1"}, 283 {loggo.INFO, "filtering tools by version: \\d+\\.\\d+\\.\\d+"}, 284 {loggo.DEBUG, "no architecture specified when finding tools, looking for any"}, 285 {loggo.DEBUG, "no series specified when finding tools, looking for any"}, 286 } 287 sources, err := envtools.GetMetadataSources(s.env) 288 c.Assert(err, gc.IsNil) 289 for i := 0; i < 2*len(sources); i++ { 290 messages = append(messages, 291 jc.SimpleMessage{loggo.DEBUG, `fetchData failed for .*`}, 292 jc.SimpleMessage{loggo.DEBUG, `cannot load index .*`}) 293 } 294 c.Check(tw.Log, jc.LogMatches, messages) 295 } 296 297 func (s *SimpleStreamsToolsSuite) TestFindBootstrapTools(c *gc.C) { 298 // Remove the default tools URL from the search path, just look in cloud storage. 299 s.PatchValue(&envtools.DefaultBaseURL, "") 300 for i, test := range envtesting.BootstrapToolsTests { 301 c.Logf("\ntest %d: %s", i, test.Info) 302 attrs := map[string]interface{}{ 303 "development": test.Development, 304 } 305 var agentVersion *version.Number 306 if test.AgentVersion != version.Zero { 307 attrs["agent-version"] = test.AgentVersion.String() 308 agentVersion = &test.AgentVersion 309 } 310 s.reset(c, attrs) 311 version.Current = test.CliVersion 312 available := s.uploadCustom(c, test.Available...) 313 314 params := envtools.BootstrapToolsParams{ 315 Version: agentVersion, 316 Series: test.DefaultSeries, 317 Arch: &test.Arch, 318 } 319 actual, err := envtools.FindBootstrapTools(s.env, params) 320 if test.Err != nil { 321 if len(actual) > 0 { 322 c.Logf(actual.String()) 323 } 324 c.Check(err, jc.Satisfies, errors.IsNotFoundError) 325 continue 326 } 327 expect := map[version.Binary]string{} 328 for _, expected := range test.Expect { 329 expect[expected] = available[expected] 330 } 331 c.Check(actual.URLs(), gc.DeepEquals, expect) 332 } 333 } 334 335 var findInstanceToolsTests = []struct { 336 info string 337 available []version.Binary 338 agentVersion version.Number 339 series string 340 arch string 341 expect []version.Binary 342 err error 343 }{{ 344 info: "nothing at all", 345 agentVersion: envtesting.V120, 346 series: "precise", 347 err: envtools.ErrNoTools, 348 }, { 349 info: "nothing matching 1", 350 available: envtesting.V100Xall, 351 agentVersion: envtesting.V120, 352 series: "precise", 353 err: coretools.ErrNoMatches, 354 }, { 355 info: "nothing matching 2", 356 available: envtesting.V120all, 357 agentVersion: envtesting.V110, 358 series: "precise", 359 err: coretools.ErrNoMatches, 360 }, { 361 info: "nothing matching 3", 362 available: envtesting.V120q, 363 agentVersion: envtesting.V120, 364 series: "precise", 365 err: coretools.ErrNoMatches, 366 }, { 367 info: "nothing matching 4", 368 available: envtesting.V120q, 369 agentVersion: envtesting.V120, 370 series: "quantal", 371 arch: "arm", 372 err: coretools.ErrNoMatches, 373 }, { 374 info: "actual match 1", 375 available: envtesting.VAll, 376 agentVersion: envtesting.V1001, 377 series: "precise", 378 expect: []version.Binary{envtesting.V1001p64}, 379 }, { 380 info: "actual match 2", 381 available: envtesting.VAll, 382 agentVersion: envtesting.V120, 383 series: "quantal", 384 expect: []version.Binary{envtesting.V120q64, envtesting.V120q32}, 385 }, { 386 info: "actual match 3", 387 available: envtesting.VAll, 388 agentVersion: envtesting.V110, 389 series: "quantal", 390 arch: "i386", 391 expect: []version.Binary{envtesting.V110q32}, 392 }} 393 394 func (s *SimpleStreamsToolsSuite) TestFindInstanceTools(c *gc.C) { 395 for i, test := range findInstanceToolsTests { 396 c.Logf("\ntest %d: %s", i, test.info) 397 s.reset(c, map[string]interface{}{ 398 "agent-version": test.agentVersion.String(), 399 }) 400 available := s.uploadCustom(c, test.available...) 401 402 agentVersion, _ := s.env.Config().AgentVersion() 403 actual, err := envtools.FindInstanceTools(s.env, agentVersion, test.series, &test.arch) 404 if test.err != nil { 405 if len(actual) > 0 { 406 c.Logf(actual.String()) 407 } 408 c.Check(err, jc.Satisfies, errors.IsNotFoundError) 409 continue 410 } 411 expect := map[version.Binary]string{} 412 for _, expected := range test.expect { 413 expect[expected] = available[expected] 414 } 415 c.Check(actual.URLs(), gc.DeepEquals, expect) 416 } 417 } 418 419 var findExactToolsTests = []struct { 420 info string 421 custom []version.Binary 422 public []version.Binary 423 seek version.Binary 424 err error 425 }{{ 426 info: "nothing available", 427 seek: envtesting.V100p64, 428 err: envtools.ErrNoTools, 429 }, { 430 info: "only non-matches available in custom", 431 custom: append(envtesting.V110all, envtesting.V100p32, envtesting.V100q64, envtesting.V1001p64), 432 seek: envtesting.V100p64, 433 err: coretools.ErrNoMatches, 434 }, { 435 info: "exact match available in custom", 436 custom: []version.Binary{envtesting.V100p64}, 437 seek: envtesting.V100p64, 438 }, { 439 info: "only non-matches available in public", 440 custom: append(envtesting.V110all, envtesting.V100p32, envtesting.V100q64, envtesting.V1001p64), 441 seek: envtesting.V100p64, 442 err: coretools.ErrNoMatches, 443 }, { 444 info: "exact match available in public", 445 public: []version.Binary{envtesting.V100p64}, 446 seek: envtesting.V100p64, 447 }, { 448 info: "exact match in public not blocked by custom", 449 custom: envtesting.V110all, 450 public: []version.Binary{envtesting.V100p64}, 451 seek: envtesting.V100p64, 452 }} 453 454 func (s *SimpleStreamsToolsSuite) TestFindExactTools(c *gc.C) { 455 for i, test := range findExactToolsTests { 456 c.Logf("\ntest %d: %s", i, test.info) 457 s.reset(c, nil) 458 custom := s.uploadCustom(c, test.custom...) 459 public := s.uploadPublic(c, test.public...) 460 actual, err := envtools.FindExactTools(s.env, test.seek.Number, test.seek.Series, test.seek.Arch) 461 if test.err == nil { 462 if !c.Check(err, gc.IsNil) { 463 continue 464 } 465 c.Check(actual.Version, gc.Equals, test.seek) 466 if _, ok := custom[actual.Version]; ok { 467 c.Check(actual.URL, gc.DeepEquals, custom[actual.Version]) 468 } else { 469 c.Check(actual.URL, gc.DeepEquals, public[actual.Version]) 470 } 471 } else { 472 c.Check(err, jc.Satisfies, errors.IsNotFoundError) 473 } 474 } 475 } 476 477 // fakeToolsForSeries fakes a Tools object with just enough information for 478 // testing the handling its OS series. 479 func fakeToolsForSeries(series string) *coretools.Tools { 480 return &coretools.Tools{Version: version.Binary{Series: series}} 481 } 482 483 // fakeToolsList fakes a envtools.List containing Tools objects for the given 484 // respective series, in the same number and order. 485 func fakeToolsList(series ...string) coretools.List { 486 list := coretools.List{} 487 for _, name := range series { 488 list = append(list, fakeToolsForSeries(name)) 489 } 490 return list 491 } 492 493 type ToolsListSuite struct{} 494 495 func (s *ToolsListSuite) TestCheckToolsSeriesRequiresTools(c *gc.C) { 496 err := envtools.CheckToolsSeries(fakeToolsList(), "precise") 497 c.Assert(err, gc.NotNil) 498 c.Check(err, gc.ErrorMatches, "expected single series, got \\[\\]") 499 } 500 501 func (s *ToolsListSuite) TestCheckToolsSeriesAcceptsOneSetOfTools(c *gc.C) { 502 names := []string{"precise", "raring"} 503 for _, series := range names { 504 list := fakeToolsList(series) 505 err := envtools.CheckToolsSeries(list, series) 506 c.Check(err, gc.IsNil) 507 } 508 } 509 510 func (s *ToolsListSuite) TestCheckToolsSeriesAcceptsMultipleForSameSeries(c *gc.C) { 511 series := "quantal" 512 list := fakeToolsList(series, series, series) 513 err := envtools.CheckToolsSeries(list, series) 514 c.Check(err, gc.IsNil) 515 } 516 517 func (s *ToolsListSuite) TestCheckToolsSeriesRejectsToolsForOtherSeries(c *gc.C) { 518 list := fakeToolsList("hoary") 519 err := envtools.CheckToolsSeries(list, "warty") 520 c.Assert(err, gc.NotNil) 521 c.Check(err, gc.ErrorMatches, "tools mismatch: expected series warty, got hoary") 522 } 523 524 func (s *ToolsListSuite) TestCheckToolsSeriesRejectsToolsForMixedSeries(c *gc.C) { 525 list := fakeToolsList("precise", "raring") 526 err := envtools.CheckToolsSeries(list, "precise") 527 c.Assert(err, gc.NotNil) 528 c.Check(err, gc.ErrorMatches, "expected single series, got .*") 529 }