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  }