github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/api/crossmodelrelations/crossmodelrelations.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package crossmodelrelations 5 6 import ( 7 "github.com/juju/clock" 8 "github.com/juju/errors" 9 "github.com/juju/loggo" 10 "gopkg.in/macaroon.v2-unstable" 11 12 "github.com/juju/juju/api/base" 13 apiwatcher "github.com/juju/juju/api/watcher" 14 "github.com/juju/juju/apiserver/params" 15 "github.com/juju/juju/core/watcher" 16 ) 17 18 var logger = loggo.GetLogger("juju.api.crossmodelrelations") 19 20 // Client provides access to the crossmodelrelations api facade. 21 type Client struct { 22 base.ClientFacade 23 facade base.FacadeCaller 24 25 cache *MacaroonCache 26 } 27 28 // NewClient creates a new client-side CrossModelRelations facade. 29 func NewClient(caller base.APICallCloser) *Client { 30 return NewClientWithCache(caller, NewMacaroonCache(clock.WallClock)) 31 } 32 33 // NewClientWithCache creates a new client-side CrossModelRelations facade 34 // with the specified cache. 35 func NewClientWithCache(caller base.APICallCloser, cache *MacaroonCache) *Client { 36 frontend, backend := base.NewClientFacade(caller, "CrossModelRelations") 37 return &Client{ 38 ClientFacade: frontend, 39 facade: backend, 40 cache: cache, 41 } 42 } 43 44 // handleError is used to process an error obtained when making a facade call. 45 // If the error indicates that a macaroon discharge is required, this is done 46 // and the resulting discharge macaroons passed back so the api call can be retried. 47 func (c *Client) handleError(apiErr error) (macaroon.Slice, error) { 48 if params.ErrCode(apiErr) != params.CodeDischargeRequired { 49 return nil, apiErr 50 } 51 errResp := errors.Cause(apiErr).(*params.Error) 52 if errResp.Info == nil { 53 return nil, errors.Annotatef(apiErr, "no error info found in discharge-required response error") 54 } 55 logger.Debugf("attempting to discharge macaroon due to error: %v", apiErr) 56 ms, err := c.facade.RawAPICaller().BakeryClient().DischargeAll(errResp.Info.Macaroon) 57 if err == nil && logger.IsTraceEnabled() { 58 logger.Tracef("discharge macaroon ids:") 59 for _, m := range ms { 60 logger.Tracef(" - %v", m.Id()) 61 } 62 } 63 if err != nil { 64 return nil, errors.Wrap(apiErr, err) 65 } 66 return ms, err 67 } 68 69 func (c *Client) getCachedMacaroon(opName, token string) (macaroon.Slice, bool) { 70 ms, ok := c.cache.Get(token) 71 if ok { 72 logger.Debugf("%s using cached macaroons for %s", opName, token) 73 if logger.IsTraceEnabled() { 74 for _, m := range ms { 75 logger.Tracef(" - %v", m.Id()) 76 } 77 } 78 } 79 return ms, ok 80 } 81 82 // PublishRelationChange publishes relation changes to the 83 // model hosting the remote application involved in the relation. 84 func (c *Client) PublishRelationChange(change params.RemoteRelationChangeEvent) error { 85 args := params.RemoteRelationsChanges{ 86 Changes: []params.RemoteRelationChangeEvent{change}, 87 } 88 // Use any previously cached discharge macaroons. 89 if ms, ok := c.getCachedMacaroon("publish relation changed", change.RelationToken); ok { 90 args.Changes[0].Macaroons = ms 91 } 92 93 apiCall := func() error { 94 var results params.ErrorResults 95 if err := c.facade.FacadeCall("PublishRelationChanges", args, &results); err != nil { 96 return errors.Trace(err) 97 } 98 err := results.OneError() 99 if params.IsCodeNotFound(err) { 100 return errors.NotFoundf("relation for event %v", change) 101 } 102 return err 103 } 104 // Make the api call the first time. 105 err := apiCall() 106 if err == nil || errors.IsNotFound(err) { 107 return errors.Trace(err) 108 } 109 110 // On error, possibly discharge the macaroon and retry. 111 mac, err2 := c.handleError(err) 112 if err2 != nil { 113 return errors.Trace(err2) 114 } 115 args.Changes[0].Macaroons = mac 116 c.cache.Upsert(args.Changes[0].RelationToken, mac) 117 return apiCall() 118 } 119 120 func (c *Client) PublishIngressNetworkChange(change params.IngressNetworksChangeEvent) error { 121 args := params.IngressNetworksChanges{ 122 Changes: []params.IngressNetworksChangeEvent{change}, 123 } 124 // Use any previously cached discharge macaroons. 125 if ms, ok := c.getCachedMacaroon("publish ingress network change", change.RelationToken); ok { 126 args.Changes[0].Macaroons = ms 127 } 128 129 apiCall := func() error { 130 var results params.ErrorResults 131 if err := c.facade.FacadeCall("PublishIngressNetworkChanges", args, &results); err != nil { 132 return errors.Trace(err) 133 } 134 return results.OneError() 135 } 136 137 // Make the api call the first time. 138 err := apiCall() 139 if err == nil { 140 return nil 141 } 142 143 // On error, possibly discharge the macaroon and retry. 144 mac, err2 := c.handleError(err) 145 if err2 != nil { 146 return errors.Trace(err2) 147 } 148 args.Changes[0].Macaroons = mac 149 c.cache.Upsert(args.Changes[0].RelationToken, mac) 150 return apiCall() 151 } 152 153 // RegisterRemoteRelations sets up the remote model to participate 154 // in the specified relations. 155 func (c *Client) RegisterRemoteRelations(relations ...params.RegisterRemoteRelationArg) ([]params.RegisterRemoteRelationResult, error) { 156 var ( 157 args params.RegisterRemoteRelationArgs 158 retryIndices []int 159 ) 160 161 args = params.RegisterRemoteRelationArgs{Relations: relations} 162 // Use any previously cached discharge macaroons. 163 for i, arg := range relations { 164 if ms, ok := c.getCachedMacaroon("register remote relation", arg.RelationToken); ok { 165 newArg := arg 166 newArg.Macaroons = ms 167 args.Relations[i] = newArg 168 } 169 } 170 171 var results params.RegisterRemoteRelationResults 172 apiCall := func() error { 173 // Reset the results struct before each api call. 174 results = params.RegisterRemoteRelationResults{} 175 err := c.facade.FacadeCall("RegisterRemoteRelations", args, &results) 176 if err != nil { 177 return errors.Trace(err) 178 } 179 if len(results.Results) != len(args.Relations) { 180 return errors.Errorf("expected %d result(s), got %d", len(args.Relations), len(results.Results)) 181 } 182 return nil 183 } 184 185 // Make the api call the first time. 186 if err := apiCall(); err != nil { 187 return nil, errors.Trace(err) 188 } 189 // On error, possibly discharge the macaroon and retry. 190 result := results.Results 191 args = params.RegisterRemoteRelationArgs{} 192 // Separate the successful calls from those needing a retry. 193 for i, res := range results.Results { 194 if res.Error == nil { 195 continue 196 } 197 mac, err := c.handleError(res.Error) 198 if err != nil { 199 resCopy := res 200 resCopy.Error.Message = err.Error() 201 result[i] = resCopy 202 continue 203 } 204 retryArg := relations[i] 205 retryArg.Macaroons = mac 206 args.Relations = append(args.Relations, retryArg) 207 retryIndices = append(retryIndices, i) 208 c.cache.Upsert(retryArg.RelationToken, mac) 209 } 210 // Nothing to retry so return the original result. 211 if len(args.Relations) == 0 { 212 return result, nil 213 } 214 215 if err := apiCall(); err != nil { 216 return nil, errors.Trace(err) 217 } 218 // After a retry, insert the results into the original result slice. 219 for j, res := range results.Results { 220 resCopy := res 221 result[retryIndices[j]] = resCopy 222 } 223 return result, nil 224 } 225 226 // WatchRelationUnits returns a watcher that notifies of changes to the 227 // units in the remote model for the relation with the given remote token. 228 func (c *Client) WatchRelationUnits(remoteRelationArg params.RemoteEntityArg) (watcher.RelationUnitsWatcher, error) { 229 args := params.RemoteEntityArgs{Args: []params.RemoteEntityArg{remoteRelationArg}} 230 // Use any previously cached discharge macaroons. 231 if ms, ok := c.getCachedMacaroon("watch relation units", remoteRelationArg.Token); ok { 232 args.Args[0].Macaroons = ms 233 } 234 235 var results params.RelationUnitsWatchResults 236 apiCall := func() error { 237 // Reset the results struct before each api call. 238 results = params.RelationUnitsWatchResults{} 239 if err := c.facade.FacadeCall("WatchRelationUnits", args, &results); err != nil { 240 return errors.Trace(err) 241 } 242 if len(results.Results) != 1 { 243 return errors.Errorf("expected 1 result, got %d", len(results.Results)) 244 } 245 return nil 246 } 247 248 // Make the api call the first time. 249 if err := apiCall(); err != nil { 250 return nil, errors.Trace(err) 251 } 252 253 // On error, possibly discharge the macaroon and retry. 254 result := results.Results[0] 255 if result.Error != nil { 256 mac, err := c.handleError(result.Error) 257 if err != nil { 258 result.Error.Message = err.Error() 259 return nil, result.Error 260 } 261 args.Args[0].Macaroons = mac 262 c.cache.Upsert(args.Args[0].Token, mac) 263 264 if err := apiCall(); err != nil { 265 return nil, errors.Trace(err) 266 } 267 result = results.Results[0] 268 } 269 if result.Error != nil { 270 return nil, result.Error 271 } 272 273 w := apiwatcher.NewRelationUnitsWatcher(c.facade.RawAPICaller(), result) 274 return w, nil 275 } 276 277 // RelationUnitSettings returns the relation unit settings for the given relation units in the remote model. 278 func (c *Client) RelationUnitSettings(relationUnits []params.RemoteRelationUnit) ([]params.SettingsResult, error) { 279 var ( 280 args params.RemoteRelationUnits 281 retryIndices []int 282 ) 283 284 args = params.RemoteRelationUnits{RelationUnits: relationUnits} 285 // Use any previously cached discharge macaroons. 286 for i, arg := range args.RelationUnits { 287 if ms, ok := c.getCachedMacaroon("relation unit settings", arg.RelationToken); ok { 288 newArg := arg 289 newArg.Macaroons = ms 290 args.RelationUnits[i] = newArg 291 } 292 } 293 294 var results params.SettingsResults 295 apiCall := func() error { 296 // Reset the results struct before each api call. 297 results = params.SettingsResults{} 298 err := c.facade.FacadeCall("RelationUnitSettings", args, &results) 299 if err != nil { 300 return errors.Trace(err) 301 } 302 if len(results.Results) != len(args.RelationUnits) { 303 return errors.Errorf("expected %d result(s), got %d", len(args.RelationUnits), len(results.Results)) 304 } 305 return nil 306 } 307 308 // Make the api call the first time. 309 if err := apiCall(); err != nil { 310 return nil, errors.Trace(err) 311 } 312 // On error, possibly discharge the macaroon and retry. 313 result := results.Results 314 args = params.RemoteRelationUnits{} 315 // Separate the successful calls from those needing a retry. 316 for i, res := range results.Results { 317 if res.Error == nil { 318 continue 319 } 320 mac, err := c.handleError(res.Error) 321 if err != nil { 322 resCopy := res 323 resCopy.Error.Message = err.Error() 324 result[i] = resCopy 325 continue 326 } 327 retryArg := relationUnits[i] 328 retryArg.Macaroons = mac 329 args.RelationUnits = append(args.RelationUnits, retryArg) 330 retryIndices = append(retryIndices, i) 331 c.cache.Upsert(retryArg.RelationToken, mac) 332 } 333 // Nothing to retry so return the original result. 334 if len(args.RelationUnits) == 0 { 335 return result, nil 336 } 337 338 if err := apiCall(); err != nil { 339 return nil, errors.Trace(err) 340 } 341 // After a retry, insert the results into the original result slice. 342 for j, res := range results.Results { 343 resCopy := res 344 result[retryIndices[j]] = resCopy 345 } 346 return result, nil 347 } 348 349 // WatchEgressAddressesForRelation returns a watcher that notifies when addresses, 350 // from which connections will originate to the offering side of the relation, change. 351 // Each event contains the entire set of addresses which the offering side is required 352 // to allow for access to the other side of the relation. 353 func (c *Client) WatchEgressAddressesForRelation(remoteRelationArg params.RemoteEntityArg) (watcher.StringsWatcher, error) { 354 args := params.RemoteEntityArgs{Args: []params.RemoteEntityArg{remoteRelationArg}} 355 // Use any previously cached discharge macaroons. 356 if ms, ok := c.getCachedMacaroon("watch relation egress addresses", remoteRelationArg.Token); ok { 357 args.Args[0].Macaroons = ms 358 } 359 360 var results params.StringsWatchResults 361 apiCall := func() error { 362 // Reset the results struct before each api call. 363 results = params.StringsWatchResults{} 364 if err := c.facade.FacadeCall("WatchEgressAddressesForRelations", args, &results); err != nil { 365 return errors.Trace(err) 366 } 367 if len(results.Results) != 1 { 368 return errors.Errorf("expected 1 result, got %d", len(results.Results)) 369 } 370 return nil 371 } 372 373 // Make the api call the first time. 374 if err := apiCall(); err != nil { 375 return nil, errors.Trace(err) 376 } 377 378 // On error, possibly discharge the macaroon and retry. 379 result := results.Results[0] 380 if result.Error != nil { 381 mac, err := c.handleError(result.Error) 382 if err != nil { 383 result.Error.Message = err.Error() 384 return nil, result.Error 385 } 386 args.Args[0].Macaroons = mac 387 c.cache.Upsert(args.Args[0].Token, mac) 388 389 if err := apiCall(); err != nil { 390 return nil, errors.Trace(err) 391 } 392 result = results.Results[0] 393 } 394 if result.Error != nil { 395 return nil, result.Error 396 } 397 398 w := apiwatcher.NewStringsWatcher(c.facade.RawAPICaller(), result) 399 return w, nil 400 } 401 402 // WatchRelationSuspendedStatus starts a RelationStatusWatcher for watching the life and 403 // suspended status of the specified relation in the remote model. 404 func (c *Client) WatchRelationSuspendedStatus(arg params.RemoteEntityArg) (watcher.RelationStatusWatcher, error) { 405 args := params.RemoteEntityArgs{Args: []params.RemoteEntityArg{arg}} 406 // Use any previously cached discharge macaroons. 407 if ms, ok := c.getCachedMacaroon("watch relation status", arg.Token); ok { 408 args.Args[0].Macaroons = ms 409 } 410 411 var results params.RelationStatusWatchResults 412 apiCall := func() error { 413 // Reset the results struct before each api call. 414 results = params.RelationStatusWatchResults{} 415 if err := c.facade.FacadeCall("WatchRelationsSuspendedStatus", args, &results); err != nil { 416 return errors.Trace(err) 417 } 418 if len(results.Results) != 1 { 419 return errors.Errorf("expected 1 result, got %d", len(results.Results)) 420 } 421 return nil 422 } 423 424 // Make the api call the first time. 425 if err := apiCall(); err != nil { 426 return nil, errors.Trace(err) 427 } 428 429 // On error, possibly discharge the macaroon and retry. 430 result := results.Results[0] 431 if result.Error != nil { 432 mac, err := c.handleError(result.Error) 433 if err != nil { 434 result.Error.Message = err.Error() 435 return nil, result.Error 436 } 437 args.Args[0].Macaroons = mac 438 c.cache.Upsert(args.Args[0].Token, mac) 439 440 if err := apiCall(); err != nil { 441 return nil, errors.Trace(err) 442 } 443 result = results.Results[0] 444 } 445 if result.Error != nil { 446 return nil, result.Error 447 } 448 449 w := apiwatcher.NewRelationStatusWatcher(c.facade.RawAPICaller(), result) 450 return w, nil 451 } 452 453 // WatchOfferStatus starts an OfferStatusWatcher for watching the status 454 // of the specified offer in the remote model. 455 func (c *Client) WatchOfferStatus(arg params.OfferArg) (watcher.OfferStatusWatcher, error) { 456 args := params.OfferArgs{Args: []params.OfferArg{arg}} 457 // Use any previously cached discharge macaroons. 458 if ms, ok := c.getCachedMacaroon("watch offer status", arg.OfferUUID); ok { 459 args.Args[0].Macaroons = ms 460 } 461 462 var results params.OfferStatusWatchResults 463 apiCall := func() error { 464 // Reset the results struct before each api call. 465 results = params.OfferStatusWatchResults{} 466 if err := c.facade.FacadeCall("WatchOfferStatus", args, &results); err != nil { 467 return errors.Trace(err) 468 } 469 if len(results.Results) != 1 { 470 return errors.Errorf("expected 1 result, got %d", len(results.Results)) 471 } 472 return nil 473 } 474 475 // Make the api call the first time. 476 if err := apiCall(); err != nil { 477 return nil, errors.Trace(err) 478 } 479 480 // On error, possibly discharge the macaroon and retry. 481 result := results.Results[0] 482 if result.Error != nil { 483 mac, err := c.handleError(result.Error) 484 if err != nil { 485 result.Error.Message = err.Error() 486 return nil, result.Error 487 } 488 args.Args[0].Macaroons = mac 489 c.cache.Upsert(args.Args[0].OfferUUID, mac) 490 491 if err := apiCall(); err != nil { 492 return nil, errors.Trace(err) 493 } 494 result = results.Results[0] 495 } 496 if result.Error != nil { 497 return nil, result.Error 498 } 499 500 w := apiwatcher.NewOfferStatusWatcher(c.facade.RawAPICaller(), result) 501 return w, nil 502 }