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 }