github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/resources_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"bytes"
     8  	"crypto/sha512"
     9  	"fmt"
    10  	"io"
    11  	"sort"
    12  	"time"
    13  
    14  	"github.com/juju/charm/v12"
    15  	charmresource "github.com/juju/charm/v12/resource"
    16  	"github.com/juju/errors"
    17  	"github.com/juju/names/v5"
    18  	jc "github.com/juju/testing/checkers"
    19  	gc "gopkg.in/check.v1"
    20  
    21  	"github.com/juju/juju/core/resources"
    22  	resourcetesting "github.com/juju/juju/core/resources/testing"
    23  	"github.com/juju/juju/state"
    24  	"github.com/juju/juju/testing"
    25  	"github.com/juju/juju/testing/factory"
    26  )
    27  
    28  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/resources_mock.go github.com/juju/juju/state Resources
    29  
    30  var _ = gc.Suite(&ResourcesSuite{})
    31  
    32  type ResourcesSuite struct {
    33  	ConnSuite
    34  
    35  	ch *state.Charm
    36  }
    37  
    38  func (s *ResourcesSuite) SetUpTest(c *gc.C) {
    39  	s.ConnSuite.SetUpTest(c)
    40  	s.ch = s.ConnSuite.AddTestingCharm(c, "starsay")
    41  	s.Factory.MakeApplication(c, &factory.ApplicationParams{
    42  		Name:  "starsay",
    43  		Charm: s.ch,
    44  	})
    45  }
    46  
    47  func newResource(c *gc.C, name, data string) resources.Resource {
    48  	opened := resourcetesting.NewResource(c, nil, name, "wordpress", data)
    49  	res := opened.Resource
    50  	res.Timestamp = time.Unix(res.Timestamp.Unix(), 0)
    51  	return res
    52  }
    53  
    54  func newResourceFromCharm(ch charm.Charm, name string) resources.Resource {
    55  	return resources.Resource{
    56  		Resource: charmresource.Resource{
    57  			Meta:   ch.Meta().Resources[name],
    58  			Origin: charmresource.OriginUpload,
    59  		},
    60  		ID:            "starsay/" + name,
    61  		ApplicationID: "starsay",
    62  	}
    63  }
    64  
    65  func (s *ResourcesSuite) TestListResources(c *gc.C) {
    66  	ch := s.AddTestingCharm(c, "wordpress")
    67  	s.AddTestingApplication(c, "wordpress", ch)
    68  
    69  	res := s.State.Resources()
    70  	data := "spamspamspam"
    71  	spam := newResource(c, "store-resource", data)
    72  	file := bytes.NewBufferString(data)
    73  	_, err := res.SetResource("wordpress", spam.Username, spam.Resource, file, state.IncrementCharmModifiedVersion)
    74  	c.Assert(err, jc.ErrorIsNil)
    75  
    76  	resultRes, err := res.ListResources("wordpress")
    77  	c.Assert(err, jc.ErrorIsNil)
    78  
    79  	spam.Timestamp = resultRes.Resources[0].Timestamp
    80  	c.Assert(resultRes, jc.DeepEquals, resources.ApplicationResources{
    81  		Resources: []resources.Resource{spam},
    82  	})
    83  }
    84  
    85  func (s *ResourcesSuite) TestListResourcesNoResources(c *gc.C) {
    86  	res := s.State.Resources()
    87  	resultRes, err := res.ListResources("wordpress")
    88  	c.Assert(err, jc.ErrorIsNil)
    89  	c.Check(resultRes.Resources, gc.HasLen, 0)
    90  }
    91  
    92  func (s *ResourcesSuite) TestListResourcesIgnorePending(c *gc.C) {
    93  	ch := s.AddTestingCharm(c, "wordpress")
    94  	s.AddTestingApplication(c, "wordpress", ch)
    95  
    96  	res := s.State.Resources()
    97  	data := "spamspamspam"
    98  	spam := newResource(c, "store-resource", data)
    99  	file := bytes.NewBufferString(data)
   100  	_, err := res.SetResource("wordpress", spam.Username, spam.Resource, file, state.IncrementCharmModifiedVersion)
   101  	c.Assert(err, jc.ErrorIsNil)
   102  
   103  	ham := newResource(c, "install-resource", "install-resource")
   104  	_, err = res.AddPendingResource("wordpress", "user", ham.Resource)
   105  	c.Assert(err, jc.ErrorIsNil)
   106  	csResources := []charmresource.Resource{spam.Resource}
   107  	err = res.SetCharmStoreResources("wordpress", csResources, testing.NonZeroTime())
   108  	c.Assert(err, jc.ErrorIsNil)
   109  
   110  	resultRes, err := res.ListResources("wordpress")
   111  	c.Assert(err, jc.ErrorIsNil)
   112  
   113  	spam.Timestamp = resultRes.Resources[0].Timestamp
   114  	c.Assert(resultRes, jc.DeepEquals, resources.ApplicationResources{
   115  		Resources:           []resources.Resource{spam},
   116  		CharmStoreResources: csResources,
   117  	})
   118  }
   119  
   120  func (s *ResourcesSuite) TestListPendingResources(c *gc.C) {
   121  	ch := s.AddTestingCharm(c, "wordpress")
   122  	s.AddTestingApplication(c, "wordpress", ch)
   123  
   124  	res := s.State.Resources()
   125  	data := "spamspamspam"
   126  	spam := newResource(c, "store-resource", data)
   127  	file := bytes.NewBufferString(data)
   128  	_, err := res.SetResource("wordpress", spam.Username, spam.Resource, file, state.IncrementCharmModifiedVersion)
   129  	c.Assert(err, jc.ErrorIsNil)
   130  
   131  	ham := newResource(c, "install-resource", "install-resource")
   132  	pendingID, err := res.AddPendingResource("wordpress", ham.Username, ham.Resource)
   133  	c.Assert(err, jc.ErrorIsNil)
   134  
   135  	resultRes, err := res.ListPendingResources("wordpress")
   136  	c.Assert(err, jc.ErrorIsNil)
   137  	ham.PendingID = pendingID
   138  	ham.Username = ""
   139  	ham.Timestamp = resultRes.Resources[0].Timestamp
   140  	c.Assert(resultRes, jc.DeepEquals, resources.ApplicationResources{
   141  		Resources: []resources.Resource{ham},
   142  	})
   143  }
   144  
   145  func (s *ResourcesSuite) TestUpdatePending(c *gc.C) {
   146  	ch := s.AddTestingCharm(c, "wordpress")
   147  	s.AddTestingApplication(c, "wordpress", ch)
   148  
   149  	res := s.State.Resources()
   150  
   151  	ham := newResource(c, "install-resource", "install-resource")
   152  	pendingID, err := res.AddPendingResource("wordpress", ham.Username, ham.Resource)
   153  	c.Assert(err, jc.ErrorIsNil)
   154  
   155  	data := "spamspamspam"
   156  	ham.Size = int64(len(data))
   157  	sha384hash := sha512.New384()
   158  	sha384hash.Write([]byte(data))
   159  	fp := fmt.Sprintf("%x", sha384hash.Sum(nil))
   160  	ham.Fingerprint, err = charmresource.ParseFingerprint(fp)
   161  	c.Assert(err, jc.ErrorIsNil)
   162  
   163  	r, err := res.UpdatePendingResource("wordpress", pendingID, ham.Username, ham.Resource, bytes.NewBufferString(data))
   164  	c.Assert(err, jc.ErrorIsNil)
   165  
   166  	ham.Timestamp = r.Timestamp
   167  	ham.PendingID = pendingID
   168  	c.Assert(r, jc.DeepEquals, ham)
   169  }
   170  
   171  func (s *ResourcesSuite) TestGetResource(c *gc.C) {
   172  	ch := s.AddTestingCharm(c, "wordpress")
   173  	s.AddTestingApplication(c, "wordpress", ch)
   174  
   175  	res := s.State.Resources()
   176  	_, err := res.GetResource("wordpress", "store-resource")
   177  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   178  
   179  	data := "spamspamspam"
   180  	spam := newResource(c, "store-resource", data)
   181  	file := bytes.NewBufferString(data)
   182  	_, err = res.SetResource("wordpress", spam.Username, spam.Resource, file, state.IncrementCharmModifiedVersion)
   183  	c.Assert(err, jc.ErrorIsNil)
   184  
   185  	r, err := res.GetResource("wordpress", "store-resource")
   186  	c.Assert(err, jc.ErrorIsNil)
   187  	spam.Timestamp = r.Timestamp
   188  	c.Assert(r, jc.DeepEquals, spam)
   189  }
   190  
   191  func (s *ResourcesSuite) TestGetPendingResource(c *gc.C) {
   192  	ch := s.AddTestingCharm(c, "wordpress")
   193  	s.AddTestingApplication(c, "wordpress", ch)
   194  
   195  	res := s.State.Resources()
   196  	ham := newResource(c, "install-resource", "install-resource")
   197  	pendingID, err := res.AddPendingResource("wordpress", ham.Username, ham.Resource)
   198  	c.Assert(err, jc.ErrorIsNil)
   199  
   200  	r, err := res.GetPendingResource("wordpress", "install-resource", pendingID)
   201  	c.Assert(err, jc.ErrorIsNil)
   202  	ham.PendingID = pendingID
   203  	ham.Username = ""
   204  	ham.Timestamp = r.Timestamp
   205  	c.Assert(r, jc.DeepEquals, ham)
   206  }
   207  
   208  func (s *ResourcesSuite) TestSetResource(c *gc.C) {
   209  	ch := s.AddTestingCharm(c, "wordpress")
   210  	s.AddTestingApplication(c, "wordpress", ch)
   211  
   212  	app, err := s.State.Application("wordpress")
   213  	c.Assert(err, jc.ErrorIsNil)
   214  	c.Assert(app.CharmModifiedVersion(), gc.Equals, 0)
   215  
   216  	res := s.State.Resources()
   217  
   218  	data := "spamspamspam"
   219  	spam := newResource(c, "store-resource", data)
   220  	file := bytes.NewBufferString(data)
   221  
   222  	_, err = res.AddPendingResource("wordpress", "user", spam.Resource)
   223  	c.Assert(err, jc.ErrorIsNil)
   224  	r, err := res.SetResource("wordpress", spam.Username, spam.Resource, file, state.IncrementCharmModifiedVersion)
   225  	c.Assert(err, jc.ErrorIsNil)
   226  	spam.Timestamp = r.Timestamp
   227  	c.Assert(r, jc.DeepEquals, spam)
   228  	c.Assert(r.PendingID, gc.Equals, "")
   229  
   230  	r, err = res.GetResource("wordpress", "store-resource")
   231  	c.Assert(err, jc.ErrorIsNil)
   232  	c.Assert(r.PendingID, gc.Equals, "")
   233  
   234  	err = app.Refresh()
   235  	c.Assert(err, jc.ErrorIsNil)
   236  	c.Assert(app.CharmModifiedVersion(), gc.Equals, 1)
   237  }
   238  
   239  func (s *ResourcesSuite) TestSetCharmStoreResources(c *gc.C) {
   240  	res := s.State.Resources()
   241  	updatedRes := newResourceFromCharm(s.ch, "store-resource")
   242  	updatedRes.Revision = 666
   243  	csResources := []charmresource.Resource{updatedRes.Resource}
   244  	err := res.SetCharmStoreResources("starsay", csResources, testing.NonZeroTime())
   245  	c.Assert(err, jc.ErrorIsNil)
   246  
   247  	resultRes, err := res.ListResources("starsay")
   248  	c.Assert(err, jc.ErrorIsNil)
   249  
   250  	sort.Slice(resultRes.Resources, func(i, j int) bool {
   251  		return resultRes.Resources[i].Name < resultRes.Resources[j].Name
   252  	})
   253  	sort.Slice(resultRes.CharmStoreResources, func(i, j int) bool {
   254  		return resultRes.CharmStoreResources[i].Name < resultRes.CharmStoreResources[j].Name
   255  	})
   256  
   257  	expected := []resources.Resource{
   258  		newResourceFromCharm(s.ch, "install-resource"),
   259  		newResourceFromCharm(s.ch, "store-resource"),
   260  		newResourceFromCharm(s.ch, "upload-resource"),
   261  	}
   262  	c.Assert(resultRes, jc.DeepEquals, resources.ApplicationResources{
   263  		Resources: expected,
   264  		CharmStoreResources: []charmresource.Resource{
   265  			expected[0].Resource,
   266  			updatedRes.Resource,
   267  			expected[2].Resource,
   268  		},
   269  	})
   270  }
   271  
   272  func (s *ResourcesSuite) TestUnitResource(c *gc.C) {
   273  	ch := s.AddTestingCharm(c, "wordpress")
   274  	s.AddTestingApplication(c, "wordpress", ch)
   275  
   276  	res := s.State.Resources()
   277  	data := "spamspamspam"
   278  	spam := newResource(c, "store-resource", data)
   279  	_, err := res.SetUnitResource("wordpress/0", spam.Username, spam.Resource)
   280  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   281  
   282  	file := bytes.NewBufferString(data)
   283  	_, err = res.SetResource("wordpress", spam.Username, spam.Resource, file, state.IncrementCharmModifiedVersion)
   284  	c.Assert(err, jc.ErrorIsNil)
   285  
   286  	r, err := res.SetUnitResource("wordpress/0", spam.Username, spam.Resource)
   287  	c.Assert(err, jc.ErrorIsNil)
   288  	spam.Timestamp = r.Timestamp
   289  	c.Assert(r, jc.DeepEquals, spam)
   290  	resultRes, err := res.ListResources("wordpress")
   291  	c.Assert(err, jc.ErrorIsNil)
   292  
   293  	spam.Timestamp = resultRes.Resources[0].Timestamp
   294  	resultRes.UnitResources[0].Resources[0].Timestamp = spam.Timestamp
   295  	c.Assert(resultRes, jc.DeepEquals, resources.ApplicationResources{
   296  		Resources: []resources.Resource{spam},
   297  		UnitResources: []resources.UnitResources{{
   298  			Tag:       names.NewUnitTag("wordpress/0"),
   299  			Resources: []resources.Resource{spam},
   300  		}},
   301  	})
   302  }
   303  
   304  func (s *ResourcesSuite) TestOpenResource(c *gc.C) {
   305  	app, err := s.State.Application("starsay")
   306  	c.Assert(err, jc.ErrorIsNil)
   307  	s.Factory.MakeUnit(c, &factory.UnitParams{
   308  		Application: app,
   309  	})
   310  	res := s.State.Resources()
   311  
   312  	_, _, err = res.OpenResource("starsay", "install-resource")
   313  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   314  
   315  	spam := newResourceFromCharm(s.ch, "install-resource")
   316  	data := "spamspamspam"
   317  	spam.Size = int64(len(data))
   318  	sha384hash := sha512.New384()
   319  	sha384hash.Write([]byte(data))
   320  	fp := fmt.Sprintf("%x", sha384hash.Sum(nil))
   321  	spam.Fingerprint, err = charmresource.ParseFingerprint(fp)
   322  	c.Assert(err, jc.ErrorIsNil)
   323  	file := bytes.NewBufferString(data)
   324  	_, err = res.SetResource("starsay", spam.Username, spam.Resource, file, state.IncrementCharmModifiedVersion)
   325  	c.Assert(err, jc.ErrorIsNil)
   326  	_, err = res.SetUnitResource("starsay/0", spam.Username, spam.Resource)
   327  	c.Assert(err, jc.ErrorIsNil)
   328  
   329  	r, rdr, err := res.OpenResource("starsay", "install-resource")
   330  	c.Assert(err, jc.ErrorIsNil)
   331  	defer func() { _ = rdr.Close() }()
   332  
   333  	spam.Timestamp = r.Timestamp
   334  	c.Assert(r, jc.DeepEquals, spam)
   335  
   336  	resData, err := io.ReadAll(rdr)
   337  	c.Assert(err, jc.ErrorIsNil)
   338  	c.Assert(string(resData), gc.Equals, data)
   339  
   340  	resultRes, err := res.ListResources("starsay")
   341  	c.Assert(err, jc.ErrorIsNil)
   342  	c.Assert(resultRes.Resources, gc.HasLen, 3)
   343  
   344  	sort.Slice(resultRes.Resources, func(i, j int) bool {
   345  		return resultRes.Resources[i].Name < resultRes.Resources[j].Name
   346  	})
   347  	sort.Slice(resultRes.CharmStoreResources, func(i, j int) bool {
   348  		return resultRes.CharmStoreResources[i].Name < resultRes.CharmStoreResources[j].Name
   349  	})
   350  
   351  	expected := []resources.Resource{
   352  		newResourceFromCharm(s.ch, "install-resource"),
   353  		newResourceFromCharm(s.ch, "store-resource"),
   354  		newResourceFromCharm(s.ch, "upload-resource"),
   355  	}
   356  	chRes := []charmresource.Resource{
   357  		expected[0].Resource,
   358  		expected[1].Resource,
   359  		expected[2].Resource,
   360  	}
   361  	expected[0].Resource = spam.Resource
   362  	expected[0].Timestamp = resultRes.Resources[0].Timestamp
   363  
   364  	resultRes.UnitResources[0].Resources[0].Timestamp = spam.Timestamp
   365  
   366  	c.Assert(resultRes, jc.DeepEquals, resources.ApplicationResources{
   367  		Resources:           expected,
   368  		CharmStoreResources: chRes,
   369  		UnitResources: []resources.UnitResources{{
   370  			Tag:       names.NewUnitTag("starsay/0"),
   371  			Resources: []resources.Resource{spam},
   372  		}},
   373  	})
   374  }
   375  
   376  func (s *ResourcesSuite) TestOpenResourceForUniter(c *gc.C) {
   377  	app, err := s.State.Application("starsay")
   378  	c.Assert(err, jc.ErrorIsNil)
   379  	s.Factory.MakeUnit(c, &factory.UnitParams{
   380  		Application: app,
   381  	})
   382  	res := s.State.Resources()
   383  
   384  	spam := newResourceFromCharm(s.ch, "install-resource")
   385  	data := "spamspamspam"
   386  	spam.Size = int64(len(data))
   387  	sha384hash := sha512.New384()
   388  	sha384hash.Write([]byte(data))
   389  	fp := fmt.Sprintf("%x", sha384hash.Sum(nil))
   390  	spam.Fingerprint, err = charmresource.ParseFingerprint(fp)
   391  	c.Assert(err, jc.ErrorIsNil)
   392  	file := bytes.NewBufferString(data)
   393  	_, err = res.SetResource("starsay", spam.Username, spam.Resource, file, state.IncrementCharmModifiedVersion)
   394  	c.Assert(err, jc.ErrorIsNil)
   395  	_, err = res.SetUnitResource("starsay/0", spam.Username, spam.Resource)
   396  	c.Assert(err, jc.ErrorIsNil)
   397  
   398  	unitRes, rdr, err := res.OpenResourceForUniter("starsay/0", "install-resource")
   399  	c.Assert(err, jc.ErrorIsNil)
   400  	defer func() { _ = rdr.Close() }()
   401  
   402  	buf := make([]byte, 2)
   403  	_, err = rdr.Read(buf)
   404  	c.Assert(err, jc.ErrorIsNil)
   405  
   406  	resultRes, err := res.ListPendingResources("starsay")
   407  	c.Assert(err, jc.ErrorIsNil)
   408  
   409  	c.Assert(resultRes.UnitResources, gc.HasLen, 1)
   410  	c.Assert(resultRes.UnitResources[0].Resources, gc.HasLen, 1)
   411  	resultRes.UnitResources[0].Resources[0].PendingID = ""
   412  	c.Assert(resultRes, jc.DeepEquals, resources.ApplicationResources{
   413  		UnitResources: []resources.UnitResources{{
   414  			Tag:              names.NewUnitTag("starsay/0"),
   415  			Resources:        []resources.Resource{unitRes},
   416  			DownloadProgress: map[string]int64{"install-resource": 2},
   417  		}},
   418  	})
   419  }
   420  
   421  func (s *ResourcesSuite) TestRemovePendingAppResources(c *gc.C) {
   422  	ch := s.AddTestingCharm(c, "wordpress")
   423  	s.AddTestingApplication(c, "wordpress", ch)
   424  
   425  	res := s.State.Resources()
   426  
   427  	spam := newResource(c, "install-resource", "install-resource")
   428  	pendingID, err := res.AddPendingResource("wordpress", spam.Username, spam.Resource)
   429  	c.Assert(err, jc.ErrorIsNil)
   430  
   431  	// Add some data so we force a cleanup.
   432  	data := "spamspamspam"
   433  	spam.Size = int64(len(data))
   434  	sha384hash := sha512.New384()
   435  	sha384hash.Write([]byte(data))
   436  	fp := fmt.Sprintf("%x", sha384hash.Sum(nil))
   437  	spam.Fingerprint, err = charmresource.ParseFingerprint(fp)
   438  	c.Assert(err, jc.ErrorIsNil)
   439  
   440  	_, err = res.UpdatePendingResource("wordpress", pendingID, spam.Username, spam.Resource, bytes.NewBufferString(data))
   441  	c.Assert(err, jc.ErrorIsNil)
   442  
   443  	err = res.RemovePendingAppResources("wordpress", map[string]string{"install-resource": pendingID})
   444  	c.Assert(err, jc.ErrorIsNil)
   445  
   446  	resources, err := res.ListPendingResources("wordpress")
   447  	c.Assert(err, jc.ErrorIsNil)
   448  	c.Assert(resources.Resources, gc.HasLen, 0)
   449  
   450  	state.AssertCleanupsWithKind(c, s.State, "resourceBlob")
   451  }