github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/api/controller/crossmodelrelations/crossmodelrelations_test.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package crossmodelrelations_test 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/json" 10 11 "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" 12 "github.com/juju/clock" 13 "github.com/juju/errors" 14 jc "github.com/juju/testing/checkers" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/macaroon.v2" 17 18 "github.com/juju/juju/api/base" 19 "github.com/juju/juju/api/base/testing" 20 "github.com/juju/juju/api/controller/crossmodelrelations" 21 apitesting "github.com/juju/juju/api/testing" 22 "github.com/juju/juju/rpc/params" 23 coretesting "github.com/juju/juju/testing" 24 ) 25 26 var _ = gc.Suite(&CrossModelRelationsSuite{}) 27 28 type CrossModelRelationsSuite struct { 29 coretesting.BaseSuite 30 31 cache *crossmodelrelations.MacaroonCache 32 } 33 34 func (s *CrossModelRelationsSuite) SetUpTest(c *gc.C) { 35 s.cache = crossmodelrelations.NewMacaroonCache(clock.WallClock) 36 s.BaseSuite.SetUpTest(c) 37 } 38 39 func (s *CrossModelRelationsSuite) TestNewClient(c *gc.C) { 40 apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 41 return nil 42 }) 43 client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache) 44 c.Assert(client, gc.NotNil) 45 } 46 47 type mockDischargeAcquirer struct { 48 base.MacaroonDischarger 49 } 50 51 func (m *mockDischargeAcquirer) DischargeAll(ctx context.Context, b *bakery.Macaroon) (macaroon.Slice, error) { 52 if !bytes.Equal(b.M().Caveats()[0].Id, []byte("third party caveat")) { 53 return nil, errors.New("permission denied") 54 } 55 mac, err := apitesting.NewMacaroon("discharge mac") 56 if err != nil { 57 return nil, err 58 } 59 return macaroon.Slice{mac}, nil 60 } 61 62 func (s *CrossModelRelationsSuite) newDischargeMacaroon(c *gc.C) *macaroon.Macaroon { 63 mac, err := apitesting.NewMacaroon("id") 64 c.Assert(err, jc.ErrorIsNil) 65 err = mac.AddThirdPartyCaveat(nil, []byte("third party caveat"), "third party location") 66 c.Assert(err, jc.ErrorIsNil) 67 return mac 68 } 69 70 func (s *CrossModelRelationsSuite) fillResponse(c *gc.C, resp interface{}, value interface{}) { 71 b, err := json.Marshal(value) 72 c.Assert(err, jc.ErrorIsNil) 73 err = json.Unmarshal(b, resp) 74 c.Assert(err, jc.ErrorIsNil) 75 } 76 77 func (s *CrossModelRelationsSuite) TestPublishRelationChange(c *gc.C) { 78 var callCount int 79 mac, err := apitesting.NewMacaroon("id") 80 c.Assert(err, jc.ErrorIsNil) 81 apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 82 c.Check(objType, gc.Equals, "CrossModelRelations") 83 c.Check(version, gc.Equals, 0) 84 c.Check(id, gc.Equals, "") 85 c.Check(request, gc.Equals, "PublishRelationChanges") 86 c.Check(arg, gc.DeepEquals, params.RemoteRelationsChanges{ 87 Changes: []params.RemoteRelationChangeEvent{{ 88 RelationToken: "token", 89 DepartedUnits: []int{1}, Macaroons: macaroon.Slice{mac}, 90 BakeryVersion: bakery.LatestVersion, 91 }}, 92 }) 93 c.Assert(result, gc.FitsTypeOf, ¶ms.ErrorResults{}) 94 *(result.(*params.ErrorResults)) = params.ErrorResults{ 95 Results: []params.ErrorResult{{ 96 Error: ¶ms.Error{Message: "FAIL"}, 97 }}, 98 } 99 callCount++ 100 return nil 101 }) 102 client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache) 103 err = client.PublishRelationChange(params.RemoteRelationChangeEvent{ 104 RelationToken: "token", 105 DepartedUnits: []int{1}, 106 Macaroons: macaroon.Slice{mac}, 107 BakeryVersion: bakery.LatestVersion, 108 }) 109 c.Check(err, gc.ErrorMatches, "FAIL") 110 // Call again with a different macaroon but the first one will be 111 // cached and override the passed in macaroon. 112 different, err := apitesting.NewMacaroon("different") 113 c.Assert(err, jc.ErrorIsNil) 114 s.cache.Upsert("token", macaroon.Slice{mac}) 115 err = client.PublishRelationChange(params.RemoteRelationChangeEvent{ 116 RelationToken: "token", 117 DepartedUnits: []int{1}, 118 Macaroons: macaroon.Slice{different}, 119 BakeryVersion: bakery.LatestVersion, 120 }) 121 c.Check(err, gc.ErrorMatches, "FAIL") 122 c.Check(callCount, gc.Equals, 2) 123 } 124 125 func (s *CrossModelRelationsSuite) TestPublishRelationChangeDischargeRequired(c *gc.C) { 126 var ( 127 callCount int 128 dischargeMac macaroon.Slice 129 ) 130 apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 131 var resultErr *params.Error 132 if callCount == 0 { 133 mac := s.newDischargeMacaroon(c) 134 resultErr = ¶ms.Error{ 135 Code: params.CodeDischargeRequired, 136 Info: params.DischargeRequiredErrorInfo{ 137 Macaroon: mac, 138 }.AsMap(), 139 } 140 } 141 argParam := arg.(params.RemoteRelationsChanges) 142 dischargeMac = argParam.Changes[0].Macaroons 143 resp := params.ErrorResults{ 144 Results: []params.ErrorResult{{Error: resultErr}}, 145 } 146 s.fillResponse(c, result, resp) 147 callCount++ 148 return nil 149 }) 150 acquirer := &mockDischargeAcquirer{} 151 callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer) 152 client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache) 153 err := client.PublishRelationChange(params.RemoteRelationChangeEvent{ 154 RelationToken: "token", 155 DepartedUnits: []int{1}, 156 }) 157 c.Check(callCount, gc.Equals, 2) 158 c.Check(err, jc.ErrorIsNil) 159 c.Check(dischargeMac, gc.HasLen, 1) 160 c.Assert(dischargeMac[0].Id(), jc.DeepEquals, []byte("discharge mac")) 161 // Macaroon has been cached. 162 ms, ok := s.cache.Get("token") 163 c.Assert(ok, jc.IsTrue) 164 apitesting.MacaroonEquals(c, ms[0], dischargeMac[0]) 165 } 166 167 func (s *CrossModelRelationsSuite) TestRegisterRemoteRelations(c *gc.C) { 168 var callCount int 169 mac, err := apitesting.NewMacaroon("id") 170 c.Assert(err, jc.ErrorIsNil) 171 apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 172 c.Check(objType, gc.Equals, "CrossModelRelations") 173 c.Check(version, gc.Equals, 0) 174 c.Check(id, gc.Equals, "") 175 c.Check(request, gc.Equals, "RegisterRemoteRelations") 176 c.Check(arg, gc.DeepEquals, params.RegisterRemoteRelationArgs{ 177 Relations: []params.RegisterRemoteRelationArg{{ 178 RelationToken: "token", 179 OfferUUID: "offer-uuid", 180 Macaroons: macaroon.Slice{mac}, 181 BakeryVersion: bakery.LatestVersion, 182 }}}) 183 c.Assert(result, gc.FitsTypeOf, ¶ms.RegisterRemoteRelationResults{}) 184 *(result.(*params.RegisterRemoteRelationResults)) = params.RegisterRemoteRelationResults{ 185 Results: []params.RegisterRemoteRelationResult{{ 186 Error: ¶ms.Error{Message: "FAIL"}, 187 }}, 188 } 189 callCount++ 190 return nil 191 }) 192 client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache) 193 result, err := client.RegisterRemoteRelations(params.RegisterRemoteRelationArg{ 194 RelationToken: "token", 195 OfferUUID: "offer-uuid", 196 Macaroons: macaroon.Slice{mac}, 197 BakeryVersion: bakery.LatestVersion, 198 }) 199 c.Check(err, jc.ErrorIsNil) 200 c.Assert(result, gc.HasLen, 1) 201 c.Check(result[0].Error, gc.ErrorMatches, "FAIL") 202 // Call again with a different macaroon but the first one will be 203 // cached and override the passed in macaroon. 204 different, err := apitesting.NewMacaroon("different") 205 c.Assert(err, jc.ErrorIsNil) 206 s.cache.Upsert("token", macaroon.Slice{mac}) 207 result, err = client.RegisterRemoteRelations(params.RegisterRemoteRelationArg{ 208 RelationToken: "token", 209 OfferUUID: "offer-uuid", 210 Macaroons: macaroon.Slice{different}, 211 BakeryVersion: bakery.LatestVersion, 212 }) 213 c.Check(err, jc.ErrorIsNil) 214 c.Assert(result, gc.HasLen, 1) 215 c.Check(result[0].Error, gc.ErrorMatches, "FAIL") 216 c.Check(callCount, gc.Equals, 2) 217 } 218 219 func (s *CrossModelRelationsSuite) TestRegisterRemoteRelationCount(c *gc.C) { 220 apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 221 *(result.(*params.RegisterRemoteRelationResults)) = params.RegisterRemoteRelationResults{ 222 Results: []params.RegisterRemoteRelationResult{ 223 {Error: ¶ms.Error{Message: "FAIL"}}, 224 {Error: ¶ms.Error{Message: "FAIL"}}, 225 }, 226 } 227 return nil 228 }) 229 client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache) 230 _, err := client.RegisterRemoteRelations(params.RegisterRemoteRelationArg{}) 231 c.Check(err, gc.ErrorMatches, `expected 1 result\(s\), got 2`) 232 } 233 234 func (s *CrossModelRelationsSuite) TestRegisterRemoteRelationDischargeRequired(c *gc.C) { 235 var ( 236 callCount int 237 dischargeMac macaroon.Slice 238 ) 239 apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 240 var resultErr *params.Error 241 if callCount == 0 { 242 mac := s.newDischargeMacaroon(c) 243 resultErr = ¶ms.Error{ 244 Code: params.CodeDischargeRequired, 245 Info: params.DischargeRequiredErrorInfo{ 246 Macaroon: mac, 247 }.AsMap(), 248 } 249 } 250 argParam := arg.(params.RegisterRemoteRelationArgs) 251 dischargeMac = argParam.Relations[0].Macaroons 252 resp := params.RegisterRemoteRelationResults{ 253 Results: []params.RegisterRemoteRelationResult{{Error: resultErr}}, 254 } 255 s.fillResponse(c, result, resp) 256 callCount++ 257 return nil 258 }) 259 acquirer := &mockDischargeAcquirer{} 260 callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer) 261 client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache) 262 result, err := client.RegisterRemoteRelations(params.RegisterRemoteRelationArg{ 263 RelationToken: "token", 264 OfferUUID: "offer-uuid"}) 265 c.Check(err, jc.ErrorIsNil) 266 c.Check(callCount, gc.Equals, 2) 267 c.Assert(result, gc.HasLen, 1) 268 c.Check(result[0].Error, gc.IsNil) 269 c.Assert(dischargeMac, gc.HasLen, 1) 270 c.Assert(dischargeMac[0].Id(), jc.DeepEquals, []byte("discharge mac")) 271 // Macaroon has been cached. 272 ms, ok := s.cache.Get("token") 273 c.Assert(ok, jc.IsTrue) 274 apitesting.MacaroonEquals(c, ms[0], dischargeMac[0]) 275 } 276 277 func (s *CrossModelRelationsSuite) TestWatchRelationChanges(c *gc.C) { 278 remoteRelationToken := "token" 279 mac, err := apitesting.NewMacaroon("id") 280 c.Assert(err, jc.ErrorIsNil) 281 var callCount int 282 apiCaller := testing.BestVersionCaller{ 283 APICallerFunc: testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 284 c.Check(objType, gc.Equals, "CrossModelRelations") 285 c.Check(version, gc.Equals, 2) 286 c.Check(id, gc.Equals, "") 287 c.Check(arg, jc.DeepEquals, params.RemoteEntityArgs{Args: []params.RemoteEntityArg{{ 288 Token: remoteRelationToken, Macaroons: macaroon.Slice{mac}, 289 BakeryVersion: bakery.LatestVersion, 290 }}}) 291 c.Check(request, gc.Equals, "WatchRelationChanges") 292 c.Assert(result, gc.FitsTypeOf, ¶ms.RemoteRelationWatchResults{}) 293 *(result.(*params.RemoteRelationWatchResults)) = params.RemoteRelationWatchResults{ 294 Results: []params.RemoteRelationWatchResult{{ 295 Error: ¶ms.Error{Message: "FAIL"}, 296 }}, 297 } 298 callCount++ 299 return nil 300 }), 301 BestVersion: 2, 302 } 303 client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache) 304 _, err = client.WatchRelationChanges( 305 remoteRelationToken, 306 "app-token", 307 macaroon.Slice{mac}, 308 ) 309 c.Check(err, gc.ErrorMatches, "FAIL") 310 // Call again with a different macaroon but the first one will be 311 // cached and override the passed in macaroon. 312 different, err := apitesting.NewMacaroon("different") 313 c.Assert(err, jc.ErrorIsNil) 314 s.cache.Upsert("token", macaroon.Slice{mac}) 315 _, err = client.WatchRelationChanges( 316 remoteRelationToken, 317 "app-token", 318 macaroon.Slice{different}, 319 ) 320 c.Check(err, gc.ErrorMatches, "FAIL") 321 c.Check(callCount, gc.Equals, 2) 322 } 323 324 func (s *CrossModelRelationsSuite) TestWatchRelationChangesDischargeRequired(c *gc.C) { 325 var ( 326 callCount int 327 dischargeMac macaroon.Slice 328 ) 329 apiCaller := testing.BestVersionCaller{ 330 BestVersion: 2, 331 APICallerFunc: testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 332 var resultErr *params.Error 333 c.Logf("called") 334 switch callCount { 335 case 2, 3: //Watcher Next, Stop 336 return nil 337 case 0: 338 mac := s.newDischargeMacaroon(c) 339 resultErr = ¶ms.Error{ 340 Code: params.CodeDischargeRequired, 341 Info: params.DischargeRequiredErrorInfo{ 342 Macaroon: mac, 343 }.AsMap(), 344 } 345 case 1: 346 argParam := arg.(params.RemoteEntityArgs) 347 dischargeMac = argParam.Args[0].Macaroons 348 } 349 resp := params.RemoteRelationWatchResults{ 350 Results: []params.RemoteRelationWatchResult{{Error: resultErr}}, 351 } 352 s.fillResponse(c, result, resp) 353 callCount++ 354 return nil 355 }), 356 } 357 acquirer := &mockDischargeAcquirer{} 358 callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer) 359 client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache) 360 _, err := client.WatchRelationChanges("token", "app-token", nil) 361 c.Check(callCount, gc.Equals, 2) 362 c.Check(err, jc.ErrorIsNil) 363 c.Assert(dischargeMac, gc.HasLen, 1) 364 c.Assert(dischargeMac[0].Id(), jc.DeepEquals, []byte("discharge mac")) 365 // Macaroon has been cached. 366 ms, ok := s.cache.Get("token") 367 c.Assert(ok, jc.IsTrue) 368 apitesting.MacaroonEquals(c, ms[0], dischargeMac[0]) 369 } 370 371 func (s *CrossModelRelationsSuite) TestWatchRelationStatus(c *gc.C) { 372 remoteRelationToken := "token" 373 mac, err := apitesting.NewMacaroon("id") 374 c.Assert(err, jc.ErrorIsNil) 375 var callCount int 376 apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 377 c.Check(objType, gc.Equals, "CrossModelRelations") 378 c.Check(version, gc.Equals, 0) 379 c.Check(id, gc.Equals, "") 380 c.Check(arg, jc.DeepEquals, params.RemoteEntityArgs{Args: []params.RemoteEntityArg{{ 381 Token: remoteRelationToken, Macaroons: macaroon.Slice{mac}, 382 BakeryVersion: bakery.LatestVersion, 383 }}}) 384 c.Check(request, gc.Equals, "WatchRelationsSuspendedStatus") 385 c.Assert(result, gc.FitsTypeOf, ¶ms.RelationStatusWatchResults{}) 386 *(result.(*params.RelationStatusWatchResults)) = params.RelationStatusWatchResults{ 387 Results: []params.RelationLifeSuspendedStatusWatchResult{{ 388 Error: ¶ms.Error{Message: "FAIL"}, 389 }}, 390 } 391 callCount++ 392 return nil 393 }) 394 client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache) 395 _, err = client.WatchRelationSuspendedStatus(params.RemoteEntityArg{ 396 Token: remoteRelationToken, 397 Macaroons: macaroon.Slice{mac}, 398 BakeryVersion: bakery.LatestVersion, 399 }) 400 c.Check(err, gc.ErrorMatches, "FAIL") 401 // Call again with a different macaroon but the first one will be 402 // cached and override the passed in macaroon. 403 different, err := apitesting.NewMacaroon("different") 404 c.Assert(err, jc.ErrorIsNil) 405 s.cache.Upsert("token", macaroon.Slice{mac}) 406 _, err = client.WatchRelationSuspendedStatus(params.RemoteEntityArg{ 407 Token: remoteRelationToken, 408 Macaroons: macaroon.Slice{different}, 409 BakeryVersion: bakery.LatestVersion, 410 }) 411 c.Check(err, gc.ErrorMatches, "FAIL") 412 c.Check(callCount, gc.Equals, 2) 413 } 414 415 func (s *CrossModelRelationsSuite) TestWatchRelationStatusDischargeRequired(c *gc.C) { 416 var ( 417 callCount int 418 dischargeMac macaroon.Slice 419 ) 420 apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 421 var resultErr *params.Error 422 switch callCount { 423 case 2, 3: //Watcher Next, Stop 424 return nil 425 case 0: 426 mac := s.newDischargeMacaroon(c) 427 resultErr = ¶ms.Error{ 428 Code: params.CodeDischargeRequired, 429 Info: params.DischargeRequiredErrorInfo{ 430 Macaroon: mac, 431 }.AsMap(), 432 } 433 case 1: 434 argParam := arg.(params.RemoteEntityArgs) 435 dischargeMac = argParam.Args[0].Macaroons 436 } 437 resp := params.RelationStatusWatchResults{ 438 Results: []params.RelationLifeSuspendedStatusWatchResult{{Error: resultErr}}, 439 } 440 s.fillResponse(c, result, resp) 441 callCount++ 442 return nil 443 }) 444 acquirer := &mockDischargeAcquirer{} 445 callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer) 446 client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache) 447 _, err := client.WatchRelationSuspendedStatus(params.RemoteEntityArg{Token: "token"}) 448 c.Check(callCount, gc.Equals, 2) 449 c.Check(err, jc.ErrorIsNil) 450 c.Assert(dischargeMac, gc.HasLen, 1) 451 c.Assert(dischargeMac[0].Id(), jc.DeepEquals, []byte("discharge mac")) 452 // Macaroon has been cached. 453 ms, ok := s.cache.Get("token") 454 c.Assert(ok, jc.IsTrue) 455 apitesting.MacaroonEquals(c, ms[0], dischargeMac[0]) 456 } 457 458 func (s *CrossModelRelationsSuite) TestPublishIngressNetworkChange(c *gc.C) { 459 mac, err := apitesting.NewMacaroon("id") 460 c.Assert(err, jc.ErrorIsNil) 461 var callCount int 462 apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 463 c.Check(objType, gc.Equals, "CrossModelRelations") 464 c.Check(version, gc.Equals, 0) 465 c.Check(id, gc.Equals, "") 466 c.Check(request, gc.Equals, "PublishIngressNetworkChanges") 467 c.Check(arg, gc.DeepEquals, params.IngressNetworksChanges{ 468 Changes: []params.IngressNetworksChangeEvent{{ 469 RelationToken: "token", 470 Networks: []string{"1.2.3.4/32"}, Macaroons: macaroon.Slice{mac}, 471 BakeryVersion: bakery.LatestVersion, 472 }}, 473 }) 474 c.Assert(result, gc.FitsTypeOf, ¶ms.ErrorResults{}) 475 *(result.(*params.ErrorResults)) = params.ErrorResults{ 476 Results: []params.ErrorResult{{ 477 Error: ¶ms.Error{Message: "FAIL"}, 478 }}, 479 } 480 callCount++ 481 return nil 482 }) 483 client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache) 484 err = client.PublishIngressNetworkChange(params.IngressNetworksChangeEvent{ 485 RelationToken: "token", 486 Networks: []string{"1.2.3.4/32"}, Macaroons: macaroon.Slice{mac}, 487 BakeryVersion: bakery.LatestVersion, 488 }) 489 c.Check(err, gc.ErrorMatches, "FAIL") 490 // Call again with a different macaroon but the first one will be 491 // cached and override the passed in macaroon. 492 different, err := apitesting.NewMacaroon("different") 493 c.Assert(err, jc.ErrorIsNil) 494 s.cache.Upsert("token", macaroon.Slice{mac}) 495 err = client.PublishIngressNetworkChange(params.IngressNetworksChangeEvent{ 496 RelationToken: "token", 497 Networks: []string{"1.2.3.4/32"}, Macaroons: macaroon.Slice{different}, 498 BakeryVersion: bakery.LatestVersion, 499 }) 500 c.Check(err, gc.ErrorMatches, "FAIL") 501 c.Check(callCount, gc.Equals, 2) 502 } 503 504 func (s *CrossModelRelationsSuite) TestPublishIngressNetworkChangeDischargeRequired(c *gc.C) { 505 var ( 506 callCount int 507 dischargeMac macaroon.Slice 508 ) 509 apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 510 var resultErr *params.Error 511 if callCount == 0 { 512 mac := s.newDischargeMacaroon(c) 513 resultErr = ¶ms.Error{ 514 Code: params.CodeDischargeRequired, 515 Info: params.DischargeRequiredErrorInfo{ 516 Macaroon: mac, 517 }.AsMap(), 518 } 519 } 520 argParam := arg.(params.IngressNetworksChanges) 521 dischargeMac = argParam.Changes[0].Macaroons 522 resp := params.ErrorResults{ 523 Results: []params.ErrorResult{{Error: resultErr}}, 524 } 525 s.fillResponse(c, result, resp) 526 callCount++ 527 return nil 528 }) 529 acquirer := &mockDischargeAcquirer{} 530 callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer) 531 client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache) 532 err := client.PublishIngressNetworkChange(params.IngressNetworksChangeEvent{ 533 RelationToken: "token", 534 Networks: []string{"1.2.3.4/32"}}) 535 c.Check(callCount, gc.Equals, 2) 536 c.Check(err, jc.ErrorIsNil) 537 c.Assert(dischargeMac, gc.HasLen, 1) 538 c.Assert(dischargeMac[0].Id(), jc.DeepEquals, []byte("discharge mac")) 539 // Macaroon has been cached. 540 ms, ok := s.cache.Get("token") 541 c.Assert(ok, jc.IsTrue) 542 apitesting.MacaroonEquals(c, ms[0], dischargeMac[0]) 543 } 544 545 func (s *CrossModelRelationsSuite) TestWatchEgressAddressesForRelation(c *gc.C) { 546 var callCount int 547 remoteRelationToken := "token" 548 mac, err := apitesting.NewMacaroon("id") 549 relation := params.RemoteEntityArg{ 550 Token: remoteRelationToken, Macaroons: macaroon.Slice{mac}, 551 BakeryVersion: bakery.LatestVersion, 552 } 553 c.Check(err, jc.ErrorIsNil) 554 apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 555 c.Check(objType, gc.Equals, "CrossModelRelations") 556 c.Check(version, gc.Equals, 0) 557 c.Check(id, gc.Equals, "") 558 c.Check(request, gc.Equals, "WatchEgressAddressesForRelations") 559 c.Check(arg, gc.DeepEquals, params.RemoteEntityArgs{ 560 Args: []params.RemoteEntityArg{relation}}) 561 c.Assert(result, gc.FitsTypeOf, ¶ms.StringsWatchResults{}) 562 *(result.(*params.StringsWatchResults)) = params.StringsWatchResults{ 563 Results: []params.StringsWatchResult{{ 564 Error: ¶ms.Error{Message: "FAIL"}, 565 }}, 566 } 567 callCount++ 568 return nil 569 }) 570 client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache) 571 _, err = client.WatchEgressAddressesForRelation(relation) 572 c.Check(err, gc.ErrorMatches, "FAIL") 573 // Call again with a different macaroon but the first one will be 574 // cached and override the passed in macaroon. 575 different, err := apitesting.NewMacaroon("different") 576 c.Assert(err, jc.ErrorIsNil) 577 s.cache.Upsert("token", macaroon.Slice{mac}) 578 rel2 := relation 579 rel2.Macaroons = macaroon.Slice{different} 580 rel2.BakeryVersion = bakery.LatestVersion 581 _, err = client.WatchEgressAddressesForRelation(rel2) 582 c.Check(err, gc.ErrorMatches, "FAIL") 583 c.Check(callCount, gc.Equals, 2) 584 } 585 586 func (s *CrossModelRelationsSuite) TestWatchEgressAddressesForRelationDischargeRequired(c *gc.C) { 587 var ( 588 callCount int 589 mac *macaroon.Macaroon 590 dischargeMac macaroon.Slice 591 ) 592 apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 593 var resultErr *params.Error 594 switch callCount { 595 case 2, 3: //Watcher Next, Stop 596 return nil 597 case 0: 598 mac = s.newDischargeMacaroon(c) 599 resultErr = ¶ms.Error{ 600 Code: params.CodeDischargeRequired, 601 Info: params.DischargeRequiredErrorInfo{ 602 Macaroon: mac, 603 }.AsMap(), 604 } 605 case 1: 606 argParam := arg.(params.RemoteEntityArgs) 607 dischargeMac = argParam.Args[0].Macaroons 608 } 609 resp := params.StringsWatchResults{ 610 Results: []params.StringsWatchResult{{Error: resultErr}}, 611 } 612 s.fillResponse(c, result, resp) 613 callCount++ 614 return nil 615 }) 616 acquirer := &mockDischargeAcquirer{} 617 callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer) 618 client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache) 619 _, err := client.WatchEgressAddressesForRelation(params.RemoteEntityArg{Token: "token"}) 620 c.Check(callCount, gc.Equals, 2) 621 c.Check(err, jc.ErrorIsNil) 622 c.Assert(dischargeMac, gc.HasLen, 1) 623 c.Assert(dischargeMac[0].Id(), jc.DeepEquals, []byte("discharge mac")) 624 // Macaroon has been cached. 625 ms, ok := s.cache.Get("token") 626 c.Assert(ok, jc.IsTrue) 627 apitesting.MacaroonEquals(c, ms[0], dischargeMac[0]) 628 } 629 630 func (s *CrossModelRelationsSuite) TestWatchOfferStatus(c *gc.C) { 631 offerUUID := "offer-uuid" 632 mac, err := apitesting.NewMacaroon("id") 633 c.Assert(err, jc.ErrorIsNil) 634 var callCount int 635 apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 636 c.Check(objType, gc.Equals, "CrossModelRelations") 637 c.Check(version, gc.Equals, 0) 638 c.Check(id, gc.Equals, "") 639 c.Check(arg, jc.DeepEquals, params.OfferArgs{Args: []params.OfferArg{{ 640 OfferUUID: offerUUID, Macaroons: macaroon.Slice{mac}, 641 BakeryVersion: bakery.LatestVersion, 642 }}}) 643 c.Check(request, gc.Equals, "WatchOfferStatus") 644 c.Assert(result, gc.FitsTypeOf, ¶ms.OfferStatusWatchResults{}) 645 *(result.(*params.OfferStatusWatchResults)) = params.OfferStatusWatchResults{ 646 Results: []params.OfferStatusWatchResult{{ 647 Error: ¶ms.Error{Message: "FAIL"}, 648 }}, 649 } 650 callCount++ 651 return nil 652 }) 653 client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache) 654 _, err = client.WatchOfferStatus(params.OfferArg{ 655 OfferUUID: offerUUID, 656 Macaroons: macaroon.Slice{mac}, 657 BakeryVersion: bakery.LatestVersion, 658 }) 659 c.Check(err, gc.ErrorMatches, "FAIL") 660 // Call again with a different macaroon but the first one will be 661 // cached and override the passed in macaroon. 662 different, err := apitesting.NewMacaroon("different") 663 c.Assert(err, jc.ErrorIsNil) 664 s.cache.Upsert("offer-uuid", macaroon.Slice{mac}) 665 _, err = client.WatchOfferStatus(params.OfferArg{ 666 OfferUUID: offerUUID, 667 Macaroons: macaroon.Slice{different}, 668 BakeryVersion: bakery.LatestVersion, 669 }) 670 c.Check(err, gc.ErrorMatches, "FAIL") 671 c.Check(callCount, gc.Equals, 2) 672 } 673 674 func (s *CrossModelRelationsSuite) TestWatchOfferStatusDischargeRequired(c *gc.C) { 675 var ( 676 callCount int 677 dischargeMac macaroon.Slice 678 ) 679 apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 680 var resultErr *params.Error 681 switch callCount { 682 case 2, 3: //Watcher Next, Stop 683 return nil 684 case 0: 685 mac := s.newDischargeMacaroon(c) 686 resultErr = ¶ms.Error{ 687 Code: params.CodeDischargeRequired, 688 Info: params.DischargeRequiredErrorInfo{ 689 Macaroon: mac, 690 }.AsMap(), 691 } 692 case 1: 693 argParam := arg.(params.OfferArgs) 694 dischargeMac = argParam.Args[0].Macaroons 695 } 696 resp := params.OfferStatusWatchResults{ 697 Results: []params.OfferStatusWatchResult{{Error: resultErr}}, 698 } 699 s.fillResponse(c, result, resp) 700 callCount++ 701 return nil 702 }) 703 acquirer := &mockDischargeAcquirer{} 704 callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer) 705 client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache) 706 _, err := client.WatchOfferStatus(params.OfferArg{OfferUUID: "offer-uuid"}) 707 c.Check(callCount, gc.Equals, 2) 708 c.Check(err, jc.ErrorIsNil) 709 c.Assert(dischargeMac, gc.HasLen, 1) 710 c.Assert(dischargeMac[0].Id(), jc.DeepEquals, []byte("discharge mac")) 711 // Macaroon has been cached. 712 ms, ok := s.cache.Get("offer-uuid") 713 c.Assert(ok, jc.IsTrue) 714 apitesting.MacaroonEquals(c, ms[0], dischargeMac[0]) 715 } 716 717 func (s *CrossModelRelationsSuite) TestWatchConsumedSecretsChanges(c *gc.C) { 718 appToken := "app-token" 719 relToken := "rel-token" 720 mac, err := apitesting.NewMacaroon("id") 721 c.Assert(err, jc.ErrorIsNil) 722 var callCount int 723 apiCaller := testing.BestVersionCaller{APICallerFunc: testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 724 c.Check(objType, gc.Equals, "CrossModelRelations") 725 c.Check(version, gc.Equals, 3) 726 c.Check(id, gc.Equals, "") 727 c.Check(arg, jc.DeepEquals, params.WatchRemoteSecretChangesArgs{Args: []params.WatchRemoteSecretChangesArg{{ 728 ApplicationToken: appToken, RelationToken: relToken, Macaroons: macaroon.Slice{mac}, 729 BakeryVersion: bakery.LatestVersion, 730 }}}) 731 c.Check(request, gc.Equals, "WatchConsumedSecretsChanges") 732 c.Assert(result, gc.FitsTypeOf, ¶ms.SecretRevisionWatchResults{}) 733 *(result.(*params.SecretRevisionWatchResults)) = params.SecretRevisionWatchResults{ 734 Results: []params.SecretRevisionWatchResult{{ 735 Error: ¶ms.Error{Message: "FAIL"}, 736 }}, 737 } 738 callCount++ 739 return nil 740 }), BestVersion: 3} 741 client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache) 742 _, err = client.WatchConsumedSecretsChanges(appToken, relToken, mac) 743 c.Check(err, gc.ErrorMatches, "FAIL") 744 // Call again with a different macaroon but the first one will be 745 // cached and override the passed in macaroon. 746 different, err := apitesting.NewMacaroon("different") 747 c.Assert(err, jc.ErrorIsNil) 748 s.cache.Upsert(relToken, macaroon.Slice{mac}) 749 _, err = client.WatchConsumedSecretsChanges(appToken, relToken, different) 750 c.Check(err, gc.ErrorMatches, "FAIL") 751 c.Check(callCount, gc.Equals, 2) 752 } 753 754 func (s *CrossModelRelationsSuite) TestWatchConsumedSecretsChangesDischargeRequired(c *gc.C) { 755 var ( 756 callCount int 757 dischargeMac macaroon.Slice 758 ) 759 apiCaller := testing.BestVersionCaller{APICallerFunc: testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { 760 var resultErr *params.Error 761 switch callCount { 762 case 2, 3: //Watcher Next, Stop 763 return nil 764 case 0: 765 mac := s.newDischargeMacaroon(c) 766 resultErr = ¶ms.Error{ 767 Code: params.CodeDischargeRequired, 768 Info: params.DischargeRequiredErrorInfo{ 769 Macaroon: mac, 770 }.AsMap(), 771 } 772 case 1: 773 argParam := arg.(params.WatchRemoteSecretChangesArgs) 774 dischargeMac = argParam.Args[0].Macaroons 775 } 776 resp := params.SecretRevisionWatchResults{ 777 Results: []params.SecretRevisionWatchResult{{Error: resultErr}}, 778 } 779 s.fillResponse(c, result, resp) 780 callCount++ 781 return nil 782 }), BestVersion: 3} 783 acquirer := &mockDischargeAcquirer{} 784 callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer) 785 client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache) 786 _, err := client.WatchConsumedSecretsChanges("app-token", "rel-token", nil) 787 c.Check(callCount, gc.Equals, 2) 788 c.Check(err, jc.ErrorIsNil) 789 c.Assert(dischargeMac, gc.HasLen, 1) 790 c.Assert(dischargeMac[0].Id(), jc.DeepEquals, []byte("discharge mac")) 791 // Macaroon has been cached. 792 ms, ok := s.cache.Get("rel-token") 793 c.Assert(ok, jc.IsTrue) 794 apitesting.MacaroonEquals(c, ms[0], dischargeMac[0]) 795 }