github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/api/agent/uniter/uniter.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter 5 6 import ( 7 "fmt" 8 9 "github.com/juju/collections/transform" 10 "github.com/juju/errors" 11 "github.com/juju/names/v5" 12 13 "github.com/juju/juju/api" 14 "github.com/juju/juju/api/base" 15 "github.com/juju/juju/api/common" 16 apiwatcher "github.com/juju/juju/api/watcher" 17 apiservererrors "github.com/juju/juju/apiserver/errors" 18 "github.com/juju/juju/core/application" 19 "github.com/juju/juju/core/life" 20 "github.com/juju/juju/core/model" 21 "github.com/juju/juju/core/network" 22 "github.com/juju/juju/core/relation" 23 "github.com/juju/juju/core/watcher" 24 "github.com/juju/juju/rpc/params" 25 ) 26 27 const uniterFacade = "Uniter" 28 29 // State provides access to the Uniter API facade. 30 type State struct { 31 *common.ModelWatcher 32 *common.APIAddresser 33 *common.UpgradeSeriesAPI 34 *common.UnitStateAPI 35 *StorageAccessor 36 37 LeadershipSettings *LeadershipSettingsAccessor 38 facade base.FacadeCaller 39 // unitTag contains the authenticated unit's tag. 40 unitTag names.UnitTag 41 } 42 43 // NewState creates a new client-side Uniter facade. 44 func NewState( 45 caller base.APICaller, 46 authTag names.UnitTag, 47 ) *State { 48 facadeCaller := base.NewFacadeCaller( 49 caller, 50 uniterFacade, 51 ) 52 state := &State{ 53 ModelWatcher: common.NewModelWatcher(facadeCaller), 54 APIAddresser: common.NewAPIAddresser(facadeCaller), 55 UpgradeSeriesAPI: common.NewUpgradeSeriesAPI(facadeCaller, authTag), 56 UnitStateAPI: common.NewUniterStateAPI(facadeCaller, authTag), 57 StorageAccessor: NewStorageAccessor(facadeCaller), 58 facade: facadeCaller, 59 unitTag: authTag, 60 } 61 62 newWatcher := func(result params.NotifyWatchResult) watcher.NotifyWatcher { 63 return apiwatcher.NewNotifyWatcher(caller, result) 64 } 65 state.LeadershipSettings = NewLeadershipSettingsAccessor( 66 facadeCaller.FacadeCall, 67 newWatcher, 68 ) 69 return state 70 } 71 72 // NewFromConnection returns a version of the Connection that provides 73 // functionality required by the uniter worker if possible else a non-nil error. 74 func NewFromConnection(c api.Connection) (*State, error) { 75 authTag := c.AuthTag() 76 unitTag, ok := authTag.(names.UnitTag) 77 if !ok { 78 return nil, errors.Errorf("expected UnitTag, got %T %v", authTag, authTag) 79 } 80 return NewState(c, unitTag), nil 81 } 82 83 // BestAPIVersion returns the API version that we were able to 84 // determine is supported by both the client and the API Server. 85 func (st *State) BestAPIVersion() int { 86 return st.facade.BestAPIVersion() 87 } 88 89 // Facade returns the current facade. 90 func (st *State) Facade() base.FacadeCaller { 91 return st.facade 92 } 93 94 // life requests the lifecycle of the given entity from the server. 95 func (st *State) life(tag names.Tag) (life.Value, error) { 96 return common.OneLife(st.facade, tag) 97 } 98 99 // relation requests relation information from the server. 100 func (st *State) relation(relationTag, unitTag names.Tag) (params.RelationResult, error) { 101 nothing := params.RelationResult{} 102 var result params.RelationResults 103 args := params.RelationUnits{ 104 RelationUnits: []params.RelationUnit{ 105 {Relation: relationTag.String(), Unit: unitTag.String()}, 106 }, 107 } 108 err := st.facade.FacadeCall("Relation", args, &result) 109 if err != nil { 110 return nothing, err 111 } 112 if len(result.Results) != 1 { 113 return nothing, fmt.Errorf("expected 1 result, got %d", len(result.Results)) 114 } 115 if err := result.Results[0].Error; err != nil { 116 return nothing, err 117 } 118 return result.Results[0], nil 119 } 120 121 func (st *State) setRelationStatus(id int, status relation.Status) error { 122 args := params.RelationStatusArgs{ 123 Args: []params.RelationStatusArg{{ 124 UnitTag: st.unitTag.String(), 125 RelationId: id, 126 Status: params.RelationStatusValue(status), 127 }}, 128 } 129 var results params.ErrorResults 130 if err := st.facade.FacadeCall("SetRelationStatus", args, &results); err != nil { 131 return errors.Trace(err) 132 } 133 return results.OneError() 134 } 135 136 // getOneAction retrieves a single Action from the controller. 137 func (st *State) getOneAction(tag *names.ActionTag) (params.ActionResult, error) { 138 nothing := params.ActionResult{} 139 140 args := params.Entities{ 141 Entities: []params.Entity{ 142 {Tag: tag.String()}, 143 }, 144 } 145 146 var results params.ActionResults 147 err := st.facade.FacadeCall("Actions", args, &results) 148 if err != nil { 149 return nothing, err 150 } 151 152 if len(results.Results) > 1 { 153 return nothing, fmt.Errorf("expected only 1 action query result, got %d", len(results.Results)) 154 } 155 156 // handle server errors 157 result := results.Results[0] 158 if err := result.Error; err != nil { 159 return nothing, err 160 } 161 162 return result, nil 163 } 164 165 // ActionStatus provides the status of a single action. 166 func (st *State) ActionStatus(tag names.ActionTag) (string, error) { 167 args := params.Entities{ 168 Entities: []params.Entity{ 169 {Tag: tag.String()}, 170 }, 171 } 172 173 var results params.StringResults 174 err := st.facade.FacadeCall("ActionStatus", args, &results) 175 if err != nil { 176 return "", err 177 } 178 179 if len(results.Results) > 1 { 180 return "", fmt.Errorf("expected only 1 action query result, got %d", len(results.Results)) 181 } 182 183 // handle server errors 184 result := results.Results[0] 185 if err := result.Error; err != nil { 186 return "", err 187 } 188 189 return result.Result, nil 190 } 191 192 // Unit provides access to methods of a state.Unit through the facade. 193 func (st *State) Unit(tag names.UnitTag) (*Unit, error) { 194 unit := &Unit{ 195 tag: tag, 196 st: st, 197 } 198 err := unit.Refresh() 199 if err != nil { 200 return nil, err 201 } 202 return unit, nil 203 } 204 205 // Application returns an application state by tag. 206 func (st *State) Application(tag names.ApplicationTag) (*Application, error) { 207 life, err := st.life(tag) 208 if err != nil { 209 return nil, err 210 } 211 return &Application{ 212 tag: tag, 213 life: life, 214 st: st, 215 }, nil 216 } 217 218 // ProviderType returns a provider type used by the current juju model. 219 // 220 // TODO(dimitern): We might be able to drop this, once we have machine 221 // addresses implemented fully. See also LP bug 1221798. 222 func (st *State) ProviderType() (string, error) { 223 var result params.StringResult 224 err := st.facade.FacadeCall("ProviderType", nil, &result) 225 if err != nil { 226 return "", err 227 } 228 if err := result.Error; err != nil { 229 return "", err 230 } 231 return result.Result, nil 232 } 233 234 // Charm returns the charm with the given URL. 235 func (st *State) Charm(curl string) (*Charm, error) { 236 if curl == "" { 237 return nil, fmt.Errorf("charm url cannot be empty") 238 } 239 return &Charm{ 240 st: st, 241 curl: curl, 242 }, nil 243 } 244 245 // Relation returns the existing relation with the given tag. 246 func (st *State) Relation(relationTag names.RelationTag) (*Relation, error) { 247 result, err := st.relation(relationTag, st.unitTag) 248 if err != nil { 249 return nil, err 250 } 251 return &Relation{ 252 id: result.Id, 253 tag: relationTag, 254 life: result.Life, 255 suspended: result.Suspended, 256 st: st, 257 otherApp: result.OtherApplication, 258 }, nil 259 } 260 261 // Action returns the Action with the given tag. 262 func (st *State) Action(tag names.ActionTag) (*Action, error) { 263 result, err := st.getOneAction(&tag) 264 if err != nil { 265 return nil, err 266 } 267 a := &Action{ 268 id: tag.Id(), 269 name: result.Action.Name, 270 params: result.Action.Parameters, 271 } 272 if result.Action.Parallel != nil { 273 a.parallel = *result.Action.Parallel 274 } 275 if result.Action.ExecutionGroup != nil { 276 a.executionGroup = *result.Action.ExecutionGroup 277 } 278 return a, nil 279 } 280 281 // ActionBegin marks an action as running. 282 func (st *State) ActionBegin(tag names.ActionTag) error { 283 var outcome params.ErrorResults 284 285 args := params.Entities{ 286 Entities: []params.Entity{ 287 {Tag: tag.String()}, 288 }, 289 } 290 291 err := st.facade.FacadeCall("BeginActions", args, &outcome) 292 if err != nil { 293 return err 294 } 295 if len(outcome.Results) != 1 { 296 return fmt.Errorf("expected 1 result, got %d", len(outcome.Results)) 297 } 298 result := outcome.Results[0] 299 if result.Error != nil { 300 return result.Error 301 } 302 return nil 303 } 304 305 // ActionFinish captures the structured output of an action. 306 func (st *State) ActionFinish(tag names.ActionTag, status string, results map[string]interface{}, message string) error { 307 var outcome params.ErrorResults 308 309 args := params.ActionExecutionResults{ 310 Results: []params.ActionExecutionResult{ 311 { 312 ActionTag: tag.String(), 313 Status: status, 314 Results: results, 315 Message: message, 316 }, 317 }, 318 } 319 320 err := st.facade.FacadeCall("FinishActions", args, &outcome) 321 if err != nil { 322 return err 323 } 324 if len(outcome.Results) != 1 { 325 return fmt.Errorf("expected 1 result, got %d", len(outcome.Results)) 326 } 327 result := outcome.Results[0] 328 if result.Error != nil { 329 return result.Error 330 } 331 return nil 332 } 333 334 // RelationById returns the existing relation with the given id. 335 func (st *State) RelationById(id int) (*Relation, error) { 336 var results params.RelationResults 337 args := params.RelationIds{ 338 RelationIds: []int{id}, 339 } 340 341 err := st.facade.FacadeCall("RelationById", args, &results) 342 if err != nil { 343 return nil, err 344 } 345 if len(results.Results) != 1 { 346 return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results)) 347 } 348 result := results.Results[0] 349 if err := result.Error; err != nil { 350 return nil, err 351 } 352 relationTag := names.NewRelationTag(result.Key) 353 return &Relation{ 354 id: result.Id, 355 tag: relationTag, 356 life: result.Life, 357 suspended: result.Suspended, 358 st: st, 359 otherApp: result.OtherApplication, 360 }, nil 361 } 362 363 // Model returns the model entity. 364 func (st *State) Model() (*model.Model, error) { 365 var result params.ModelResult 366 err := st.facade.FacadeCall("CurrentModel", nil, &result) 367 if err != nil { 368 return nil, err 369 } 370 if err := result.Error; err != nil { 371 return nil, err 372 } 373 modelType := model.ModelType(result.Type) 374 if modelType == "" { 375 modelType = model.IAAS 376 } 377 return &model.Model{ 378 Name: result.Name, 379 UUID: result.UUID, 380 ModelType: modelType, 381 }, nil 382 } 383 384 func processOpenPortRangesByEndpointResults(results params.OpenPortRangesByEndpointResults, tag names.Tag) (map[names.UnitTag]network.GroupedPortRanges, error) { 385 if len(results.Results) != 1 { 386 return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results)) 387 } 388 result := results.Results[0] 389 if result.Error != nil { 390 err := apiservererrors.RestoreError(result.Error) 391 return nil, errors.Annotatef(err, "unable to fetch opened ports for %s", tag) 392 } 393 394 portRangeMap := make(map[names.UnitTag]network.GroupedPortRanges) 395 for unitTagStr, unitPortRanges := range result.UnitPortRanges { 396 unitTag, err := names.ParseUnitTag(unitTagStr) 397 if err != nil { 398 return nil, errors.Trace(err) 399 } 400 portRangeMap[unitTag] = make(network.GroupedPortRanges) 401 for _, group := range unitPortRanges { 402 portRangeMap[unitTag][group.Endpoint] = transform.Slice(group.PortRanges, func(pr params.PortRange) network.PortRange { 403 return pr.NetworkPortRange() 404 }) 405 } 406 } 407 return portRangeMap, nil 408 } 409 410 // OpenedMachinePortRangesByEndpoint returns all port ranges currently open on the given 411 // machine, grouped by unit tag and application endpoint. 412 func (st *State) OpenedMachinePortRangesByEndpoint(machineTag names.MachineTag) (map[names.UnitTag]network.GroupedPortRanges, error) { 413 var results params.OpenPortRangesByEndpointResults 414 args := params.Entities{ 415 Entities: []params.Entity{{Tag: machineTag.String()}}, 416 } 417 err := st.facade.FacadeCall("OpenedMachinePortRangesByEndpoint", args, &results) 418 if err != nil { 419 return nil, err 420 } 421 return processOpenPortRangesByEndpointResults(results, machineTag) 422 } 423 424 // OpenedPortRangesByEndpoint returns all port ranges currently opened grouped by unit tag and application endpoint. 425 func (st *State) OpenedPortRangesByEndpoint() (map[names.UnitTag]network.GroupedPortRanges, error) { 426 if st.BestAPIVersion() < 18 { 427 // OpenedPortRangesByEndpoint() was introduced in UniterAPIV18. 428 return nil, errors.NotImplementedf("OpenedPortRangesByEndpoint() (need V18+)") 429 } 430 var results params.OpenPortRangesByEndpointResults 431 if err := st.facade.FacadeCall("OpenedPortRangesByEndpoint", nil, &results); err != nil { 432 return nil, errors.Trace(err) 433 } 434 return processOpenPortRangesByEndpointResults(results, st.unitTag) 435 } 436 437 // WatchRelationUnits returns a watcher that notifies of changes to the 438 // counterpart units in the relation for the given unit. 439 func (st *State) WatchRelationUnits( 440 relationTag names.RelationTag, 441 unitTag names.UnitTag, 442 ) (watcher.RelationUnitsWatcher, error) { 443 var results params.RelationUnitsWatchResults 444 args := params.RelationUnits{ 445 RelationUnits: []params.RelationUnit{{ 446 Relation: relationTag.String(), 447 Unit: unitTag.String(), 448 }}, 449 } 450 err := st.facade.FacadeCall("WatchRelationUnits", args, &results) 451 if err != nil { 452 return nil, err 453 } 454 if len(results.Results) != 1 { 455 return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results)) 456 } 457 result := results.Results[0] 458 if result.Error != nil { 459 return nil, result.Error 460 } 461 w := apiwatcher.NewRelationUnitsWatcher(st.facade.RawAPICaller(), result) 462 return w, nil 463 } 464 465 // SLALevel returns the SLA level set on the model. 466 func (st *State) SLALevel() (string, error) { 467 var result params.StringResult 468 err := st.facade.FacadeCall("SLALevel", nil, &result) 469 if err != nil { 470 return "", errors.Trace(err) 471 } 472 if err := result.Error; err != nil { 473 return "", errors.Trace(err) 474 } 475 return result.Result, nil 476 } 477 478 // CloudAPIVersion returns the API version of the cloud, if known. 479 func (st *State) CloudAPIVersion() (string, error) { 480 var result params.StringResult 481 err := st.facade.FacadeCall("CloudAPIVersion", nil, &result) 482 if err != nil { 483 return "", errors.Trace(err) 484 } 485 if err := result.Error; err != nil { 486 return "", errors.Trace(err) 487 } 488 return result.Result, nil 489 } 490 491 // GoalState returns a GoalState struct with the charm's 492 // peers and related units information. 493 func (st *State) GoalState() (application.GoalState, error) { 494 var result params.GoalStateResults 495 496 gs := application.GoalState{} 497 498 args := params.Entities{ 499 Entities: []params.Entity{ 500 {Tag: st.unitTag.String()}, 501 }, 502 } 503 504 err := st.facade.FacadeCall("GoalStates", args, &result) 505 if err != nil { 506 return gs, err 507 } 508 if len(result.Results) != 1 { 509 return gs, errors.Errorf("expected 1 result, got %d", len(result.Results)) 510 } 511 if err := result.Results[0].Error; err != nil { 512 return gs, err 513 } 514 gs = goalStateFromParams(result.Results[0].Result) 515 return gs, nil 516 } 517 518 func goalStateFromParams(paramsGoalState *params.GoalState) application.GoalState { 519 goalState := application.GoalState{} 520 521 copyUnits := func(units params.UnitsGoalState) application.UnitsGoalState { 522 copiedUnits := application.UnitsGoalState{} 523 for name, gs := range units { 524 copiedUnits[name] = application.GoalStateStatus{ 525 Status: gs.Status, 526 Since: gs.Since, 527 } 528 } 529 return copiedUnits 530 } 531 532 goalState.Units = copyUnits(paramsGoalState.Units) 533 534 if paramsGoalState.Relations != nil { 535 goalState.Relations = make(map[string]application.UnitsGoalState) 536 for relation, relationUnits := range paramsGoalState.Relations { 537 goalState.Relations[relation] = copyUnits(relationUnits) 538 } 539 } 540 541 return goalState 542 } 543 544 // GetPodSpec gets the pod spec of the specified application. 545 func (st *State) GetPodSpec(appName string) (string, error) { 546 if !names.IsValidApplication(appName) { 547 return "", errors.NotValidf("application name %q", appName) 548 } 549 tag := names.NewApplicationTag(appName) 550 var result params.StringResults 551 args := params.Entities{ 552 Entities: []params.Entity{{ 553 Tag: tag.String(), 554 }}, 555 } 556 if err := st.facade.FacadeCall("GetPodSpec", args, &result); err != nil { 557 return "", errors.Trace(err) 558 } 559 if len(result.Results) != 1 { 560 return "", fmt.Errorf("expected 1 result, got %d", len(result.Results)) 561 } 562 if err := result.Results[0].Error; err != nil { 563 if params.IsCodeNotFound(result.Results[0].Error) { 564 return "", errors.NotFoundf("podspec for application %s", appName) 565 } 566 return "", err 567 } 568 return result.Results[0].Result, nil 569 } 570 571 // GetRawK8sSpec gets the raw k8s spec of the specified application. 572 func (st *State) GetRawK8sSpec(appName string) (string, error) { 573 if !names.IsValidApplication(appName) { 574 return "", errors.NotValidf("application name %q", appName) 575 } 576 tag := names.NewApplicationTag(appName) 577 var result params.StringResults 578 args := params.Entities{ 579 Entities: []params.Entity{{ 580 Tag: tag.String(), 581 }}, 582 } 583 if err := st.facade.FacadeCall("GetRawK8sSpec", args, &result); err != nil { 584 return "", errors.Trace(err) 585 } 586 if len(result.Results) != 1 { 587 return "", fmt.Errorf("expected 1 result, got %d", len(result.Results)) 588 } 589 if err := result.Results[0].Error; err != nil { 590 if params.IsCodeNotFound(result.Results[0].Error) { 591 return "", errors.NotFoundf("raw k8s spec for application %s", appName) 592 } 593 return "", err 594 } 595 return result.Results[0].Result, nil 596 } 597 598 // CloudSpec returns the cloud spec for the model that calling unit or 599 // application resides in. 600 // If the application has not been authorised to access its cloud spec, 601 // then an authorisation error will be returned. 602 func (st *State) CloudSpec() (*params.CloudSpec, error) { 603 var result params.CloudSpecResult 604 605 err := st.facade.FacadeCall("CloudSpec", nil, &result) 606 if err != nil { 607 return nil, err 608 } 609 if err := result.Error; err != nil { 610 return nil, err 611 } 612 return result.Result, nil 613 } 614 615 // UnitWorkloadVersion returns the version of the workload reported by 616 // the specified unit. 617 func (st *State) UnitWorkloadVersion(tag names.UnitTag) (string, error) { 618 var results params.StringResults 619 args := params.Entities{ 620 Entities: []params.Entity{{Tag: tag.String()}}, 621 } 622 err := st.facade.FacadeCall("WorkloadVersion", args, &results) 623 if err != nil { 624 return "", err 625 } 626 if len(results.Results) != 1 { 627 return "", fmt.Errorf("expected 1 result, got %d", len(results.Results)) 628 } 629 result := results.Results[0] 630 if result.Error != nil { 631 return "", result.Error 632 } 633 return result.Result, nil 634 } 635 636 // SetUnitWorkloadVersion sets the specified unit's workload version to 637 // the provided value. 638 func (st *State) SetUnitWorkloadVersion(tag names.UnitTag, version string) error { 639 var result params.ErrorResults 640 args := params.EntityWorkloadVersions{ 641 Entities: []params.EntityWorkloadVersion{ 642 {Tag: tag.String(), WorkloadVersion: version}, 643 }, 644 } 645 err := st.facade.FacadeCall("SetWorkloadVersion", args, &result) 646 if err != nil { 647 return err 648 } 649 return result.OneError() 650 }