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