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