github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/environs/testing/tools.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  	"context"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  
    15  	"github.com/juju/collections/set"
    16  	"github.com/juju/http/v2"
    17  	jc "github.com/juju/testing/checkers"
    18  	"github.com/juju/version/v2"
    19  	gc "gopkg.in/check.v1"
    20  
    21  	agenttools "github.com/juju/juju/agent/tools"
    22  	agenterrors "github.com/juju/juju/cmd/jujud/agent/errors"
    23  	"github.com/juju/juju/core/arch"
    24  	coreos "github.com/juju/juju/core/os"
    25  	"github.com/juju/juju/environs/filestorage"
    26  	"github.com/juju/juju/environs/simplestreams"
    27  	sstesting "github.com/juju/juju/environs/simplestreams/testing"
    28  	"github.com/juju/juju/environs/storage"
    29  	envtools "github.com/juju/juju/environs/tools"
    30  	"github.com/juju/juju/juju/names"
    31  	coretesting "github.com/juju/juju/testing"
    32  	coretools "github.com/juju/juju/tools"
    33  	jujuversion "github.com/juju/juju/version"
    34  )
    35  
    36  // ToolsFixture is used as a fixture to stub out the default tools URL so we
    37  // don't hit the real internet during tests.
    38  type ToolsFixture struct {
    39  	origDefaultURL string
    40  	DefaultBaseURL string
    41  
    42  	// UploadArches holds the architectures of tools to
    43  	// upload in UploadFakeTools. If empty, it will default
    44  	// to just arch.HostArch()
    45  	UploadArches []string
    46  }
    47  
    48  func (s *ToolsFixture) SetUpTest(c *gc.C) {
    49  	s.origDefaultURL = envtools.DefaultBaseURL
    50  	envtools.DefaultBaseURL = s.DefaultBaseURL
    51  }
    52  
    53  func (s *ToolsFixture) TearDownTest(c *gc.C) {
    54  	envtools.DefaultBaseURL = s.origDefaultURL
    55  }
    56  
    57  // UploadFakeToolsToDirectory uploads fake tools of the architectures in
    58  // s.UploadArches for each LTS release to the specified directory.
    59  func (s *ToolsFixture) UploadFakeToolsToDirectory(c *gc.C, dir, toolsDir, stream string) {
    60  	stor, err := filestorage.NewFileStorageWriter(dir)
    61  	c.Assert(err, jc.ErrorIsNil)
    62  	s.UploadFakeTools(c, stor, toolsDir, stream)
    63  }
    64  
    65  // UploadFakeTools uploads fake tools of the architectures in
    66  // s.UploadArches for each LTS release to the specified storage.
    67  func (s *ToolsFixture) UploadFakeTools(c *gc.C, stor storage.Storage, toolsDir, stream string) {
    68  	UploadFakeTools(c, stor, toolsDir, stream, s.UploadArches...)
    69  }
    70  
    71  // RemoveFakeToolsMetadata deletes the fake simplestreams tools metadata from the supplied storage.
    72  func RemoveFakeToolsMetadata(c *gc.C, stor storage.Storage) {
    73  	files, err := stor.List("tools/streams")
    74  	c.Assert(err, jc.ErrorIsNil)
    75  	for _, file := range files {
    76  		err = stor.Remove(file)
    77  		c.Check(err, jc.ErrorIsNil)
    78  	}
    79  }
    80  
    81  // CheckTools ensures the obtained and expected tools are equal, allowing for the fact that
    82  // the obtained tools may not have size and checksum set.
    83  func CheckTools(c *gc.C, obtained, expected *coretools.Tools) {
    84  	c.Assert(obtained.Version, gc.Equals, expected.Version)
    85  	// TODO(dimitern) 2013-10-02 bug #1234217
    86  	// Are these used at at all? If not we should drop them.
    87  	if obtained.URL != "" {
    88  		c.Assert(obtained.URL, gc.Equals, expected.URL)
    89  	}
    90  	if obtained.Size > 0 {
    91  		c.Assert(obtained.Size, gc.Equals, expected.Size)
    92  		c.Assert(obtained.SHA256, gc.Equals, expected.SHA256)
    93  	}
    94  }
    95  
    96  // CheckUpgraderReadyError ensures the obtained and expected errors are equal.
    97  func CheckUpgraderReadyError(c *gc.C, obtained error, expected *agenterrors.UpgradeReadyError) {
    98  	c.Assert(obtained, gc.FitsTypeOf, &agenterrors.UpgradeReadyError{})
    99  	err := obtained.(*agenterrors.UpgradeReadyError)
   100  	c.Assert(err.AgentName, gc.Equals, expected.AgentName)
   101  	c.Assert(err.DataDir, gc.Equals, expected.DataDir)
   102  	c.Assert(err.OldTools, gc.Equals, expected.OldTools)
   103  	c.Assert(err.NewTools, gc.Equals, expected.NewTools)
   104  }
   105  
   106  // PrimeTools sets up the current version of the tools to vers and
   107  // makes sure that they're available in the dataDir.
   108  func PrimeTools(c *gc.C, stor storage.Storage, dataDir, toolsDir string, vers version.Binary) *coretools.Tools {
   109  	err := os.RemoveAll(filepath.Join(dataDir, "tools"))
   110  	c.Assert(err, jc.ErrorIsNil)
   111  	agentTools, err := uploadFakeToolsVersion(stor, toolsDir, vers)
   112  	c.Assert(err, jc.ErrorIsNil)
   113  	client := http.NewClient()
   114  	resp, err := client.Get(context.TODO(), agentTools.URL)
   115  	c.Assert(err, jc.ErrorIsNil)
   116  	defer resp.Body.Close()
   117  	err = agenttools.UnpackTools(dataDir, agentTools, resp.Body)
   118  	c.Assert(err, jc.ErrorIsNil)
   119  	return agentTools
   120  }
   121  
   122  func uploadFakeToolsVersion(stor storage.Storage, toolsDir string, vers version.Binary) (*coretools.Tools, error) {
   123  	logger.Infof("uploading FAKE tools %s", vers)
   124  	tgz, checksum := makeFakeTools(vers)
   125  	size := int64(len(tgz))
   126  	name := envtools.StorageName(vers, toolsDir)
   127  	if err := stor.Put(name, bytes.NewReader(tgz), size); err != nil {
   128  		return nil, err
   129  	}
   130  	url, err := stor.URL(name)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	return &coretools.Tools{URL: url, Version: vers, Size: size, SHA256: checksum}, nil
   135  }
   136  
   137  // InstallFakeDownloadedTools creates and unpacks fake tools of the
   138  // given version into the data directory specified.
   139  func InstallFakeDownloadedTools(c *gc.C, dataDir string, vers version.Binary) *coretools.Tools {
   140  	tgz, checksum := makeFakeTools(vers)
   141  	agentTools := &coretools.Tools{
   142  		Version: vers,
   143  		Size:    int64(len(tgz)),
   144  		SHA256:  checksum,
   145  	}
   146  	err := agenttools.UnpackTools(dataDir, agentTools, bytes.NewReader(tgz))
   147  	c.Assert(err, jc.ErrorIsNil)
   148  	return agentTools
   149  }
   150  
   151  func makeFakeTools(vers version.Binary) ([]byte, string) {
   152  	return coretesting.TarGz(
   153  		coretesting.NewTarFile(names.Jujud, 0777, "jujud contents "+vers.String()))
   154  }
   155  
   156  // UploadFakeToolsVersions puts fake tools in the supplied storage for the supplied versions.
   157  func UploadFakeToolsVersions(store storage.Storage, toolsDir, stream string, versions ...version.Binary) ([]*coretools.Tools, error) {
   158  	// Leave existing tools alone.
   159  	existingTools := make(map[version.Binary]*coretools.Tools)
   160  	existing, _ := envtools.ReadList(store, toolsDir, 1, -1)
   161  	for _, tools := range existing {
   162  		existingTools[tools.Version] = tools
   163  	}
   164  	var agentTools = make(coretools.List, len(versions))
   165  	for i, version := range versions {
   166  		if tools, ok := existingTools[version]; ok {
   167  			agentTools[i] = tools
   168  		} else {
   169  			t, err := uploadFakeToolsVersion(store, toolsDir, version)
   170  			if err != nil {
   171  				return nil, err
   172  			}
   173  			agentTools[i] = t
   174  		}
   175  	}
   176  	ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory())
   177  	if err := envtools.MergeAndWriteMetadata(ss, store, toolsDir, stream, agentTools, envtools.DoNotWriteMirrors); err != nil {
   178  		return nil, err
   179  	}
   180  	err := SignTestTools(store)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  	return agentTools, nil
   185  }
   186  
   187  func SignTestTools(store storage.Storage) error {
   188  	files, err := store.List("")
   189  	if err != nil {
   190  		return err
   191  	}
   192  	for _, file := range files {
   193  		if strings.HasSuffix(file, sstesting.UnsignedJsonSuffix) {
   194  			// only sign .json files and data
   195  			if err := SignFileData(store, file); err != nil {
   196  				return err
   197  			}
   198  		}
   199  	}
   200  	return nil
   201  }
   202  
   203  func SignFileData(stor storage.Storage, fileName string) error {
   204  	r, err := stor.Get(fileName)
   205  	if err != nil {
   206  		return err
   207  	}
   208  	defer r.Close()
   209  
   210  	fileData, err := io.ReadAll(r)
   211  	if err != nil {
   212  		return err
   213  	}
   214  
   215  	signedName, signedContent, err := sstesting.SignMetadata(fileName, fileData)
   216  	if err != nil {
   217  		return err
   218  	}
   219  
   220  	err = stor.Put(signedName, strings.NewReader(string(signedContent)), int64(len(string(signedContent))))
   221  	if err != nil {
   222  		return err
   223  	}
   224  	return nil
   225  }
   226  
   227  // AssertUploadFakeToolsVersions puts fake tools in the supplied storage for the supplied versions.
   228  func AssertUploadFakeToolsVersions(c *gc.C, stor storage.Storage, toolsDir, stream string, versions ...version.Binary) []*coretools.Tools {
   229  	agentTools, err := UploadFakeToolsVersions(stor, toolsDir, stream, versions...)
   230  	c.Assert(err, jc.ErrorIsNil)
   231  	return agentTools
   232  }
   233  
   234  // MustUploadFakeToolsVersions acts as UploadFakeToolsVersions, but panics on failure.
   235  func MustUploadFakeToolsVersions(store storage.Storage, stream string, versions ...version.Binary) []*coretools.Tools {
   236  	var agentTools = make(coretools.List, len(versions))
   237  	for i, version := range versions {
   238  		t, err := uploadFakeToolsVersion(store, stream, version)
   239  		if err != nil {
   240  			panic(err)
   241  		}
   242  		agentTools[i] = t
   243  	}
   244  	ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory())
   245  	err := envtools.MergeAndWriteMetadata(ss, store, stream, stream, agentTools, envtools.DoNotWriteMirrors)
   246  	if err != nil {
   247  		panic(err)
   248  	}
   249  	return agentTools
   250  }
   251  
   252  // UploadFakeTools puts fake tools into the supplied storage with a binary
   253  // version matching jujuversion.Current; if jujuversion.Current's os type is different
   254  // to the host os type, matching fake tools will be uploaded for that host os type.
   255  func UploadFakeTools(c *gc.C, stor storage.Storage, toolsDir, stream string, arches ...string) {
   256  	if len(arches) == 0 {
   257  		arches = []string{arch.HostArch()}
   258  	}
   259  	toolsOS := set.NewStrings("ubuntu")
   260  	toolsOS.Add(coreos.HostOSTypeName())
   261  	var versions []version.Binary
   262  	for _, arch := range arches {
   263  		for _, osType := range toolsOS.Values() {
   264  			v := version.Binary{
   265  				Number:  jujuversion.Current,
   266  				Arch:    arch,
   267  				Release: osType,
   268  			}
   269  			versions = append(versions, v)
   270  		}
   271  	}
   272  	c.Logf("uploading fake tool versions: %v", versions)
   273  	_, err := UploadFakeToolsVersions(stor, toolsDir, stream, versions...)
   274  	c.Assert(err, jc.ErrorIsNil)
   275  }
   276  
   277  // RemoveFakeTools deletes the fake tools from the supplied storage.
   278  func RemoveFakeTools(c *gc.C, stor storage.Storage, toolsDir string) {
   279  	c.Logf("removing fake tools")
   280  	toolsVersion := coretesting.CurrentVersion()
   281  	name := envtools.StorageName(toolsVersion, toolsDir)
   282  	err := stor.Remove(name)
   283  	c.Check(err, jc.ErrorIsNil)
   284  	defaultBase := jujuversion.DefaultSupportedLTSBase()
   285  	if !defaultBase.IsCompatible(coretesting.HostBase(c)) {
   286  		toolsVersion.Release = "ubuntu"
   287  		name := envtools.StorageName(toolsVersion, toolsDir)
   288  		err := stor.Remove(name)
   289  		c.Check(err, jc.ErrorIsNil)
   290  	}
   291  	RemoveFakeToolsMetadata(c, stor)
   292  }
   293  
   294  // RemoveTools deletes all tools from the supplied storage.
   295  func RemoveTools(c *gc.C, stor storage.Storage, toolsDir string) {
   296  	names, err := storage.List(stor, fmt.Sprintf("tools/%s/juju-", toolsDir))
   297  	c.Assert(err, jc.ErrorIsNil)
   298  	c.Logf("removing files: %v", names)
   299  	for _, name := range names {
   300  		err = stor.Remove(name)
   301  		c.Check(err, jc.ErrorIsNil)
   302  	}
   303  	RemoveFakeToolsMetadata(c, stor)
   304  }
   305  
   306  var (
   307  	V100    = version.MustParse("1.0.0")
   308  	V100u64 = version.MustParseBinary("1.0.0-ubuntu-amd64")
   309  	V100u32 = version.MustParseBinary("1.0.0-ubuntu-arm64")
   310  	V100p   = []version.Binary{V100u64, V100u32}
   311  
   312  	V100c64 = version.MustParseBinary("1.0.0-centos-amd64")
   313  	V100c32 = version.MustParseBinary("1.0.0-centos-arm64")
   314  	V100q   = []version.Binary{V100c64, V100c32}
   315  	V100all = append(V100p, V100q...)
   316  
   317  	V1001    = version.MustParse("1.0.0.1")
   318  	V1001u64 = version.MustParseBinary("1.0.0.1-ubuntu-amd64")
   319  	V100Xall = append(V100all, V1001u64)
   320  
   321  	V110    = version.MustParse("1.1.0")
   322  	V110u64 = version.MustParseBinary("1.1.0-ubuntu-amd64")
   323  	V110u32 = version.MustParseBinary("1.1.0-ubuntu-arm64")
   324  	V110p   = []version.Binary{V110u64, V110u32}
   325  
   326  	V110c64 = version.MustParseBinary("1.1.0-centos-amd64")
   327  	V110c32 = version.MustParseBinary("1.1.0-centos-arm64")
   328  	V110c   = []version.Binary{V110c64, V110c32}
   329  	V110all = append(V110p, V110c...)
   330  
   331  	V120    = version.MustParse("1.2.0")
   332  	V120u64 = version.MustParseBinary("1.2.0-ubuntu-amd64")
   333  	V120u32 = version.MustParseBinary("1.2.0-ubuntu-arm64")
   334  	V120all = []version.Binary{V120u64, V120u32}
   335  
   336  	V1all = append(V100Xall, append(V110all, V120all...)...)
   337  
   338  	V220    = version.MustParse("2.2.0")
   339  	V220u32 = version.MustParseBinary("2.2.0-ubuntu-arm64")
   340  	V220u64 = version.MustParseBinary("2.2.0-ubuntu-amd64")
   341  	V220all = []version.Binary{V220u64, V220u32}
   342  	VAll    = append(V1all, V220all...)
   343  )
   344  
   345  type BootstrapToolsTest struct {
   346  	Info          string
   347  	Available     []version.Binary
   348  	CliVersion    version.Binary
   349  	DefaultSeries string
   350  	AgentVersion  version.Number
   351  	Development   bool
   352  	Arch          string
   353  	Expect        []version.Binary
   354  	Err           string
   355  }
   356  
   357  var noToolsMessage = "Juju cannot bootstrap because no agent binaries are available for your model.*"
   358  
   359  var BootstrapToolsTests = []BootstrapToolsTest{
   360  	{
   361  		Info:          "no tools at all",
   362  		CliVersion:    V100u64,
   363  		DefaultSeries: "precise",
   364  		Err:           noToolsMessage,
   365  	}, {
   366  		Info:          "released cli: use newest compatible release version",
   367  		Available:     VAll,
   368  		CliVersion:    V100u64,
   369  		DefaultSeries: "precise",
   370  		Expect:        V100p,
   371  	}, {
   372  		Info:          "released cli: cli Arch ignored",
   373  		Available:     VAll,
   374  		CliVersion:    V100u32,
   375  		DefaultSeries: "precise",
   376  		Expect:        V100p,
   377  	}, {
   378  		Info:          "released cli: cli series ignored",
   379  		Available:     VAll,
   380  		CliVersion:    V100c64,
   381  		DefaultSeries: "precise",
   382  		Expect:        V100p,
   383  	}, {
   384  		Info:          "released cli: series taken from default-series",
   385  		Available:     V120all,
   386  		CliVersion:    V120u64,
   387  		DefaultSeries: "quantal",
   388  		Expect:        V120all,
   389  	}, {
   390  		Info:          "released cli: ignore close dev match",
   391  		Available:     V100Xall,
   392  		CliVersion:    V100u64,
   393  		DefaultSeries: "precise",
   394  		Expect:        V100p,
   395  	}, {
   396  		Info:          "released cli: filter by arch constraints",
   397  		Available:     V120all,
   398  		CliVersion:    V120u64,
   399  		DefaultSeries: "precise",
   400  		Arch:          "i386",
   401  		Expect:        []version.Binary{V120u32},
   402  	}, {
   403  		Info:          "released cli: specific released version",
   404  		Available:     VAll,
   405  		CliVersion:    V100u64,
   406  		AgentVersion:  V100,
   407  		DefaultSeries: "precise",
   408  		Expect:        V100p,
   409  	}, {
   410  		Info:          "released cli: specific dev version",
   411  		Available:     VAll,
   412  		CliVersion:    V110u64,
   413  		AgentVersion:  V110,
   414  		DefaultSeries: "precise",
   415  		Expect:        V110p,
   416  	}, {
   417  		Info:          "released cli: major upgrades bad",
   418  		Available:     V220all,
   419  		CliVersion:    V100u64,
   420  		DefaultSeries: "precise",
   421  		Err:           noToolsMessage,
   422  	}, {
   423  		Info:          "released cli: minor upgrades bad",
   424  		Available:     V120all,
   425  		CliVersion:    V100u64,
   426  		DefaultSeries: "precise",
   427  		Err:           noToolsMessage,
   428  	}, {
   429  		Info:          "released cli: major downgrades bad",
   430  		Available:     V100Xall,
   431  		CliVersion:    V220u64,
   432  		DefaultSeries: "precise",
   433  		Err:           noToolsMessage,
   434  	}, {
   435  		Info:          "released cli: minor downgrades bad",
   436  		Available:     V100Xall,
   437  		CliVersion:    V120u64,
   438  		DefaultSeries: "quantal",
   439  		Err:           noToolsMessage,
   440  	}, {
   441  		Info:          "released cli: no matching series",
   442  		Available:     VAll,
   443  		CliVersion:    V100u64,
   444  		DefaultSeries: "raring",
   445  		Err:           noToolsMessage,
   446  	}, {
   447  		Info:          "released cli: no matching arches",
   448  		Available:     VAll,
   449  		CliVersion:    V100u64,
   450  		DefaultSeries: "precise",
   451  		Arch:          "armhf",
   452  		Err:           noToolsMessage,
   453  	}, {
   454  		Info:          "released cli: specific bad major 1",
   455  		Available:     VAll,
   456  		CliVersion:    V220u64,
   457  		AgentVersion:  V120,
   458  		DefaultSeries: "precise",
   459  		Err:           noToolsMessage,
   460  	}, {
   461  		Info:          "released cli: specific bad major 2",
   462  		Available:     VAll,
   463  		CliVersion:    V120u64,
   464  		AgentVersion:  V220,
   465  		DefaultSeries: "precise",
   466  		Err:           noToolsMessage,
   467  	}, {
   468  		Info:          "released cli: ignore dev tools 1",
   469  		Available:     V110all,
   470  		CliVersion:    V100u64,
   471  		DefaultSeries: "precise",
   472  		Err:           noToolsMessage,
   473  	}, {
   474  		Info:          "released cli: ignore dev tools 2",
   475  		Available:     V110all,
   476  		CliVersion:    V120u64,
   477  		DefaultSeries: "precise",
   478  		Err:           noToolsMessage,
   479  	}, {
   480  		Info:          "released cli: ignore dev tools 3",
   481  		Available:     []version.Binary{V1001u64},
   482  		CliVersion:    V100u64,
   483  		DefaultSeries: "precise",
   484  		Err:           noToolsMessage,
   485  	}, {
   486  		Info:          "released cli with dev setting respects agent-version",
   487  		Available:     VAll,
   488  		CliVersion:    V100c32,
   489  		AgentVersion:  V1001,
   490  		DefaultSeries: "precise",
   491  		Development:   true,
   492  		Expect:        []version.Binary{V1001u64},
   493  	}, {
   494  		Info:          "dev cli respects agent-version",
   495  		Available:     VAll,
   496  		CliVersion:    V100c32,
   497  		AgentVersion:  V1001,
   498  		DefaultSeries: "precise",
   499  		Expect:        []version.Binary{V1001u64},
   500  	}, {
   501  		Info:          "released cli with dev setting respects agent-version",
   502  		Available:     V1all,
   503  		CliVersion:    V100c32,
   504  		AgentVersion:  V1001,
   505  		DefaultSeries: "precise",
   506  		Development:   true,
   507  		Expect:        []version.Binary{V1001u64},
   508  	}, {
   509  		Info:          "dev cli respects agent-version",
   510  		Available:     V1all,
   511  		CliVersion:    V100c32,
   512  		AgentVersion:  V1001,
   513  		DefaultSeries: "precise",
   514  		Expect:        []version.Binary{V1001u64},
   515  	}}