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  }