github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/state/apiserver/client/client.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package client 5 6 import ( 7 "fmt" 8 "net/url" 9 "os" 10 "strings" 11 12 "github.com/juju/errors" 13 "github.com/juju/loggo" 14 "github.com/juju/names" 15 "github.com/juju/utils" 16 17 "github.com/juju/juju/charm" 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/environs/config" 20 "github.com/juju/juju/environs/manual" 21 envtools "github.com/juju/juju/environs/tools" 22 "github.com/juju/juju/instance" 23 "github.com/juju/juju/juju" 24 "github.com/juju/juju/state" 25 "github.com/juju/juju/state/api" 26 "github.com/juju/juju/state/api/params" 27 "github.com/juju/juju/state/apiserver/common" 28 coretools "github.com/juju/juju/tools" 29 "github.com/juju/juju/version" 30 ) 31 32 var logger = loggo.GetLogger("juju.state.apiserver.client") 33 34 type API struct { 35 state *state.State 36 auth common.Authorizer 37 resources *common.Resources 38 client *Client 39 // statusSetter provides common methods for updating an entity's provisioning status. 40 statusSetter *common.StatusSetter 41 } 42 43 // Client serves client-specific API methods. 44 type Client struct { 45 api *API 46 } 47 48 // NewAPI creates a new instance of the Client API. 49 func NewAPI(st *state.State, resources *common.Resources, authorizer common.Authorizer) *API { 50 r := &API{ 51 state: st, 52 auth: authorizer, 53 resources: resources, 54 statusSetter: common.NewStatusSetter(st, common.AuthAlways(true)), 55 } 56 r.client = &Client{ 57 api: r, 58 } 59 return r 60 } 61 62 // Client returns an object that provides access 63 // to methods accessible to non-agent clients. 64 func (r *API) Client(id string) (*Client, error) { 65 if !r.auth.AuthClient() { 66 return nil, common.ErrPerm 67 } 68 if id != "" { 69 // Safeguard id for possible future use. 70 return nil, common.ErrBadId 71 } 72 return r.client, nil 73 } 74 75 func (c *Client) WatchAll() (params.AllWatcherId, error) { 76 w := c.api.state.Watch() 77 return params.AllWatcherId{ 78 AllWatcherId: c.api.resources.Register(w), 79 }, nil 80 } 81 82 // ServiceSet implements the server side of Client.ServiceSet. Values set to an 83 // empty string will be unset. 84 // 85 // (Deprecated) Use NewServiceSetForClientAPI instead, to preserve values set to 86 // an empty string, and use ServiceUnset to unset values. 87 func (c *Client) ServiceSet(p params.ServiceSet) error { 88 svc, err := c.api.state.Service(p.ServiceName) 89 if err != nil { 90 return err 91 } 92 return serviceSetSettingsStrings(svc, p.Options) 93 } 94 95 // NewServiceSetForClientAPI implements the server side of 96 // Client.NewServiceSetForClientAPI. This is exactly like ServiceSet except that 97 // it does not unset values that are set to an empty string. ServiceUnset 98 // should be used for that. 99 // 100 // TODO(Nate): rename this to ServiceSet (and remove the deprecated ServiceSet) 101 // when the GUI handles the new behavior. 102 func (c *Client) NewServiceSetForClientAPI(p params.ServiceSet) error { 103 svc, err := c.api.state.Service(p.ServiceName) 104 if err != nil { 105 return err 106 } 107 return newServiceSetSettingsStringsForClientAPI(svc, p.Options) 108 } 109 110 // ServiceUnset implements the server side of Client.ServiceUnset. 111 func (c *Client) ServiceUnset(p params.ServiceUnset) error { 112 svc, err := c.api.state.Service(p.ServiceName) 113 if err != nil { 114 return err 115 } 116 settings := make(charm.Settings) 117 for _, option := range p.Options { 118 settings[option] = nil 119 } 120 return svc.UpdateConfigSettings(settings) 121 } 122 123 // ServiceSetYAML implements the server side of Client.ServerSetYAML. 124 func (c *Client) ServiceSetYAML(p params.ServiceSetYAML) error { 125 svc, err := c.api.state.Service(p.ServiceName) 126 if err != nil { 127 return err 128 } 129 return serviceSetSettingsYAML(svc, p.Config) 130 } 131 132 // ServiceCharmRelations implements the server side of Client.ServiceCharmRelations. 133 func (c *Client) ServiceCharmRelations(p params.ServiceCharmRelations) (params.ServiceCharmRelationsResults, error) { 134 var results params.ServiceCharmRelationsResults 135 service, err := c.api.state.Service(p.ServiceName) 136 if err != nil { 137 return results, err 138 } 139 endpoints, err := service.Endpoints() 140 if err != nil { 141 return results, err 142 } 143 results.CharmRelations = make([]string, len(endpoints)) 144 for i, endpoint := range endpoints { 145 results.CharmRelations[i] = endpoint.Relation.Name 146 } 147 return results, nil 148 } 149 150 // Resolved implements the server side of Client.Resolved. 151 func (c *Client) Resolved(p params.Resolved) error { 152 unit, err := c.api.state.Unit(p.UnitName) 153 if err != nil { 154 return err 155 } 156 return unit.Resolve(p.Retry) 157 } 158 159 // PublicAddress implements the server side of Client.PublicAddress. 160 func (c *Client) PublicAddress(p params.PublicAddress) (results params.PublicAddressResults, err error) { 161 switch { 162 case names.IsMachine(p.Target): 163 machine, err := c.api.state.Machine(p.Target) 164 if err != nil { 165 return results, err 166 } 167 addr := instance.SelectPublicAddress(machine.Addresses()) 168 if addr == "" { 169 return results, fmt.Errorf("machine %q has no public address", machine) 170 } 171 return params.PublicAddressResults{PublicAddress: addr}, nil 172 173 case names.IsUnit(p.Target): 174 unit, err := c.api.state.Unit(p.Target) 175 if err != nil { 176 return results, err 177 } 178 addr, ok := unit.PublicAddress() 179 if !ok { 180 return results, fmt.Errorf("unit %q has no public address", unit) 181 } 182 return params.PublicAddressResults{PublicAddress: addr}, nil 183 } 184 return results, fmt.Errorf("unknown unit or machine %q", p.Target) 185 } 186 187 // PrivateAddress implements the server side of Client.PrivateAddress. 188 func (c *Client) PrivateAddress(p params.PrivateAddress) (results params.PrivateAddressResults, err error) { 189 switch { 190 case names.IsMachine(p.Target): 191 machine, err := c.api.state.Machine(p.Target) 192 if err != nil { 193 return results, err 194 } 195 addr := instance.SelectInternalAddress(machine.Addresses(), false) 196 if addr == "" { 197 return results, fmt.Errorf("machine %q has no internal address", machine) 198 } 199 return params.PrivateAddressResults{PrivateAddress: addr}, nil 200 201 case names.IsUnit(p.Target): 202 unit, err := c.api.state.Unit(p.Target) 203 if err != nil { 204 return results, err 205 } 206 addr, ok := unit.PrivateAddress() 207 if !ok { 208 return results, fmt.Errorf("unit %q has no internal address", unit) 209 } 210 return params.PrivateAddressResults{PrivateAddress: addr}, nil 211 } 212 return results, fmt.Errorf("unknown unit or machine %q", p.Target) 213 } 214 215 // ServiceExpose changes the juju-managed firewall to expose any ports that 216 // were also explicitly marked by units as open. 217 func (c *Client) ServiceExpose(args params.ServiceExpose) error { 218 svc, err := c.api.state.Service(args.ServiceName) 219 if err != nil { 220 return err 221 } 222 return svc.SetExposed() 223 } 224 225 // ServiceUnexpose changes the juju-managed firewall to unexpose any ports that 226 // were also explicitly marked by units as open. 227 func (c *Client) ServiceUnexpose(args params.ServiceUnexpose) error { 228 svc, err := c.api.state.Service(args.ServiceName) 229 if err != nil { 230 return err 231 } 232 return svc.ClearExposed() 233 } 234 235 var CharmStore charm.Repository = charm.Store 236 237 func networkTagsToNames(tags []string) ([]string, error) { 238 netNames := make([]string, len(tags)) 239 for i, tag := range tags { 240 _, name, err := names.ParseTag(tag, names.NetworkTagKind) 241 if err != nil { 242 return nil, err 243 } 244 netNames[i] = name 245 } 246 return netNames, nil 247 } 248 249 // ServiceDeploy fetches the charm from the charm store and deploys it. 250 // AddCharm or AddLocalCharm should be called to add the charm 251 // before calling ServiceDeploy, although for backward compatibility 252 // this is not necessary until 1.16 support is removed. 253 func (c *Client) ServiceDeploy(args params.ServiceDeploy) error { 254 curl, err := charm.ParseURL(args.CharmUrl) 255 if err != nil { 256 return err 257 } 258 if curl.Revision < 0 { 259 return fmt.Errorf("charm url must include revision") 260 } 261 262 // Try to find the charm URL in state first. 263 ch, err := c.api.state.Charm(curl) 264 if errors.IsNotFound(err) { 265 // Remove this whole if block when 1.16 compatibility is dropped. 266 if curl.Schema != "cs" { 267 return fmt.Errorf(`charm url has unsupported schema %q`, curl.Schema) 268 } 269 err = c.AddCharm(params.CharmURL{args.CharmUrl}) 270 if err != nil { 271 return err 272 } 273 ch, err = c.api.state.Charm(curl) 274 if err != nil { 275 return err 276 } 277 } else if err != nil { 278 return err 279 } 280 281 var settings charm.Settings 282 if len(args.ConfigYAML) > 0 { 283 settings, err = ch.Config().ParseSettingsYAML([]byte(args.ConfigYAML), args.ServiceName) 284 } else if len(args.Config) > 0 { 285 // Parse config in a compatile way (see function comment). 286 settings, err = parseSettingsCompatible(ch, args.Config) 287 } 288 if err != nil { 289 return err 290 } 291 // Convert network tags to names for any given networks. 292 requestedNetworks, err := networkTagsToNames(args.Networks) 293 if err != nil { 294 return err 295 } 296 297 _, err = juju.DeployService(c.api.state, 298 juju.DeployServiceParams{ 299 ServiceName: args.ServiceName, 300 ServiceOwner: c.api.auth.GetAuthTag(), 301 Charm: ch, 302 NumUnits: args.NumUnits, 303 ConfigSettings: settings, 304 Constraints: args.Constraints, 305 ToMachineSpec: args.ToMachineSpec, 306 Networks: requestedNetworks, 307 }) 308 return err 309 } 310 311 // ServiceDeployWithNetworks works exactly like ServiceDeploy, but 312 // allows specifying networks to include or exclude on the machine 313 // where the charm gets deployed (either with args.Network or with 314 // constraints). 315 func (c *Client) ServiceDeployWithNetworks(args params.ServiceDeploy) error { 316 return c.ServiceDeploy(args) 317 } 318 319 // ServiceUpdate updates the service attributes, including charm URL, 320 // minimum number of units, settings and constraints. 321 // All parameters in params.ServiceUpdate except the service name are optional. 322 func (c *Client) ServiceUpdate(args params.ServiceUpdate) error { 323 service, err := c.api.state.Service(args.ServiceName) 324 if err != nil { 325 return err 326 } 327 // Set the charm for the given service. 328 if args.CharmUrl != "" { 329 if err = c.serviceSetCharm(service, args.CharmUrl, args.ForceCharmUrl); err != nil { 330 return err 331 } 332 } 333 // Set the minimum number of units for the given service. 334 if args.MinUnits != nil { 335 if err = service.SetMinUnits(*args.MinUnits); err != nil { 336 return err 337 } 338 } 339 // Set up service's settings. 340 if args.SettingsYAML != "" { 341 if err = serviceSetSettingsYAML(service, args.SettingsYAML); err != nil { 342 return err 343 } 344 } else if len(args.SettingsStrings) > 0 { 345 if err = serviceSetSettingsStrings(service, args.SettingsStrings); err != nil { 346 return err 347 } 348 } 349 // Update service's constraints. 350 if args.Constraints != nil { 351 return service.SetConstraints(*args.Constraints) 352 } 353 return nil 354 } 355 356 // serviceSetCharm sets the charm for the given service. 357 func (c *Client) serviceSetCharm(service *state.Service, url string, force bool) error { 358 curl, err := charm.ParseURL(url) 359 if err != nil { 360 return err 361 } 362 sch, err := c.api.state.Charm(curl) 363 if errors.IsNotFound(err) { 364 // Charms should be added before trying to use them, with 365 // AddCharm or AddLocalCharm API calls. When they're not, 366 // we're reverting to 1.16 compatibility mode. 367 return c.serviceSetCharm1dot16(service, curl, force) 368 } 369 if err != nil { 370 return err 371 } 372 return service.SetCharm(sch, force) 373 } 374 375 // serviceSetCharm1dot16 sets the charm for the given service in 1.16 376 // compatibility mode. Remove this when support for 1.16 is dropped. 377 func (c *Client) serviceSetCharm1dot16(service *state.Service, curl *charm.URL, force bool) error { 378 if curl.Schema != "cs" { 379 return fmt.Errorf(`charm url has unsupported schema %q`, curl.Schema) 380 } 381 if curl.Revision < 0 { 382 return fmt.Errorf("charm url must include revision") 383 } 384 err := c.AddCharm(params.CharmURL{curl.String()}) 385 if err != nil { 386 return err 387 } 388 ch, err := c.api.state.Charm(curl) 389 if err != nil { 390 return err 391 } 392 return service.SetCharm(ch, force) 393 } 394 395 // serviceSetSettingsYAML updates the settings for the given service, 396 // taking the configuration from a YAML string. 397 func serviceSetSettingsYAML(service *state.Service, settings string) error { 398 ch, _, err := service.Charm() 399 if err != nil { 400 return err 401 } 402 changes, err := ch.Config().ParseSettingsYAML([]byte(settings), service.Name()) 403 if err != nil { 404 return err 405 } 406 return service.UpdateConfigSettings(changes) 407 } 408 409 // serviceSetSettingsStrings updates the settings for the given service, 410 // taking the configuration from a map of strings. 411 func serviceSetSettingsStrings(service *state.Service, settings map[string]string) error { 412 ch, _, err := service.Charm() 413 if err != nil { 414 return err 415 } 416 // Parse config in a compatible way (see function comment). 417 changes, err := parseSettingsCompatible(ch, settings) 418 if err != nil { 419 return err 420 } 421 return service.UpdateConfigSettings(changes) 422 } 423 424 // newServiceSetSettingsStringsForClientAPI updates the settings for the given 425 // service, taking the configuration from a map of strings. 426 // 427 // TODO(Nate): replace serviceSetSettingsStrings with this onces the GUI no 428 // longer expects to be able to unset values by sending an empty string. 429 func newServiceSetSettingsStringsForClientAPI(service *state.Service, settings map[string]string) error { 430 ch, _, err := service.Charm() 431 if err != nil { 432 return err 433 } 434 435 // Validate the settings. 436 changes, err := ch.Config().ParseSettingsStrings(settings) 437 if err != nil { 438 return err 439 } 440 441 return service.UpdateConfigSettings(changes) 442 } 443 444 // ServiceSetCharm sets the charm for a given service. 445 func (c *Client) ServiceSetCharm(args params.ServiceSetCharm) error { 446 service, err := c.api.state.Service(args.ServiceName) 447 if err != nil { 448 return err 449 } 450 return c.serviceSetCharm(service, args.CharmUrl, args.Force) 451 } 452 453 // addServiceUnits adds a given number of units to a service. 454 func addServiceUnits(state *state.State, args params.AddServiceUnits) ([]*state.Unit, error) { 455 service, err := state.Service(args.ServiceName) 456 if err != nil { 457 return nil, err 458 } 459 if args.NumUnits < 1 { 460 return nil, fmt.Errorf("must add at least one unit") 461 } 462 if args.NumUnits > 1 && args.ToMachineSpec != "" { 463 return nil, fmt.Errorf("cannot use NumUnits with ToMachineSpec") 464 } 465 return juju.AddUnits(state, service, args.NumUnits, args.ToMachineSpec) 466 } 467 468 // AddServiceUnits adds a given number of units to a service. 469 func (c *Client) AddServiceUnits(args params.AddServiceUnits) (params.AddServiceUnitsResults, error) { 470 units, err := addServiceUnits(c.api.state, args) 471 if err != nil { 472 return params.AddServiceUnitsResults{}, err 473 } 474 unitNames := make([]string, len(units)) 475 for i, unit := range units { 476 unitNames[i] = unit.String() 477 } 478 return params.AddServiceUnitsResults{Units: unitNames}, nil 479 } 480 481 // DestroyServiceUnits removes a given set of service units. 482 func (c *Client) DestroyServiceUnits(args params.DestroyServiceUnits) error { 483 var errs []string 484 for _, name := range args.UnitNames { 485 unit, err := c.api.state.Unit(name) 486 switch { 487 case errors.IsNotFound(err): 488 err = fmt.Errorf("unit %q does not exist", name) 489 case err != nil: 490 case unit.Life() != state.Alive: 491 continue 492 case unit.IsPrincipal(): 493 err = unit.Destroy() 494 default: 495 err = fmt.Errorf("unit %q is a subordinate", name) 496 } 497 if err != nil { 498 errs = append(errs, err.Error()) 499 } 500 } 501 return destroyErr("units", args.UnitNames, errs) 502 } 503 504 // ServiceDestroy destroys a given service. 505 func (c *Client) ServiceDestroy(args params.ServiceDestroy) error { 506 svc, err := c.api.state.Service(args.ServiceName) 507 if err != nil { 508 return err 509 } 510 return svc.Destroy() 511 } 512 513 // GetServiceConstraints returns the constraints for a given service. 514 func (c *Client) GetServiceConstraints(args params.GetServiceConstraints) (params.GetConstraintsResults, error) { 515 svc, err := c.api.state.Service(args.ServiceName) 516 if err != nil { 517 return params.GetConstraintsResults{}, err 518 } 519 cons, err := svc.Constraints() 520 return params.GetConstraintsResults{cons}, err 521 } 522 523 // GetEnvironmentConstraints returns the constraints for the environment. 524 func (c *Client) GetEnvironmentConstraints() (params.GetConstraintsResults, error) { 525 cons, err := c.api.state.EnvironConstraints() 526 if err != nil { 527 return params.GetConstraintsResults{}, err 528 } 529 return params.GetConstraintsResults{cons}, nil 530 } 531 532 // SetServiceConstraints sets the constraints for a given service. 533 func (c *Client) SetServiceConstraints(args params.SetConstraints) error { 534 svc, err := c.api.state.Service(args.ServiceName) 535 if err != nil { 536 return err 537 } 538 return svc.SetConstraints(args.Constraints) 539 } 540 541 // SetEnvironmentConstraints sets the constraints for the environment. 542 func (c *Client) SetEnvironmentConstraints(args params.SetConstraints) error { 543 return c.api.state.SetEnvironConstraints(args.Constraints) 544 } 545 546 // AddRelation adds a relation between the specified endpoints and returns the relation info. 547 func (c *Client) AddRelation(args params.AddRelation) (params.AddRelationResults, error) { 548 inEps, err := c.api.state.InferEndpoints(args.Endpoints) 549 if err != nil { 550 return params.AddRelationResults{}, err 551 } 552 rel, err := c.api.state.AddRelation(inEps...) 553 if err != nil { 554 return params.AddRelationResults{}, err 555 } 556 outEps := make(map[string]charm.Relation) 557 for _, inEp := range inEps { 558 outEp, err := rel.Endpoint(inEp.ServiceName) 559 if err != nil { 560 return params.AddRelationResults{}, err 561 } 562 outEps[inEp.ServiceName] = outEp.Relation 563 } 564 return params.AddRelationResults{Endpoints: outEps}, nil 565 } 566 567 // DestroyRelation removes the relation between the specified endpoints. 568 func (c *Client) DestroyRelation(args params.DestroyRelation) error { 569 eps, err := c.api.state.InferEndpoints(args.Endpoints) 570 if err != nil { 571 return err 572 } 573 rel, err := c.api.state.EndpointsRelation(eps...) 574 if err != nil { 575 return err 576 } 577 return rel.Destroy() 578 } 579 580 // AddMachines adds new machines with the supplied parameters. 581 func (c *Client) AddMachines(args params.AddMachines) (params.AddMachinesResults, error) { 582 return c.AddMachinesV2(args) 583 } 584 585 // AddMachinesV2 adds new machines with the supplied parameters. 586 func (c *Client) AddMachinesV2(args params.AddMachines) (params.AddMachinesResults, error) { 587 results := params.AddMachinesResults{ 588 Machines: make([]params.AddMachinesResult, len(args.MachineParams)), 589 } 590 for i, p := range args.MachineParams { 591 m, err := c.addOneMachine(p) 592 results.Machines[i].Error = common.ServerError(err) 593 if err == nil { 594 results.Machines[i].Machine = m.Id() 595 } 596 } 597 return results, nil 598 } 599 600 // InjectMachines injects a machine into state with provisioned status. 601 func (c *Client) InjectMachines(args params.AddMachines) (params.AddMachinesResults, error) { 602 return c.AddMachines(args) 603 } 604 605 func (c *Client) addOneMachine(p params.AddMachineParams) (*state.Machine, error) { 606 if p.ParentId != "" && p.ContainerType == "" { 607 return nil, fmt.Errorf("parent machine specified without container type") 608 } 609 if p.ContainerType != "" && p.Placement != nil { 610 return nil, fmt.Errorf("container type and placement are mutually exclusive") 611 } 612 if p.Placement != nil { 613 // Extract container type and parent from container placement directives. 614 containerType, err := instance.ParseContainerType(p.Placement.Scope) 615 if err == nil { 616 p.ContainerType = containerType 617 p.ParentId = p.Placement.Directive 618 p.Placement = nil 619 } 620 } 621 622 if p.ContainerType != "" || p.Placement != nil { 623 // Guard against dubious client by making sure that 624 // the following attributes can only be set when we're 625 // not using placement. 626 p.InstanceId = "" 627 p.Nonce = "" 628 p.HardwareCharacteristics = instance.HardwareCharacteristics{} 629 p.Addrs = nil 630 } 631 632 if p.Series == "" { 633 conf, err := c.api.state.EnvironConfig() 634 if err != nil { 635 return nil, err 636 } 637 p.Series = config.PreferredSeries(conf) 638 } 639 640 var placementDirective string 641 if p.Placement != nil { 642 env, err := c.api.state.Environment() 643 if err != nil { 644 return nil, err 645 } 646 if p.Placement.Scope != env.Name() { 647 return nil, fmt.Errorf("invalid environment name %q", p.Placement.Scope) 648 } 649 placementDirective = p.Placement.Directive 650 } 651 652 jobs, err := stateJobs(p.Jobs) 653 if err != nil { 654 return nil, err 655 } 656 template := state.MachineTemplate{ 657 Series: p.Series, 658 Constraints: p.Constraints, 659 InstanceId: p.InstanceId, 660 Jobs: jobs, 661 Nonce: p.Nonce, 662 HardwareCharacteristics: p.HardwareCharacteristics, 663 Addresses: p.Addrs, 664 Placement: placementDirective, 665 } 666 if p.ContainerType == "" { 667 return c.api.state.AddOneMachine(template) 668 } 669 if p.ParentId != "" { 670 return c.api.state.AddMachineInsideMachine(template, p.ParentId, p.ContainerType) 671 } 672 return c.api.state.AddMachineInsideNewMachine(template, template, p.ContainerType) 673 } 674 675 func stateJobs(jobs []params.MachineJob) ([]state.MachineJob, error) { 676 newJobs := make([]state.MachineJob, len(jobs)) 677 for i, job := range jobs { 678 newJob, err := state.MachineJobFromParams(job) 679 if err != nil { 680 return nil, err 681 } 682 newJobs[i] = newJob 683 } 684 return newJobs, nil 685 } 686 687 // ProvisioningScript returns a shell script that, when run, 688 // provisions a machine agent on the machine executing the script. 689 func (c *Client) ProvisioningScript(args params.ProvisioningScriptParams) (params.ProvisioningScriptResult, error) { 690 var result params.ProvisioningScriptResult 691 mcfg, err := MachineConfig(c.api.state, args.MachineId, args.Nonce, args.DataDir) 692 if err != nil { 693 return result, err 694 } 695 mcfg.DisablePackageCommands = args.DisablePackageCommands 696 result.Script, err = manual.ProvisioningScript(mcfg) 697 return result, err 698 } 699 700 // DestroyMachines removes a given set of machines. 701 func (c *Client) DestroyMachines(args params.DestroyMachines) error { 702 var errs []string 703 for _, id := range args.MachineNames { 704 machine, err := c.api.state.Machine(id) 705 switch { 706 case errors.IsNotFound(err): 707 err = fmt.Errorf("machine %s does not exist", id) 708 case err != nil: 709 case args.Force: 710 err = machine.ForceDestroy() 711 case machine.Life() != state.Alive: 712 continue 713 default: 714 err = machine.Destroy() 715 } 716 if err != nil { 717 errs = append(errs, err.Error()) 718 } 719 } 720 return destroyErr("machines", args.MachineNames, errs) 721 } 722 723 // CharmInfo returns information about the requested charm. 724 func (c *Client) CharmInfo(args params.CharmInfo) (api.CharmInfo, error) { 725 curl, err := charm.ParseURL(args.CharmURL) 726 if err != nil { 727 return api.CharmInfo{}, err 728 } 729 charm, err := c.api.state.Charm(curl) 730 if err != nil { 731 return api.CharmInfo{}, err 732 } 733 info := api.CharmInfo{ 734 Revision: charm.Revision(), 735 URL: curl.String(), 736 Config: charm.Config(), 737 Meta: charm.Meta(), 738 } 739 return info, nil 740 } 741 742 // EnvironmentInfo returns information about the current environment (default 743 // series and type). 744 func (c *Client) EnvironmentInfo() (api.EnvironmentInfo, error) { 745 state := c.api.state 746 conf, err := state.EnvironConfig() 747 if err != nil { 748 return api.EnvironmentInfo{}, err 749 } 750 env, err := state.Environment() 751 if err != nil { 752 return api.EnvironmentInfo{}, err 753 } 754 755 info := api.EnvironmentInfo{ 756 DefaultSeries: config.PreferredSeries(conf), 757 ProviderType: conf.Type(), 758 Name: conf.Name(), 759 UUID: env.UUID(), 760 } 761 return info, nil 762 } 763 764 // GetAnnotations returns annotations about a given entity. 765 func (c *Client) GetAnnotations(args params.GetAnnotations) (params.GetAnnotationsResults, error) { 766 nothing := params.GetAnnotationsResults{} 767 entity, err := c.findEntity(args.Tag) 768 if err != nil { 769 return nothing, err 770 } 771 ann, err := entity.Annotations() 772 if err != nil { 773 return nothing, err 774 } 775 return params.GetAnnotationsResults{Annotations: ann}, nil 776 } 777 778 func (c *Client) findEntity(tag string) (state.Annotator, error) { 779 entity0, err := c.api.state.FindEntity(tag) 780 if err != nil { 781 return nil, err 782 } 783 entity, ok := entity0.(state.Annotator) 784 if !ok { 785 return nil, common.NotSupportedError(tag, "annotations") 786 } 787 return entity, nil 788 } 789 790 // SetAnnotations stores annotations about a given entity. 791 func (c *Client) SetAnnotations(args params.SetAnnotations) error { 792 entity, err := c.findEntity(args.Tag) 793 if err != nil { 794 return err 795 } 796 return entity.SetAnnotations(args.Pairs) 797 } 798 799 // parseSettingsCompatible parses setting strings in a way that is 800 // compatible with the behavior before this CL based on the issue 801 // http://pad.lv/1194945. Until then setting an option to an empty 802 // string caused it to reset to the default value. We now allow 803 // empty strings as actual values, but we want to preserve the API 804 // behavior. 805 func parseSettingsCompatible(ch *state.Charm, settings map[string]string) (charm.Settings, error) { 806 setSettings := map[string]string{} 807 unsetSettings := charm.Settings{} 808 // Split settings into those which set and those which unset a value. 809 for name, value := range settings { 810 if value == "" { 811 unsetSettings[name] = nil 812 continue 813 } 814 setSettings[name] = value 815 } 816 // Validate the settings. 817 changes, err := ch.Config().ParseSettingsStrings(setSettings) 818 if err != nil { 819 return nil, err 820 } 821 // Validate the unsettings and merge them into the changes. 822 unsetSettings, err = ch.Config().ValidateSettings(unsetSettings) 823 if err != nil { 824 return nil, err 825 } 826 for name := range unsetSettings { 827 changes[name] = nil 828 } 829 return changes, nil 830 } 831 832 // AgentVersion returns the current version that the API server is running. 833 func (c *Client) AgentVersion() (params.AgentVersionResult, error) { 834 return params.AgentVersionResult{Version: version.Current.Number}, nil 835 } 836 837 // EnvironmentGet implements the server-side part of the 838 // get-environment CLI command. 839 func (c *Client) EnvironmentGet() (params.EnvironmentGetResults, error) { 840 result := params.EnvironmentGetResults{} 841 // Get the existing environment config from the state. 842 config, err := c.api.state.EnvironConfig() 843 if err != nil { 844 return result, err 845 } 846 result.Config = config.AllAttrs() 847 return result, nil 848 } 849 850 // EnvironmentSet implements the server-side part of the 851 // set-environment CLI command. 852 func (c *Client) EnvironmentSet(args params.EnvironmentSet) error { 853 // Make sure we don't allow changing agent-version. 854 checkAgentVersion := func(updateAttrs map[string]interface{}, removeAttrs []string, oldConfig *config.Config) error { 855 if v, found := updateAttrs["agent-version"]; found { 856 oldVersion, _ := oldConfig.AgentVersion() 857 if v != oldVersion.String() { 858 return fmt.Errorf("agent-version cannot be changed") 859 } 860 } 861 return nil 862 } 863 // TODO(waigani) 2014-3-11 #1167616 864 // Add a txn retry loop to ensure that the settings on disk have not 865 // changed underneath us. 866 return c.api.state.UpdateEnvironConfig(args.Config, nil, checkAgentVersion) 867 } 868 869 // EnvironmentUnset implements the server-side part of the 870 // set-environment CLI command. 871 func (c *Client) EnvironmentUnset(args params.EnvironmentUnset) error { 872 // TODO(waigani) 2014-3-11 #1167616 873 // Add a txn retry loop to ensure that the settings on disk have not 874 // changed underneath us. 875 return c.api.state.UpdateEnvironConfig(nil, args.Keys, nil) 876 } 877 878 // SetEnvironAgentVersion sets the environment agent version. 879 func (c *Client) SetEnvironAgentVersion(args params.SetEnvironAgentVersion) error { 880 return c.api.state.SetEnvironAgentVersion(args.Version) 881 } 882 883 // FindTools returns a List containing all tools matching the given parameters. 884 func (c *Client) FindTools(args params.FindToolsParams) (params.FindToolsResults, error) { 885 result := params.FindToolsResults{} 886 // Get the existing environment config from the state. 887 envConfig, err := c.api.state.EnvironConfig() 888 if err != nil { 889 return result, err 890 } 891 env, err := environs.New(envConfig) 892 if err != nil { 893 return result, err 894 } 895 filter := coretools.Filter{ 896 Arch: args.Arch, 897 Series: args.Series, 898 } 899 result.List, err = envtools.FindTools(env, args.MajorVersion, args.MinorVersion, filter, envtools.DoNotAllowRetry) 900 result.Error = common.ServerError(err) 901 return result, nil 902 } 903 904 func destroyErr(desc string, ids, errs []string) error { 905 if len(errs) == 0 { 906 return nil 907 } 908 msg := "some %s were not destroyed" 909 if len(errs) == len(ids) { 910 msg = "no %s were destroyed" 911 } 912 msg = fmt.Sprintf(msg, desc) 913 return fmt.Errorf("%s: %s", msg, strings.Join(errs, "; ")) 914 } 915 916 // AddCharm adds the given charm URL (which must include revision) to 917 // the environment, if it does not exist yet. Local charms are not 918 // supported, only charm store URLs. See also AddLocalCharm(). 919 func (c *Client) AddCharm(args params.CharmURL) error { 920 charmURL, err := charm.ParseURL(args.URL) 921 if err != nil { 922 return err 923 } 924 if charmURL.Schema != "cs" { 925 return fmt.Errorf("only charm store charm URLs are supported, with cs: schema") 926 } 927 if charmURL.Revision < 0 { 928 return fmt.Errorf("charm URL must include revision") 929 } 930 931 // First, check if a pending or a real charm exists in state. 932 stateCharm, err := c.api.state.PrepareStoreCharmUpload(charmURL) 933 if err == nil && stateCharm.IsUploaded() { 934 // Charm already in state (it was uploaded already). 935 return nil 936 } else if err != nil { 937 return err 938 } 939 940 // Get the charm and its information from the store. 941 envConfig, err := c.api.state.EnvironConfig() 942 if err != nil { 943 return err 944 } 945 store := config.SpecializeCharmRepo(CharmStore, envConfig) 946 downloadedCharm, err := store.Get(charmURL) 947 if err != nil { 948 return errors.Annotatef(err, "cannot download charm %q", charmURL.String()) 949 } 950 951 // Open it and calculate the SHA256 hash. 952 downloadedBundle, ok := downloadedCharm.(*charm.Bundle) 953 if !ok { 954 return errors.Errorf("expected a charm archive, got %T", downloadedCharm) 955 } 956 archive, err := os.Open(downloadedBundle.Path) 957 if err != nil { 958 return errors.Annotate(err, "cannot read downloaded charm") 959 } 960 defer archive.Close() 961 bundleSHA256, size, err := utils.ReadSHA256(archive) 962 if err != nil { 963 return errors.Annotate(err, "cannot calculate SHA256 hash of charm") 964 } 965 if _, err := archive.Seek(0, 0); err != nil { 966 return errors.Annotate(err, "cannot rewind charm archive") 967 } 968 969 // Get the environment storage and upload the charm. 970 env, err := environs.New(envConfig) 971 if err != nil { 972 return errors.Annotate(err, "cannot access environment") 973 } 974 storage := env.Storage() 975 archiveName, err := CharmArchiveName(charmURL.Name, charmURL.Revision) 976 if err != nil { 977 return errors.Annotate(err, "cannot generate charm archive name") 978 } 979 if err := storage.Put(archiveName, archive, size); err != nil { 980 return errors.Annotate(err, "cannot upload charm to provider storage") 981 } 982 storageURL, err := storage.URL(archiveName) 983 if err != nil { 984 return errors.Annotate(err, "cannot get storage URL for charm") 985 } 986 bundleURL, err := url.Parse(storageURL) 987 if err != nil { 988 return errors.Annotate(err, "cannot parse storage URL") 989 } 990 991 // Finally, update the charm data in state and mark it as no longer pending. 992 _, err = c.api.state.UpdateUploadedCharm(downloadedCharm, charmURL, bundleURL, bundleSHA256) 993 if err == state.ErrCharmRevisionAlreadyModified || 994 state.IsCharmAlreadyUploadedError(err) { 995 // This is not an error, it just signifies somebody else 996 // managed to upload and update the charm in state before 997 // us. This means we have to delete what we just uploaded 998 // to storage. 999 if err := storage.Remove(archiveName); err != nil { 1000 errors.Annotate(err, "cannot remove duplicated charm from storage") 1001 } 1002 return nil 1003 } 1004 return err 1005 } 1006 1007 func (c *Client) ResolveCharms(args params.ResolveCharms) (params.ResolveCharmResults, error) { 1008 var results params.ResolveCharmResults 1009 1010 envConfig, err := c.api.state.EnvironConfig() 1011 if err != nil { 1012 return params.ResolveCharmResults{}, err 1013 } 1014 repo := config.SpecializeCharmRepo(CharmStore, envConfig) 1015 1016 for _, ref := range args.References { 1017 result := params.ResolveCharmResult{} 1018 curl, err := c.resolveCharm(ref, repo) 1019 if err != nil { 1020 result.Error = err.Error() 1021 } else { 1022 result.URL = curl 1023 } 1024 results.URLs = append(results.URLs, result) 1025 } 1026 return results, nil 1027 } 1028 1029 func (c *Client) resolveCharm(ref charm.Reference, repo charm.Repository) (*charm.URL, error) { 1030 if ref.Schema != "cs" { 1031 return nil, fmt.Errorf("only charm store charm references are supported, with cs: schema") 1032 } 1033 1034 // Resolve the charm location with the repository. 1035 return repo.Resolve(ref) 1036 } 1037 1038 // CharmArchiveName returns a string that is suitable as a file name 1039 // in a storage URL. It is constructed from the charm name, revision 1040 // and a random UUID string. 1041 func CharmArchiveName(name string, revision int) (string, error) { 1042 uuid, err := utils.NewUUID() 1043 if err != nil { 1044 return "", err 1045 } 1046 return charm.Quote(fmt.Sprintf("%s-%d-%s", name, revision, uuid)), nil 1047 } 1048 1049 // RetryProvisioning marks a provisioning error as transient on the machines. 1050 func (c *Client) RetryProvisioning(p params.Entities) (params.ErrorResults, error) { 1051 entityStatus := make([]params.EntityStatus, len(p.Entities)) 1052 for i, entity := range p.Entities { 1053 entityStatus[i] = params.EntityStatus{Tag: entity.Tag, Data: params.StatusData{"transient": true}} 1054 } 1055 return c.api.statusSetter.UpdateStatus(params.SetStatus{ 1056 Entities: entityStatus, 1057 }) 1058 } 1059 1060 // APIHostPorts returns the API host/port addresses stored in state. 1061 func (c *Client) APIHostPorts() (result params.APIHostPortsResult, err error) { 1062 if result.Servers, err = c.api.state.APIHostPorts(); err != nil { 1063 return params.APIHostPortsResult{}, err 1064 } 1065 return result, nil 1066 } 1067 1068 // EnsureAvailability ensures the availability of Juju state servers. 1069 func (c *Client) EnsureAvailability(args params.EnsureAvailability) error { 1070 series := args.Series 1071 if series == "" { 1072 ssi, err := c.api.state.StateServerInfo() 1073 if err != nil { 1074 return err 1075 } 1076 // We should always have at least one voting machine 1077 // If we *really* wanted we could just pick whatever series is 1078 // in the majority, but really, if we always copy the value of 1079 // the first one, then they'll stay in sync. 1080 if len(ssi.VotingMachineIds) == 0 { 1081 // Better than a panic()? 1082 return fmt.Errorf("internal error, failed to find any voting machines") 1083 } 1084 templateMachine, err := c.api.state.Machine(ssi.VotingMachineIds[0]) 1085 if err != nil { 1086 return err 1087 } 1088 series = templateMachine.Series() 1089 } 1090 return c.api.state.EnsureAvailability(args.NumStateServers, args.Constraints, series) 1091 }