github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/api/uniter/unit.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/errors" 10 "github.com/juju/names" 11 "gopkg.in/juju/charm.v5" 12 13 "github.com/juju/juju/api/common" 14 "github.com/juju/juju/api/watcher" 15 "github.com/juju/juju/apiserver/params" 16 ) 17 18 // Unit represents a juju unit as seen by a uniter worker. 19 type Unit struct { 20 st *State 21 tag names.UnitTag 22 life params.Life 23 } 24 25 // Tag returns the unit's tag. 26 func (u *Unit) Tag() names.UnitTag { 27 return u.tag 28 } 29 30 // Name returns the name of the unit. 31 func (u *Unit) Name() string { 32 return u.tag.Id() 33 } 34 35 // String returns the unit as a string. 36 func (u *Unit) String() string { 37 return u.Name() 38 } 39 40 // Life returns the unit's lifecycle value. 41 func (u *Unit) Life() params.Life { 42 return u.life 43 } 44 45 // Refresh updates the cached local copy of the unit's data. 46 func (u *Unit) Refresh() error { 47 life, err := u.st.life(u.tag) 48 if err != nil { 49 return err 50 } 51 u.life = life 52 return nil 53 } 54 55 // SetUnitStatus sets the status of the unit. 56 func (u *Unit) SetUnitStatus(status params.Status, info string, data map[string]interface{}) error { 57 if u.st.facade.BestAPIVersion() < 2 { 58 return errors.NotImplementedf("SetUnitStatus") 59 } 60 var result params.ErrorResults 61 args := params.SetStatus{ 62 Entities: []params.EntityStatusArgs{ 63 {Tag: u.tag.String(), Status: status, Info: info, Data: data}, 64 }, 65 } 66 err := u.st.facade.FacadeCall("SetUnitStatus", args, &result) 67 if err != nil { 68 return errors.Trace(err) 69 } 70 return result.OneError() 71 } 72 73 // UnitStatus gets the status details of the unit. 74 func (u *Unit) UnitStatus() (params.StatusResult, error) { 75 var results params.StatusResults 76 args := params.Entities{ 77 Entities: []params.Entity{ 78 {Tag: u.tag.String()}, 79 }, 80 } 81 err := u.st.facade.FacadeCall("UnitStatus", args, &results) 82 if err != nil { 83 if params.IsCodeNotImplemented(err) { 84 return params.StatusResult{}, errors.NotImplementedf("UnitStatus") 85 } 86 return params.StatusResult{}, errors.Trace(err) 87 } 88 if len(results.Results) != 1 { 89 panic(errors.Errorf("expected 1 result, got %d", len(results.Results))) 90 } 91 result := results.Results[0] 92 if result.Error != nil { 93 return params.StatusResult{}, result.Error 94 } 95 return result, nil 96 } 97 98 // SetAgentStatus sets the status of the unit agent. 99 func (u *Unit) SetAgentStatus(status params.Status, info string, data map[string]interface{}) error { 100 var result params.ErrorResults 101 args := params.SetStatus{ 102 Entities: []params.EntityStatusArgs{ 103 {Tag: u.tag.String(), Status: status, Info: info, Data: data}, 104 }, 105 } 106 setStatusFacadeCall := "SetAgentStatus" 107 if u.st.facade.BestAPIVersion() < 2 { 108 setStatusFacadeCall = "SetStatus" 109 } 110 err := u.st.facade.FacadeCall(setStatusFacadeCall, args, &result) 111 if err != nil { 112 return err 113 } 114 return result.OneError() 115 } 116 117 // AddMetrics adds the metrics for the unit. 118 func (u *Unit) AddMetrics(metrics []params.Metric) error { 119 var result params.ErrorResults 120 args := params.MetricsParams{ 121 Metrics: []params.MetricsParam{{ 122 Tag: u.tag.String(), 123 Metrics: metrics, 124 }}, 125 } 126 err := u.st.facade.FacadeCall("AddMetrics", args, &result) 127 if err != nil { 128 return errors.Annotate(err, "unable to add metric") 129 } 130 return result.OneError() 131 } 132 133 // AddMetricsBatches makes an api call to the uniter requesting it to store metrics batches in state. 134 func (u *Unit) AddMetricBatches(batches []params.MetricBatch) (map[string]error, error) { 135 p := params.MetricBatchParams{ 136 Batches: make([]params.MetricBatchParam, len(batches)), 137 } 138 139 batchResults := make(map[string]error, len(batches)) 140 141 for i, batch := range batches { 142 p.Batches[i].Tag = u.tag.String() 143 p.Batches[i].Batch = batch 144 145 batchResults[batch.UUID] = nil 146 } 147 results := new(params.ErrorResults) 148 err := u.st.facade.FacadeCall("AddMetricBatches", p, results) 149 if params.IsCodeNotImplemented(err) { 150 for _, batch := range batches { 151 err = u.AddMetrics(batch.Metrics) 152 if err != nil { 153 batchResults[batch.UUID] = errors.Annotate(err, "failed to send metric batch") 154 } 155 } 156 return batchResults, nil 157 } else if err != nil { 158 return nil, errors.Annotate(err, "failed to send metric batches") 159 } 160 for i, result := range results.Results { 161 batchResults[batches[i].UUID] = result.Error 162 } 163 return batchResults, nil 164 } 165 166 // EnsureDead sets the unit lifecycle to Dead if it is Alive or 167 // Dying. It does nothing otherwise. 168 func (u *Unit) EnsureDead() error { 169 var result params.ErrorResults 170 args := params.Entities{ 171 Entities: []params.Entity{{Tag: u.tag.String()}}, 172 } 173 err := u.st.facade.FacadeCall("EnsureDead", args, &result) 174 if err != nil { 175 return err 176 } 177 return result.OneError() 178 } 179 180 // Watch returns a watcher for observing changes to the unit. 181 func (u *Unit) Watch() (watcher.NotifyWatcher, error) { 182 return common.Watch(u.st.facade, u.tag) 183 } 184 185 // Service returns the service. 186 func (u *Unit) Service() (*Service, error) { 187 service := &Service{ 188 st: u.st, 189 tag: u.ServiceTag(), 190 } 191 // Call Refresh() immediately to get the up-to-date 192 // life and other needed locally cached fields. 193 err := service.Refresh() 194 if err != nil { 195 return nil, err 196 } 197 return service, nil 198 } 199 200 // ConfigSettings returns the complete set of service charm config settings 201 // available to the unit. Unset values will be replaced with the default 202 // value for the associated option, and may thus be nil when no default is 203 // specified. 204 func (u *Unit) ConfigSettings() (charm.Settings, error) { 205 var results params.ConfigSettingsResults 206 args := params.Entities{ 207 Entities: []params.Entity{{Tag: u.tag.String()}}, 208 } 209 err := u.st.facade.FacadeCall("ConfigSettings", args, &results) 210 if err != nil { 211 return nil, err 212 } 213 if len(results.Results) != 1 { 214 return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results)) 215 } 216 result := results.Results[0] 217 if result.Error != nil { 218 return nil, result.Error 219 } 220 return charm.Settings(result.Settings), nil 221 } 222 223 // ServiceName returns the service name. 224 func (u *Unit) ServiceName() string { 225 service, err := names.UnitService(u.Name()) 226 if err != nil { 227 panic(err) 228 } 229 return service 230 } 231 232 // ServiceTag returns the service tag. 233 func (u *Unit) ServiceTag() names.ServiceTag { 234 return names.NewServiceTag(u.ServiceName()) 235 } 236 237 // Destroy, when called on a Alive unit, advances its lifecycle as far as 238 // possible; it otherwise has no effect. In most situations, the unit's 239 // life is just set to Dying; but if a principal unit that is not assigned 240 // to a provisioned machine is Destroyed, it will be removed from state 241 // directly. 242 func (u *Unit) Destroy() error { 243 var result params.ErrorResults 244 args := params.Entities{ 245 Entities: []params.Entity{{Tag: u.tag.String()}}, 246 } 247 err := u.st.facade.FacadeCall("Destroy", args, &result) 248 if err != nil { 249 return err 250 } 251 return result.OneError() 252 } 253 254 // DestroyAllSubordinates destroys all subordinates of the unit. 255 func (u *Unit) DestroyAllSubordinates() error { 256 var result params.ErrorResults 257 args := params.Entities{ 258 Entities: []params.Entity{{Tag: u.tag.String()}}, 259 } 260 err := u.st.facade.FacadeCall("DestroyAllSubordinates", args, &result) 261 if err != nil { 262 return err 263 } 264 return result.OneError() 265 } 266 267 // Resolved returns the resolved mode for the unit. 268 // 269 // NOTE: This differs from state.Unit.Resolved() by returning an 270 // error as well, because it needs to make an API call 271 func (u *Unit) Resolved() (params.ResolvedMode, error) { 272 var results params.ResolvedModeResults 273 args := params.Entities{ 274 Entities: []params.Entity{{Tag: u.tag.String()}}, 275 } 276 err := u.st.facade.FacadeCall("Resolved", args, &results) 277 if err != nil { 278 return "", err 279 } 280 if len(results.Results) != 1 { 281 return "", fmt.Errorf("expected 1 result, got %d", len(results.Results)) 282 } 283 result := results.Results[0] 284 if result.Error != nil { 285 return "", result.Error 286 } 287 return result.Mode, nil 288 } 289 290 // AssignedMachine returns the unit's assigned machine tag or an error 291 // satisfying params.IsCodeNotAssigned when the unit has no assigned 292 // machine.. 293 func (u *Unit) AssignedMachine() (names.MachineTag, error) { 294 if u.st.BestAPIVersion() < 1 { 295 return names.MachineTag{}, errors.NotImplementedf("unit.AssignedMachine() (need V1+)") 296 } 297 var invalidTag names.MachineTag 298 var results params.StringResults 299 args := params.Entities{ 300 Entities: []params.Entity{{Tag: u.tag.String()}}, 301 } 302 err := u.st.facade.FacadeCall("AssignedMachine", args, &results) 303 if err != nil { 304 return invalidTag, err 305 } 306 if len(results.Results) != 1 { 307 return invalidTag, fmt.Errorf("expected 1 result, got %d", len(results.Results)) 308 } 309 result := results.Results[0] 310 if result.Error != nil { 311 return invalidTag, result.Error 312 } 313 return names.ParseMachineTag(result.Result) 314 } 315 316 // IsPrincipal returns whether the unit is deployed in its own container, 317 // and can therefore have subordinate services deployed alongside it. 318 // 319 // NOTE: This differs from state.Unit.IsPrincipal() by returning an 320 // error as well, because it needs to make an API call. 321 func (u *Unit) IsPrincipal() (bool, error) { 322 var results params.StringBoolResults 323 args := params.Entities{ 324 Entities: []params.Entity{{Tag: u.tag.String()}}, 325 } 326 err := u.st.facade.FacadeCall("GetPrincipal", args, &results) 327 if err != nil { 328 return false, err 329 } 330 if len(results.Results) != 1 { 331 return false, fmt.Errorf("expected 1 result, got %d", len(results.Results)) 332 } 333 result := results.Results[0] 334 if result.Error != nil { 335 return false, result.Error 336 } 337 // GetPrincipal returns false when the unit is subordinate. 338 return !result.Ok, nil 339 } 340 341 // HasSubordinates returns the tags of any subordinate units. 342 func (u *Unit) HasSubordinates() (bool, error) { 343 var results params.BoolResults 344 args := params.Entities{ 345 Entities: []params.Entity{{Tag: u.tag.String()}}, 346 } 347 err := u.st.facade.FacadeCall("HasSubordinates", args, &results) 348 if err != nil { 349 return false, err 350 } 351 if len(results.Results) != 1 { 352 return false, fmt.Errorf("expected 1 result, got %d", len(results.Results)) 353 } 354 result := results.Results[0] 355 if result.Error != nil { 356 return false, result.Error 357 } 358 return result.Result, nil 359 } 360 361 // PublicAddress returns the public address of the unit and whether it 362 // is valid. 363 // 364 // NOTE: This differs from state.Unit.PublicAddres() by returning 365 // an error instead of a bool, because it needs to make an API call. 366 // 367 // TODO(dimitern): We might be able to drop this, once we have machine 368 // addresses implemented fully. See also LP bug 1221798. 369 func (u *Unit) PublicAddress() (string, error) { 370 var results params.StringResults 371 args := params.Entities{ 372 Entities: []params.Entity{{Tag: u.tag.String()}}, 373 } 374 err := u.st.facade.FacadeCall("PublicAddress", args, &results) 375 if err != nil { 376 return "", err 377 } 378 if len(results.Results) != 1 { 379 return "", fmt.Errorf("expected 1 result, got %d", len(results.Results)) 380 } 381 result := results.Results[0] 382 if result.Error != nil { 383 return "", result.Error 384 } 385 return result.Result, nil 386 } 387 388 // PrivateAddress returns the private address of the unit and whether 389 // it is valid. 390 // 391 // NOTE: This differs from state.Unit.PrivateAddress() by returning 392 // an error instead of a bool, because it needs to make an API call. 393 // 394 // TODO(dimitern): We might be able to drop this, once we have machine 395 // addresses implemented fully. See also LP bug 1221798. 396 func (u *Unit) PrivateAddress() (string, error) { 397 var results params.StringResults 398 args := params.Entities{ 399 Entities: []params.Entity{{Tag: u.tag.String()}}, 400 } 401 err := u.st.facade.FacadeCall("PrivateAddress", args, &results) 402 if err != nil { 403 return "", err 404 } 405 if len(results.Results) != 1 { 406 return "", fmt.Errorf("expected 1 result, got %d", len(results.Results)) 407 } 408 result := results.Results[0] 409 if result.Error != nil { 410 return "", result.Error 411 } 412 return result.Result, nil 413 } 414 415 // AvailabilityZone returns the availability zone of the unit. 416 func (u *Unit) AvailabilityZone() (string, error) { 417 var results params.StringResults 418 args := params.Entities{ 419 Entities: []params.Entity{{Tag: u.tag.String()}}, 420 } 421 if err := u.st.facade.FacadeCall("AvailabilityZone", args, &results); err != nil { 422 return "", errors.Trace(err) 423 } 424 if len(results.Results) != 1 { 425 return "", errors.Errorf("expected 1 result, got %d", len(results.Results)) 426 } 427 result := results.Results[0] 428 if result.Error != nil { 429 return "", errors.Trace(result.Error) 430 } 431 return result.Result, nil 432 } 433 434 // OpenPorts sets the policy of the port range with protocol to be 435 // opened. 436 func (u *Unit) OpenPorts(protocol string, fromPort, toPort int) error { 437 var result params.ErrorResults 438 args := params.EntitiesPortRanges{ 439 Entities: []params.EntityPortRange{{ 440 Tag: u.tag.String(), 441 Protocol: protocol, 442 FromPort: fromPort, 443 ToPort: toPort, 444 }}, 445 } 446 err := u.st.facade.FacadeCall("OpenPorts", args, &result) 447 if err != nil { 448 return err 449 } 450 return result.OneError() 451 } 452 453 // ClosePorts sets the policy of the port range with protocol to be 454 // closed. 455 func (u *Unit) ClosePorts(protocol string, fromPort, toPort int) error { 456 var result params.ErrorResults 457 args := params.EntitiesPortRanges{ 458 Entities: []params.EntityPortRange{{ 459 Tag: u.tag.String(), 460 Protocol: protocol, 461 FromPort: fromPort, 462 ToPort: toPort, 463 }}, 464 } 465 err := u.st.facade.FacadeCall("ClosePorts", args, &result) 466 if err != nil { 467 return err 468 } 469 return result.OneError() 470 } 471 472 // OpenPort sets the policy of the port with protocol and number to be 473 // opened. 474 // 475 // TODO(dimitern): This is deprecated and is kept for 476 // backwards-compatibility. Use OpenPorts instead. 477 func (u *Unit) OpenPort(protocol string, number int) error { 478 var result params.ErrorResults 479 args := params.EntitiesPorts{ 480 Entities: []params.EntityPort{ 481 {Tag: u.tag.String(), Protocol: protocol, Port: number}, 482 }, 483 } 484 err := u.st.facade.FacadeCall("OpenPort", args, &result) 485 if err != nil { 486 return err 487 } 488 return result.OneError() 489 } 490 491 // ClosePort sets the policy of the port with protocol and number to 492 // be closed. 493 // 494 // TODO(dimitern): This is deprecated and is kept for 495 // backwards-compatibility. Use ClosePorts instead. 496 func (u *Unit) ClosePort(protocol string, number int) error { 497 var result params.ErrorResults 498 args := params.EntitiesPorts{ 499 Entities: []params.EntityPort{ 500 {Tag: u.tag.String(), Protocol: protocol, Port: number}, 501 }, 502 } 503 err := u.st.facade.FacadeCall("ClosePort", args, &result) 504 if err != nil { 505 return err 506 } 507 return result.OneError() 508 } 509 510 var ErrNoCharmURLSet = errors.New("unit has no charm url set") 511 512 // CharmURL returns the charm URL this unit is currently using. 513 // 514 // NOTE: This differs from state.Unit.CharmURL() by returning 515 // an error instead of a bool, because it needs to make an API call. 516 func (u *Unit) CharmURL() (*charm.URL, error) { 517 var results params.StringBoolResults 518 args := params.Entities{ 519 Entities: []params.Entity{{Tag: u.tag.String()}}, 520 } 521 err := u.st.facade.FacadeCall("CharmURL", args, &results) 522 if err != nil { 523 return nil, err 524 } 525 if len(results.Results) != 1 { 526 return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results)) 527 } 528 result := results.Results[0] 529 if result.Error != nil { 530 return nil, result.Error 531 } 532 if result.Result != "" { 533 curl, err := charm.ParseURL(result.Result) 534 if err != nil { 535 return nil, err 536 } 537 return curl, nil 538 } 539 return nil, ErrNoCharmURLSet 540 } 541 542 // SetCharmURL marks the unit as currently using the supplied charm URL. 543 // An error will be returned if the unit is dead, or the charm URL not known. 544 func (u *Unit) SetCharmURL(curl *charm.URL) error { 545 if curl == nil { 546 return fmt.Errorf("charm URL cannot be nil") 547 } 548 var result params.ErrorResults 549 args := params.EntitiesCharmURL{ 550 Entities: []params.EntityCharmURL{ 551 {Tag: u.tag.String(), CharmURL: curl.String()}, 552 }, 553 } 554 err := u.st.facade.FacadeCall("SetCharmURL", args, &result) 555 if err != nil { 556 return err 557 } 558 return result.OneError() 559 } 560 561 // ClearResolved removes any resolved setting on the unit. 562 func (u *Unit) ClearResolved() error { 563 var result params.ErrorResults 564 args := params.Entities{ 565 Entities: []params.Entity{{Tag: u.tag.String()}}, 566 } 567 err := u.st.facade.FacadeCall("ClearResolved", args, &result) 568 if err != nil { 569 return err 570 } 571 return result.OneError() 572 } 573 574 // WatchConfigSettings returns a watcher for observing changes to the 575 // unit's service configuration settings. The unit must have a charm URL 576 // set before this method is called, and the returned watcher will be 577 // valid only while the unit's charm URL is not changed. 578 func (u *Unit) WatchConfigSettings() (watcher.NotifyWatcher, error) { 579 var results params.NotifyWatchResults 580 args := params.Entities{ 581 Entities: []params.Entity{{Tag: u.tag.String()}}, 582 } 583 err := u.st.facade.FacadeCall("WatchConfigSettings", args, &results) 584 if err != nil { 585 return nil, err 586 } 587 if len(results.Results) != 1 { 588 return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results)) 589 } 590 result := results.Results[0] 591 if result.Error != nil { 592 return nil, result.Error 593 } 594 w := watcher.NewNotifyWatcher(u.st.facade.RawAPICaller(), result) 595 return w, nil 596 } 597 598 // WatchAddresses returns a watcher for observing changes to the 599 // unit's addresses. The unit must be assigned to a machine before 600 // this method is called, and the returned watcher will be valid only 601 // while the unit's assigned machine is not changed. 602 func (u *Unit) WatchAddresses() (watcher.NotifyWatcher, error) { 603 var results params.NotifyWatchResults 604 args := params.Entities{ 605 Entities: []params.Entity{{Tag: u.tag.String()}}, 606 } 607 err := u.st.facade.FacadeCall("WatchUnitAddresses", args, &results) 608 if err != nil { 609 return nil, err 610 } 611 if len(results.Results) != 1 { 612 return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results)) 613 } 614 result := results.Results[0] 615 if result.Error != nil { 616 return nil, result.Error 617 } 618 w := watcher.NewNotifyWatcher(u.st.facade.RawAPICaller(), result) 619 return w, nil 620 } 621 622 // WatchActionNotifications returns a StringsWatcher for observing the 623 // ids of Actions added to the Unit. The initial event will contain the 624 // ids of any Actions pending at the time the Watcher is made. 625 func (u *Unit) WatchActionNotifications() (watcher.StringsWatcher, error) { 626 var results params.StringsWatchResults 627 args := params.Entities{ 628 Entities: []params.Entity{{Tag: u.tag.String()}}, 629 } 630 err := u.st.facade.FacadeCall("WatchActionNotifications", args, &results) 631 if err != nil { 632 return nil, err 633 } 634 if len(results.Results) != 1 { 635 return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results)) 636 } 637 result := results.Results[0] 638 if result.Error != nil { 639 return nil, result.Error 640 } 641 w := watcher.NewStringsWatcher(u.st.facade.RawAPICaller(), result) 642 return w, nil 643 } 644 645 // RequestReboot sets the reboot flag for its machine agent 646 func (u *Unit) RequestReboot() error { 647 machineId, err := u.AssignedMachine() 648 if err != nil { 649 return err 650 } 651 var result params.ErrorResults 652 args := params.Entities{ 653 Entities: []params.Entity{{Tag: machineId.String()}}, 654 } 655 err = u.st.facade.FacadeCall("RequestReboot", args, &result) 656 if err != nil { 657 return err 658 } 659 return result.OneError() 660 } 661 662 // JoinedRelations returns the tags of the relations the unit has joined. 663 func (u *Unit) JoinedRelations() ([]names.RelationTag, error) { 664 var results params.StringsResults 665 args := params.Entities{ 666 Entities: []params.Entity{{Tag: u.tag.String()}}, 667 } 668 err := u.st.facade.FacadeCall("JoinedRelations", args, &results) 669 if err != nil { 670 return nil, err 671 } 672 if len(results.Results) != 1 { 673 return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results)) 674 } 675 result := results.Results[0] 676 if result.Error != nil { 677 return nil, result.Error 678 } 679 var relTags []names.RelationTag 680 for _, rel := range result.Result { 681 tag, err := names.ParseRelationTag(rel) 682 if err != nil { 683 return nil, err 684 } 685 relTags = append(relTags, tag) 686 } 687 return relTags, nil 688 } 689 690 // MeterStatus returns the meter status of the unit. 691 func (u *Unit) MeterStatus() (statusCode, statusInfo string, rErr error) { 692 var results params.MeterStatusResults 693 args := params.Entities{ 694 Entities: []params.Entity{{Tag: u.tag.String()}}, 695 } 696 err := u.st.facade.FacadeCall("GetMeterStatus", args, &results) 697 if err != nil { 698 return "", "", errors.Trace(err) 699 } 700 if len(results.Results) != 1 { 701 return "", "", errors.Errorf("expected 1 result, got %d", len(results.Results)) 702 } 703 result := results.Results[0] 704 if result.Error != nil { 705 return "", "", errors.Trace(result.Error) 706 } 707 return result.Code, result.Info, nil 708 } 709 710 // WatchMeterStatus returns a watcher for observing changes to the 711 // unit's meter status. 712 func (u *Unit) WatchMeterStatus() (watcher.NotifyWatcher, error) { 713 var results params.NotifyWatchResults 714 args := params.Entities{ 715 Entities: []params.Entity{{Tag: u.tag.String()}}, 716 } 717 err := u.st.facade.FacadeCall("WatchMeterStatus", args, &results) 718 if err != nil { 719 return nil, err 720 } 721 if len(results.Results) != 1 { 722 return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results)) 723 } 724 result := results.Results[0] 725 if result.Error != nil { 726 return nil, result.Error 727 } 728 w := watcher.NewNotifyWatcher(u.st.facade.RawAPICaller(), result) 729 return w, nil 730 } 731 732 // WatchStorage returns a watcher for observing changes to the 733 // unit's storage attachments. 734 func (u *Unit) WatchStorage() (watcher.StringsWatcher, error) { 735 return u.st.WatchUnitStorageAttachments(u.tag) 736 } 737 738 // AddStorage adds desired storage instances to a unit. 739 func (u *Unit) AddStorage(constraints map[string][]params.StorageConstraints) error { 740 if u.st.facade.BestAPIVersion() < 2 { 741 return errors.NotImplementedf("AddStorage() (need V2+)") 742 } 743 744 all := make([]params.StorageAddParams, 0, len(constraints)) 745 for storage, cons := range constraints { 746 for _, one := range cons { 747 all = append(all, params.StorageAddParams{u.Tag().String(), storage, one}) 748 } 749 } 750 751 args := params.StoragesAddParams{Storages: all} 752 var results params.ErrorResults 753 err := u.st.facade.FacadeCall("AddUnitStorage", args, &results) 754 if err != nil { 755 return err 756 } 757 758 return results.Combine() 759 }