github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/environs/tools/testing/testing.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 "crypto/sha256" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "path" 14 "path/filepath" 15 "sort" 16 "strings" 17 "time" 18 19 jc "github.com/juju/testing/checkers" 20 "github.com/juju/utils" 21 "github.com/juju/utils/arch" 22 "github.com/juju/utils/series" 23 "github.com/juju/utils/set" 24 "github.com/juju/version" 25 gc "gopkg.in/check.v1" 26 27 "github.com/juju/juju/environs/filestorage" 28 "github.com/juju/juju/environs/simplestreams" 29 sstesting "github.com/juju/juju/environs/simplestreams/testing" 30 "github.com/juju/juju/environs/storage" 31 "github.com/juju/juju/environs/sync" 32 envtesting "github.com/juju/juju/environs/testing" 33 "github.com/juju/juju/environs/tools" 34 "github.com/juju/juju/juju/names" 35 coretesting "github.com/juju/juju/testing" 36 coretools "github.com/juju/juju/tools" 37 jujuversion "github.com/juju/juju/version" 38 ) 39 40 func GetMockBundleTools(c *gc.C) tools.BundleToolsFunc { 41 return func(w io.Writer, forceVersion *version.Number) (version.Binary, string, error) { 42 vers := version.Binary{ 43 Number: jujuversion.Current, 44 Arch: arch.HostArch(), 45 Series: series.HostSeries(), 46 } 47 if forceVersion != nil { 48 vers.Number = *forceVersion 49 } 50 sha256Hash := fmt.Sprintf("%x", sha256.New().Sum(nil)) 51 return vers, sha256Hash, nil 52 } 53 } 54 55 // GetMockBuildTools returns a sync.BuildToolsTarballFunc implementation which generates 56 // a fake tools tarball. 57 func GetMockBuildTools(c *gc.C) sync.BuildToolsTarballFunc { 58 return func(forceVersion *version.Number, stream string) (*sync.BuiltTools, error) { 59 vers := version.Binary{ 60 Number: jujuversion.Current, 61 Arch: arch.HostArch(), 62 Series: series.HostSeries(), 63 } 64 if forceVersion != nil { 65 vers.Number = *forceVersion 66 } 67 68 tgz, checksum := coretesting.TarGz( 69 coretesting.NewTarFile(names.Jujud, 0777, "jujud contents "+vers.String())) 70 71 toolsDir, err := ioutil.TempDir("", "juju-tools-"+stream) 72 c.Assert(err, jc.ErrorIsNil) 73 name := "name" 74 ioutil.WriteFile(filepath.Join(toolsDir, name), tgz, 0777) 75 76 return &sync.BuiltTools{ 77 Dir: toolsDir, 78 StorageName: name, 79 Version: vers, 80 Size: int64(len(tgz)), 81 Sha256Hash: checksum, 82 }, nil 83 } 84 } 85 86 // MakeTools creates some fake tools with the given version strings. 87 func MakeTools(c *gc.C, metadataDir, stream string, versionStrings []string) coretools.List { 88 return makeTools(c, metadataDir, stream, versionStrings, false) 89 } 90 91 // MakeToolsWithCheckSum creates some fake tools (including checksums) with the given version strings. 92 func MakeToolsWithCheckSum(c *gc.C, metadataDir, stream string, versionStrings []string) coretools.List { 93 return makeTools(c, metadataDir, stream, versionStrings, true) 94 } 95 96 func makeTools(c *gc.C, metadataDir, stream string, versionStrings []string, withCheckSum bool) coretools.List { 97 toolsDir := filepath.Join(metadataDir, storage.BaseToolsPath, stream) 98 c.Assert(os.MkdirAll(toolsDir, 0755), gc.IsNil) 99 var toolsList coretools.List 100 for _, versionString := range versionStrings { 101 binary, err := version.ParseBinary(versionString) 102 if err != nil { 103 c.Assert(err, jc.Satisfies, series.IsUnknownOSForSeriesError) 104 } 105 path := filepath.Join(toolsDir, fmt.Sprintf("juju-%s.tgz", binary)) 106 data := binary.String() 107 err = ioutil.WriteFile(path, []byte(data), 0644) 108 c.Assert(err, jc.ErrorIsNil) 109 tool := &coretools.Tools{ 110 Version: binary, 111 URL: path, 112 } 113 if withCheckSum { 114 tool.Size, tool.SHA256 = SHA256sum(c, path) 115 } 116 toolsList = append(toolsList, tool) 117 } 118 // Write the tools metadata. 119 stor, err := filestorage.NewFileStorageWriter(metadataDir) 120 c.Assert(err, jc.ErrorIsNil) 121 err = tools.MergeAndWriteMetadata(stor, stream, stream, toolsList, false) 122 c.Assert(err, jc.ErrorIsNil) 123 124 // Sign metadata 125 err = envtesting.SignTestTools(stor) 126 c.Assert(err, jc.ErrorIsNil) 127 return toolsList 128 } 129 130 // SHA256sum creates the sha256 checksum for the specified file. 131 func SHA256sum(c *gc.C, path string) (int64, string) { 132 if strings.HasPrefix(path, "file://") { 133 path = path[len("file://"):] 134 } 135 hash, size, err := utils.ReadFileSHA256(path) 136 c.Assert(err, jc.ErrorIsNil) 137 return size, hash 138 } 139 140 // ParseMetadataFromDir loads ToolsMetadata from the specified directory. 141 func ParseMetadataFromDir(c *gc.C, metadataDir, stream string, expectMirrors bool) []*tools.ToolsMetadata { 142 stor, err := filestorage.NewFileStorageReader(metadataDir) 143 c.Assert(err, jc.ErrorIsNil) 144 return ParseMetadataFromStorage(c, stor, stream, expectMirrors) 145 } 146 147 // ParseMetadataFromStorage loads ToolsMetadata from the specified storage reader. 148 func ParseMetadataFromStorage(c *gc.C, stor storage.StorageReader, stream string, expectMirrors bool) []*tools.ToolsMetadata { 149 source := storage.NewStorageSimpleStreamsDataSource("test storage reader", stor, "tools", simplestreams.CUSTOM_CLOUD_DATA, false) 150 params := simplestreams.ValueParams{ 151 DataType: tools.ContentDownload, 152 ValueTemplate: tools.ToolsMetadata{}, 153 } 154 155 const requireSigned = false 156 indexPath := simplestreams.UnsignedIndex("v1", 2) 157 mirrorsPath := simplestreams.MirrorsPath("v1") 158 indexRef, err := simplestreams.GetIndexWithFormat( 159 source, indexPath, "index:1.0", mirrorsPath, requireSigned, simplestreams.CloudSpec{}, params) 160 c.Assert(err, jc.ErrorIsNil) 161 162 toolsIndexMetadata := indexRef.Indexes[tools.ToolsContentId(stream)] 163 c.Assert(toolsIndexMetadata, gc.NotNil) 164 165 // Read the products file contents. 166 r, err := stor.Get(path.Join("tools", toolsIndexMetadata.ProductsFilePath)) 167 defer r.Close() 168 c.Assert(err, jc.ErrorIsNil) 169 data, err := ioutil.ReadAll(r) 170 c.Assert(err, jc.ErrorIsNil) 171 172 url, err := source.URL(toolsIndexMetadata.ProductsFilePath) 173 c.Assert(err, jc.ErrorIsNil) 174 cloudMetadata, err := simplestreams.ParseCloudMetadata(data, "products:1.0", url, tools.ToolsMetadata{}) 175 c.Assert(err, jc.ErrorIsNil) 176 177 toolsMetadataMap := make(map[string]*tools.ToolsMetadata) 178 expectedProductIds := make(set.Strings) 179 toolsVersions := make(set.Strings) 180 for _, mc := range cloudMetadata.Products { 181 for _, items := range mc.Items { 182 for key, item := range items.Items { 183 toolsMetadata := item.(*tools.ToolsMetadata) 184 toolsMetadataMap[key] = toolsMetadata 185 toolsVersions.Add(key) 186 seriesVersion, err := series.SeriesVersion(toolsMetadata.Release) 187 if err != nil { 188 c.Assert(err, jc.Satisfies, series.IsUnknownSeriesVersionError) 189 } 190 productId := fmt.Sprintf("com.ubuntu.juju:%s:%s", seriesVersion, toolsMetadata.Arch) 191 expectedProductIds.Add(productId) 192 } 193 } 194 } 195 196 // Make sure index's product IDs are all represented in the products metadata. 197 sort.Strings(toolsIndexMetadata.ProductIds) 198 c.Assert(toolsIndexMetadata.ProductIds, gc.DeepEquals, expectedProductIds.SortedValues()) 199 200 toolsMetadata := make([]*tools.ToolsMetadata, len(toolsMetadataMap)) 201 for i, key := range toolsVersions.SortedValues() { 202 toolsMetadata[i] = toolsMetadataMap[key] 203 } 204 205 if expectMirrors { 206 r, err = stor.Get(path.Join("tools", simplestreams.UnsignedMirror("v1"))) 207 c.Assert(err, jc.ErrorIsNil) 208 defer r.Close() 209 data, err = ioutil.ReadAll(r) 210 c.Assert(err, jc.ErrorIsNil) 211 c.Assert(string(data), jc.Contains, `"mirrors":`) 212 c.Assert(string(data), jc.Contains, tools.ToolsContentId(stream)) 213 c.Assert(err, jc.ErrorIsNil) 214 } 215 return toolsMetadata 216 } 217 218 type metadataFile struct { 219 path string 220 data []byte 221 } 222 223 func generateMetadata(c *gc.C, stream string, versions ...version.Binary) []metadataFile { 224 var metadata = make([]*tools.ToolsMetadata, len(versions)) 225 for i, vers := range versions { 226 basePath := fmt.Sprintf("%s/tools-%s.tar.gz", stream, vers.String()) 227 metadata[i] = &tools.ToolsMetadata{ 228 Release: vers.Series, 229 Version: vers.Number.String(), 230 Arch: vers.Arch, 231 Path: basePath, 232 } 233 } 234 var streamMetadata = map[string][]*tools.ToolsMetadata{ 235 stream: metadata, 236 } 237 index, legacyIndex, products, err := tools.MarshalToolsMetadataJSON(streamMetadata, time.Now()) 238 c.Assert(err, jc.ErrorIsNil) 239 240 objects := []metadataFile{} 241 addTools := func(fileName string, content []byte) { 242 // add unsigned 243 objects = append(objects, metadataFile{fileName, content}) 244 245 signedFilename, signedContent, err := sstesting.SignMetadata(fileName, content) 246 c.Assert(err, jc.ErrorIsNil) 247 248 // add signed 249 objects = append(objects, metadataFile{signedFilename, signedContent}) 250 } 251 252 addTools(simplestreams.UnsignedIndex("v1", 2), index) 253 if stream == "released" { 254 addTools(simplestreams.UnsignedIndex("v1", 1), legacyIndex) 255 } 256 for stream, metadata := range products { 257 addTools(tools.ProductMetadataPath(stream), metadata) 258 } 259 return objects 260 } 261 262 // UploadToStorage uploads tools and metadata for the specified versions to storage. 263 func UploadToStorage(c *gc.C, stor storage.Storage, stream string, versions ...version.Binary) map[version.Binary]string { 264 uploaded := map[version.Binary]string{} 265 if len(versions) == 0 { 266 return uploaded 267 } 268 var err error 269 for _, vers := range versions { 270 filename := fmt.Sprintf("tools/%s/tools-%s.tar.gz", stream, vers.String()) 271 // Put a file in images since the dummy storage provider requires a 272 // file to exist before the URL can be found. This is to ensure it behaves 273 // the same way as MAAS. 274 err = stor.Put(filename, strings.NewReader("dummy"), 5) 275 c.Assert(err, jc.ErrorIsNil) 276 uploaded[vers], err = stor.URL(filename) 277 c.Assert(err, jc.ErrorIsNil) 278 } 279 objects := generateMetadata(c, stream, versions...) 280 for _, object := range objects { 281 toolspath := path.Join("tools", object.path) 282 err = stor.Put(toolspath, bytes.NewReader(object.data), int64(len(object.data))) 283 c.Assert(err, jc.ErrorIsNil) 284 } 285 return uploaded 286 } 287 288 // UploadToDirectory uploads tools and metadata for the specified versions to dir. 289 func UploadToDirectory(c *gc.C, stream, dir string, versions ...version.Binary) map[version.Binary]string { 290 uploaded := map[version.Binary]string{} 291 if len(versions) == 0 { 292 return uploaded 293 } 294 for _, vers := range versions { 295 basePath := fmt.Sprintf("%s/tools-%s.tar.gz", stream, vers.String()) 296 uploaded[vers] = utils.MakeFileURL(fmt.Sprintf("%s/%s", dir, basePath)) 297 } 298 objects := generateMetadata(c, stream, versions...) 299 for _, object := range objects { 300 path := filepath.Join(dir, object.path) 301 dir := filepath.Dir(path) 302 if err := os.MkdirAll(dir, 0755); err != nil && !os.IsExist(err) { 303 c.Assert(err, jc.ErrorIsNil) 304 } 305 err := ioutil.WriteFile(path, object.data, 0644) 306 c.Assert(err, jc.ErrorIsNil) 307 } 308 return uploaded 309 }