github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "io/ioutil" 8 "os" 9 "path/filepath" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 jc "github.com/juju/testing/checkers" 14 "github.com/juju/utils" 15 "github.com/juju/version" 16 gc "gopkg.in/check.v1" 17 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/environs/bootstrap" 20 sstesting "github.com/juju/juju/environs/simplestreams/testing" 21 envtesting "github.com/juju/juju/environs/testing" 22 envtools "github.com/juju/juju/environs/tools" 23 toolstesting "github.com/juju/juju/environs/tools/testing" 24 "github.com/juju/juju/juju/keys" 25 "github.com/juju/juju/jujuclient" 26 "github.com/juju/juju/provider/dummy" 27 coretesting "github.com/juju/juju/testing" 28 coretools "github.com/juju/juju/tools" 29 jujuversion "github.com/juju/juju/version" 30 ) 31 32 type SimpleStreamsToolsSuite struct { 33 env environs.Environ 34 coretesting.BaseSuite 35 envtesting.ToolsFixture 36 origCurrentVersion version.Number 37 customToolsDir string 38 publicToolsDir string 39 } 40 41 func setupToolsTests() { 42 gc.Suite(&SimpleStreamsToolsSuite{}) 43 gc.Suite(&ToolsListSuite{}) 44 } 45 46 func (s *SimpleStreamsToolsSuite) SetUpSuite(c *gc.C) { 47 s.BaseSuite.SetUpSuite(c) 48 s.customToolsDir = c.MkDir() 49 s.publicToolsDir = c.MkDir() 50 s.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey) 51 } 52 53 func (s *SimpleStreamsToolsSuite) SetUpTest(c *gc.C) { 54 s.ToolsFixture.DefaultBaseURL = utils.MakeFileURL(s.publicToolsDir) 55 s.BaseSuite.SetUpTest(c) 56 s.ToolsFixture.SetUpTest(c) 57 s.origCurrentVersion = jujuversion.Current 58 s.reset(c, nil) 59 } 60 61 func (s *SimpleStreamsToolsSuite) TearDownTest(c *gc.C) { 62 dummy.Reset(c) 63 jujuversion.Current = s.origCurrentVersion 64 s.ToolsFixture.TearDownTest(c) 65 s.BaseSuite.TearDownTest(c) 66 } 67 68 func (s *SimpleStreamsToolsSuite) reset(c *gc.C, attrs map[string]interface{}) { 69 final := map[string]interface{}{ 70 "agent-metadata-url": utils.MakeFileURL(s.customToolsDir), 71 "agent-stream": "proposed", 72 } 73 for k, v := range attrs { 74 final[k] = v 75 } 76 s.resetEnv(c, final) 77 } 78 79 func (s *SimpleStreamsToolsSuite) removeTools(c *gc.C) { 80 for _, dir := range []string{s.customToolsDir, s.publicToolsDir} { 81 files, err := ioutil.ReadDir(dir) 82 c.Assert(err, jc.ErrorIsNil) 83 for _, f := range files { 84 err := os.RemoveAll(filepath.Join(dir, f.Name())) 85 c.Assert(err, jc.ErrorIsNil) 86 } 87 } 88 } 89 90 func (s *SimpleStreamsToolsSuite) uploadCustom(c *gc.C, verses ...version.Binary) map[version.Binary]string { 91 return toolstesting.UploadToDirectory(c, s.customToolsDir, toolstesting.StreamVersions{"proposed": verses})["proposed"] 92 } 93 94 func (s *SimpleStreamsToolsSuite) uploadPublic(c *gc.C, verses ...version.Binary) map[version.Binary]string { 95 return toolstesting.UploadToDirectory(c, s.publicToolsDir, toolstesting.StreamVersions{"proposed": verses})["proposed"] 96 } 97 98 func (s *SimpleStreamsToolsSuite) uploadStreams(c *gc.C, versions toolstesting.StreamVersions) map[string]map[version.Binary]string { 99 return toolstesting.UploadToDirectory(c, s.publicToolsDir, versions) 100 } 101 102 func (s *SimpleStreamsToolsSuite) resetEnv(c *gc.C, attrs map[string]interface{}) { 103 jujuversion.Current = s.origCurrentVersion 104 dummy.Reset(c) 105 attrs = dummy.SampleConfig().Merge(attrs) 106 env, err := bootstrap.PrepareController(false, envtesting.BootstrapContext(c), 107 jujuclient.NewMemStore(), 108 bootstrap.PrepareParams{ 109 ControllerConfig: coretesting.FakeControllerConfig(), 110 ControllerName: attrs["name"].(string), 111 ModelConfig: attrs, 112 Cloud: dummy.SampleCloudSpec(), 113 AdminSecret: "admin-secret", 114 }, 115 ) 116 c.Assert(err, jc.ErrorIsNil) 117 s.env = env.(environs.Environ) 118 s.removeTools(c) 119 } 120 121 var findToolsTests = []struct { 122 info string 123 major int 124 minor int 125 custom []version.Binary 126 public []version.Binary 127 expect []version.Binary 128 err error 129 }{{ 130 info: "none available anywhere", 131 major: 1, 132 err: envtools.ErrNoTools, 133 }, { 134 info: "custom/private tools only, none matching", 135 major: 1, 136 minor: 2, 137 custom: envtesting.V220all, 138 err: coretools.ErrNoMatches, 139 }, { 140 info: "custom tools found", 141 major: 1, 142 minor: 2, 143 custom: envtesting.VAll, 144 expect: envtesting.V120all, 145 }, { 146 info: "public tools found", 147 major: 1, 148 minor: 1, 149 public: envtesting.VAll, 150 expect: envtesting.V110all, 151 }, { 152 info: "public and custom tools found, only taken from custom", 153 major: 1, 154 minor: 1, 155 custom: envtesting.V110p, 156 public: envtesting.VAll, 157 expect: envtesting.V110p, 158 }, { 159 info: "custom tools completely block public ones", 160 major: 1, 161 minor: -1, 162 custom: envtesting.V220all, 163 public: envtesting.VAll, 164 expect: envtesting.V1all, 165 }, { 166 info: "tools matching major version only", 167 major: 1, 168 minor: -1, 169 public: envtesting.VAll, 170 expect: envtesting.V1all, 171 }} 172 173 func (s *SimpleStreamsToolsSuite) TestFindTools(c *gc.C) { 174 for i, test := range findToolsTests { 175 c.Logf("\ntest %d: %s", i, test.info) 176 s.reset(c, nil) 177 custom := s.uploadCustom(c, test.custom...) 178 public := s.uploadPublic(c, test.public...) 179 streams := envtools.PreferredStreams(&jujuversion.Current, s.env.Config().Development(), s.env.Config().AgentStream()) 180 actual, err := envtools.FindTools(s.env, test.major, test.minor, streams, coretools.Filter{}) 181 if test.err != nil { 182 if len(actual) > 0 { 183 c.Logf(actual.String()) 184 } 185 c.Check(err, jc.Satisfies, errors.IsNotFound) 186 continue 187 } 188 expect := map[version.Binary][]string{} 189 for _, expected := range test.expect { 190 // If the tools exist in custom, that's preferred. 191 url, ok := custom[expected] 192 if !ok { 193 url = public[expected] 194 } 195 expect[expected] = append(expect[expected], url) 196 } 197 c.Check(actual.URLs(), gc.DeepEquals, expect) 198 } 199 } 200 201 func (s *SimpleStreamsToolsSuite) TestFindToolsFiltering(c *gc.C) { 202 var tw loggo.TestWriter 203 c.Assert(loggo.RegisterWriter("filter-tester", &tw), gc.IsNil) 204 defer loggo.RemoveWriter("filter-tester") 205 logger := loggo.GetLogger("juju.environs") 206 defer logger.SetLogLevel(logger.LogLevel()) 207 logger.SetLogLevel(loggo.TRACE) 208 209 _, err := envtools.FindTools( 210 s.env, 1, -1, []string{"released"}, coretools.Filter{Number: version.Number{Major: 1, Minor: 2, Patch: 3}}) 211 c.Assert(err, jc.Satisfies, errors.IsNotFound) 212 // This is slightly overly prescriptive, but feel free to change or add 213 // messages. This still helps to ensure that all log messages are 214 // properly formed. 215 messages := []jc.SimpleMessage{ 216 {loggo.DEBUG, "reading agent binaries with major version 1"}, 217 {loggo.DEBUG, "filtering agent binaries by version: \\d+\\.\\d+\\.\\d+"}, 218 {loggo.TRACE, "no architecture specified when finding agent binaries, looking for "}, 219 {loggo.TRACE, "no series specified when finding agent binaries, looking for \\[.*\\]"}, 220 } 221 sources, err := envtools.GetMetadataSources(s.env) 222 c.Assert(err, jc.ErrorIsNil) 223 for i := 0; i < len(sources); i++ { 224 messages = append(messages, 225 jc.SimpleMessage{loggo.TRACE, `fetchData failed for .*`}, 226 jc.SimpleMessage{loggo.TRACE, `cannot load index .*`}) 227 } 228 c.Check(tw.Log(), jc.LogMatches, messages) 229 } 230 231 var findExactToolsTests = []struct { 232 info string 233 // These are the contents of the proposed streams in each source. 234 custom []version.Binary 235 public []version.Binary 236 seek version.Binary 237 err error 238 }{{ 239 info: "nothing available", 240 seek: envtesting.V100p64, 241 err: envtools.ErrNoTools, 242 }, { 243 info: "only non-matches available in custom", 244 custom: append(envtesting.V110all, envtesting.V100p32, envtesting.V100q64, envtesting.V1001p64), 245 seek: envtesting.V100p64, 246 err: coretools.ErrNoMatches, 247 }, { 248 info: "exact match available in custom", 249 custom: []version.Binary{envtesting.V100p64}, 250 seek: envtesting.V100p64, 251 }, { 252 info: "only non-matches available in public", 253 custom: append(envtesting.V110all, envtesting.V100p32, envtesting.V100q64, envtesting.V1001p64), 254 seek: envtesting.V100p64, 255 err: coretools.ErrNoMatches, 256 }, { 257 info: "exact match available in public", 258 public: []version.Binary{envtesting.V100p64}, 259 seek: envtesting.V100p64, 260 }, { 261 info: "exact match in public not blocked by custom", 262 custom: envtesting.V110all, 263 public: []version.Binary{envtesting.V100p64}, 264 seek: envtesting.V100p64, 265 }} 266 267 func (s *SimpleStreamsToolsSuite) TestFindExactTools(c *gc.C) { 268 for i, test := range findExactToolsTests { 269 c.Logf("\ntest %d: %s", i, test.info) 270 s.reset(c, nil) 271 custom := s.uploadCustom(c, test.custom...) 272 public := s.uploadPublic(c, test.public...) 273 actual, err := envtools.FindExactTools(s.env, test.seek.Number, test.seek.Series, test.seek.Arch) 274 if test.err == nil { 275 if !c.Check(err, jc.ErrorIsNil) { 276 continue 277 } 278 c.Check(actual.Version, gc.Equals, test.seek) 279 if _, ok := custom[actual.Version]; ok { 280 c.Check(actual.URL, gc.DeepEquals, custom[actual.Version]) 281 } else { 282 c.Check(actual.URL, gc.DeepEquals, public[actual.Version]) 283 } 284 } else { 285 c.Check(err, jc.Satisfies, errors.IsNotFound) 286 } 287 } 288 } 289 290 func copyAndAppend(vs []version.Binary, more ...[]version.Binary) []version.Binary { 291 // TODO(babbageclunk): I think the append(someversions, 292 // moreversions...) technique used in environs/testing/tools.go 293 // might be wrong because it can mutate someversions if there's 294 // enough capacity. Use this there. 295 // https://medium.com/@Jarema./golang-slice-append-gotcha-e9020ff37374 296 result := make([]version.Binary, len(vs)) 297 copy(result, vs) 298 for _, items := range more { 299 result = append(result, items...) 300 } 301 return result 302 } 303 304 var findToolsFallbackTests = []struct { 305 info string 306 major int 307 minor int 308 streams []string 309 devel []version.Binary 310 proposed []version.Binary 311 released []version.Binary 312 expect []version.Binary 313 err error 314 }{{ 315 info: "nothing available", 316 major: 1, 317 streams: []string{"released"}, 318 err: envtools.ErrNoTools, 319 }, { 320 info: "only available in non-selected stream", 321 major: 1, 322 minor: 2, 323 streams: []string{"released"}, 324 devel: envtesting.VAll, 325 err: coretools.ErrNoMatches, 326 }, { 327 info: "finds things in devel and released, ignores proposed", 328 major: 1, 329 minor: -1, 330 streams: []string{"devel", "released"}, 331 devel: envtesting.V120all, 332 proposed: envtesting.V110all, 333 released: envtesting.V100all, 334 expect: copyAndAppend(envtesting.V120all, envtesting.V100all), 335 }, { 336 info: "finds matching things everywhere", 337 major: 1, 338 minor: 2, 339 streams: []string{"devel", "proposed", "released"}, 340 devel: []version.Binary{envtesting.V110q64, envtesting.V120q64}, 341 proposed: []version.Binary{envtesting.V110p64, envtesting.V120p64}, 342 released: []version.Binary{envtesting.V100p64, envtesting.V120t64}, 343 expect: []version.Binary{envtesting.V120p64, envtesting.V120q64, envtesting.V120t64}, 344 }} 345 346 func (s *SimpleStreamsToolsSuite) TestFindToolsWithStreamFallback(c *gc.C) { 347 for i, test := range findToolsFallbackTests { 348 c.Logf("\ntest %d: %s", i, test.info) 349 s.reset(c, nil) 350 streams := s.uploadStreams(c, toolstesting.StreamVersions{ 351 "devel": test.devel, 352 "proposed": test.proposed, 353 "released": test.released, 354 }) 355 actual, err := envtools.FindTools(s.env, test.major, test.minor, test.streams, coretools.Filter{}) 356 if test.err != nil { 357 if len(actual) > 0 { 358 c.Logf(actual.String()) 359 } 360 c.Check(err, jc.Satisfies, errors.IsNotFound) 361 continue 362 } 363 expect := map[version.Binary][]string{} 364 for _, expected := range test.expect { 365 for _, stream := range []string{"devel", "proposed", "released"} { 366 if url, ok := streams[stream][expected]; ok { 367 expect[expected] = []string{url} 368 break 369 } 370 } 371 } 372 c.Check(actual.URLs(), gc.DeepEquals, expect) 373 } 374 } 375 376 var preferredStreamTests = []struct { 377 explicitVers string 378 currentVers string 379 forceDevel bool 380 streamInConfig string 381 expected []string 382 }{{ 383 currentVers: "1.22.0", 384 streamInConfig: "released", 385 expected: []string{"released"}, 386 }, { 387 currentVers: "1.22.0", 388 streamInConfig: "proposed", 389 expected: []string{"proposed", "released"}, 390 }, { 391 currentVers: "1.22.0", 392 streamInConfig: "devel", 393 expected: []string{"devel", "proposed", "released"}, 394 }, { 395 currentVers: "1.22.0", 396 streamInConfig: "testing", 397 expected: []string{"testing", "devel", "proposed", "released"}, 398 }, { 399 currentVers: "1.22.0", 400 expected: []string{"released"}, 401 }, { 402 currentVers: "1.22-beta1", 403 expected: []string{"devel", "proposed", "released"}, 404 }, { 405 currentVers: "1.22-beta1", 406 streamInConfig: "released", 407 expected: []string{"devel", "proposed", "released"}, 408 }, { 409 currentVers: "1.22-beta1", 410 streamInConfig: "devel", 411 expected: []string{"devel", "proposed", "released"}, 412 }, { 413 currentVers: "1.22.0", 414 forceDevel: true, 415 expected: []string{"devel", "proposed", "released"}, 416 }, { 417 currentVers: "1.22.0", 418 explicitVers: "1.22-beta1", 419 expected: []string{"devel", "proposed", "released"}, 420 }, { 421 currentVers: "1.22-bta1", 422 explicitVers: "1.22.0", 423 expected: []string{"released"}, 424 }} 425 426 func (s *SimpleStreamsToolsSuite) TestPreferredStreams(c *gc.C) { 427 for i, test := range preferredStreamTests { 428 c.Logf("\ntest %d", i) 429 s.PatchValue(&jujuversion.Current, version.MustParse(test.currentVers)) 430 var vers *version.Number 431 if test.explicitVers != "" { 432 v := version.MustParse(test.explicitVers) 433 vers = &v 434 } 435 obtained := envtools.PreferredStreams(vers, test.forceDevel, test.streamInConfig) 436 c.Check(obtained, gc.DeepEquals, test.expected) 437 } 438 } 439 440 // fakeToolsForSeries fakes a Tools object with just enough information for 441 // testing the handling its OS series. 442 func fakeToolsForSeries(series string) *coretools.Tools { 443 return &coretools.Tools{Version: version.Binary{Series: series}} 444 } 445 446 // fakeToolsList fakes a envtools.List containing Tools objects for the given 447 // respective series, in the same number and order. 448 func fakeToolsList(series ...string) coretools.List { 449 list := coretools.List{} 450 for _, name := range series { 451 list = append(list, fakeToolsForSeries(name)) 452 } 453 return list 454 } 455 456 type ToolsListSuite struct{} 457 458 func (s *ToolsListSuite) TestCheckToolsSeriesRequiresTools(c *gc.C) { 459 err := envtools.CheckToolsSeries(fakeToolsList(), "precise") 460 c.Assert(err, gc.NotNil) 461 c.Check(err, gc.ErrorMatches, "expected single series, got \\[\\]") 462 } 463 464 func (s *ToolsListSuite) TestCheckToolsSeriesAcceptsOneSetOfTools(c *gc.C) { 465 names := []string{"precise", "raring"} 466 for _, series := range names { 467 list := fakeToolsList(series) 468 err := envtools.CheckToolsSeries(list, series) 469 c.Check(err, jc.ErrorIsNil) 470 } 471 } 472 473 func (s *ToolsListSuite) TestCheckToolsSeriesAcceptsMultipleForSameSeries(c *gc.C) { 474 series := "quantal" 475 list := fakeToolsList(series, series, series) 476 err := envtools.CheckToolsSeries(list, series) 477 c.Check(err, jc.ErrorIsNil) 478 } 479 480 func (s *ToolsListSuite) TestCheckToolsSeriesRejectsToolsForOtherSeries(c *gc.C) { 481 list := fakeToolsList("hoary") 482 err := envtools.CheckToolsSeries(list, "warty") 483 c.Assert(err, gc.NotNil) 484 c.Check(err, gc.ErrorMatches, "agent binary mismatch: expected series warty, got hoary") 485 } 486 487 func (s *ToolsListSuite) TestCheckToolsSeriesRejectsToolsForMixedSeries(c *gc.C) { 488 list := fakeToolsList("precise", "raring") 489 err := envtools.CheckToolsSeries(list, "precise") 490 c.Assert(err, gc.NotNil) 491 c.Check(err, gc.ErrorMatches, "expected single series, got .*") 492 }