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