github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/client/client.go (about) 1 // Copyright 2013, 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package client 5 6 import ( 7 "fmt" 8 9 "github.com/juju/errors" 10 "github.com/juju/loggo" 11 "gopkg.in/juju/names.v2" 12 13 "github.com/juju/juju/apiserver/application" 14 "github.com/juju/juju/apiserver/common" 15 "github.com/juju/juju/apiserver/facade" 16 "github.com/juju/juju/apiserver/modelconfig" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/environs/config" 20 "github.com/juju/juju/environs/manual" 21 "github.com/juju/juju/instance" 22 "github.com/juju/juju/network" 23 "github.com/juju/juju/permission" 24 "github.com/juju/juju/state" 25 "github.com/juju/juju/state/stateenvirons" 26 jujuversion "github.com/juju/juju/version" 27 ) 28 29 func init() { 30 common.RegisterStandardFacade("Client", 1, newClient) 31 } 32 33 var logger = loggo.GetLogger("juju.apiserver.client") 34 35 type API struct { 36 stateAccessor Backend 37 auth facade.Authorizer 38 resources facade.Resources 39 client *Client 40 // statusSetter provides common methods for updating an entity's provisioning status. 41 statusSetter *common.StatusSetter 42 toolsFinder *common.ToolsFinder 43 } 44 45 // TODO(wallyworld) - remove this method 46 // state returns a state.State instance for this API. 47 // Until all code is refactored to use interfaces, we 48 // need this helper to keep older code happy. 49 func (api *API) state() *state.State { 50 return api.stateAccessor.(stateShim).State 51 } 52 53 // Client serves client-specific API methods. 54 type Client struct { 55 // TODO(wallyworld) - we'll retain model config facade methods 56 // on the client facade until GUI and Python client library are updated. 57 *modelconfig.ModelConfigAPI 58 59 api *API 60 newEnviron func() (environs.Environ, error) 61 check *common.BlockChecker 62 } 63 64 func (c *Client) checkCanRead() error { 65 canRead, err := c.api.auth.HasPermission(permission.ReadAccess, c.api.stateAccessor.ModelTag()) 66 if err != nil { 67 return errors.Trace(err) 68 } 69 if !canRead { 70 return common.ErrPerm 71 } 72 return nil 73 } 74 75 func (c *Client) checkCanWrite() error { 76 canWrite, err := c.api.auth.HasPermission(permission.WriteAccess, c.api.stateAccessor.ModelTag()) 77 if err != nil { 78 return errors.Trace(err) 79 } 80 if !canWrite { 81 return common.ErrPerm 82 } 83 return nil 84 } 85 86 func newClient(st *state.State, resources facade.Resources, authorizer facade.Authorizer) (*Client, error) { 87 urlGetter := common.NewToolsURLGetter(st.ModelUUID(), st) 88 configGetter := stateenvirons.EnvironConfigGetter{st} 89 statusSetter := common.NewStatusSetter(st, common.AuthAlways()) 90 toolsFinder := common.NewToolsFinder(configGetter, st, urlGetter) 91 newEnviron := func() (environs.Environ, error) { 92 return environs.GetEnviron(configGetter, environs.New) 93 } 94 blockChecker := common.NewBlockChecker(st) 95 modelConfigAPI, err := modelconfig.NewModelConfigAPI(st, authorizer) 96 if err != nil { 97 return nil, errors.Trace(err) 98 } 99 return NewClient( 100 NewStateBackend(st), 101 modelConfigAPI, 102 resources, 103 authorizer, 104 statusSetter, 105 toolsFinder, 106 newEnviron, 107 blockChecker, 108 ) 109 } 110 111 // NewClient creates a new instance of the Client Facade. 112 func NewClient( 113 st Backend, 114 modelConfigAPI *modelconfig.ModelConfigAPI, 115 resources facade.Resources, 116 authorizer facade.Authorizer, 117 statusSetter *common.StatusSetter, 118 toolsFinder *common.ToolsFinder, 119 newEnviron func() (environs.Environ, error), 120 blockChecker *common.BlockChecker, 121 ) (*Client, error) { 122 if !authorizer.AuthClient() { 123 return nil, common.ErrPerm 124 } 125 client := &Client{ 126 modelConfigAPI, 127 &API{ 128 stateAccessor: st, 129 auth: authorizer, 130 resources: resources, 131 statusSetter: statusSetter, 132 toolsFinder: toolsFinder, 133 }, 134 newEnviron, 135 blockChecker, 136 } 137 return client, nil 138 } 139 140 func (c *Client) WatchAll() (params.AllWatcherId, error) { 141 if err := c.checkCanRead(); err != nil { 142 return params.AllWatcherId{}, err 143 } 144 w := c.api.stateAccessor.Watch() 145 return params.AllWatcherId{ 146 AllWatcherId: c.api.resources.Register(w), 147 }, nil 148 } 149 150 // Resolved implements the server side of Client.Resolved. 151 func (c *Client) Resolved(p params.Resolved) error { 152 if err := c.checkCanWrite(); err != nil { 153 return err 154 } 155 if err := c.check.ChangeAllowed(); err != nil { 156 return errors.Trace(err) 157 } 158 unit, err := c.api.stateAccessor.Unit(p.UnitName) 159 if err != nil { 160 return err 161 } 162 return unit.Resolve(p.Retry) 163 } 164 165 // PublicAddress implements the server side of Client.PublicAddress. 166 func (c *Client) PublicAddress(p params.PublicAddress) (results params.PublicAddressResults, err error) { 167 if err := c.checkCanRead(); err != nil { 168 return params.PublicAddressResults{}, err 169 } 170 171 switch { 172 case names.IsValidMachine(p.Target): 173 machine, err := c.api.stateAccessor.Machine(p.Target) 174 if err != nil { 175 return results, err 176 } 177 addr, err := machine.PublicAddress() 178 if err != nil { 179 return results, errors.Annotatef(err, "error fetching address for machine %q", machine) 180 } 181 return params.PublicAddressResults{PublicAddress: addr.Value}, nil 182 183 case names.IsValidUnit(p.Target): 184 unit, err := c.api.stateAccessor.Unit(p.Target) 185 if err != nil { 186 return results, err 187 } 188 addr, err := unit.PublicAddress() 189 if err != nil { 190 return results, errors.Annotatef(err, "error fetching address for unit %q", unit) 191 } 192 return params.PublicAddressResults{PublicAddress: addr.Value}, nil 193 } 194 return results, errors.Errorf("unknown unit or machine %q", p.Target) 195 } 196 197 // PrivateAddress implements the server side of Client.PrivateAddress. 198 func (c *Client) PrivateAddress(p params.PrivateAddress) (results params.PrivateAddressResults, err error) { 199 if err := c.checkCanRead(); err != nil { 200 return params.PrivateAddressResults{}, err 201 } 202 203 switch { 204 case names.IsValidMachine(p.Target): 205 machine, err := c.api.stateAccessor.Machine(p.Target) 206 if err != nil { 207 return results, err 208 } 209 addr, err := machine.PrivateAddress() 210 if err != nil { 211 return results, errors.Annotatef(err, "error fetching address for machine %q", machine) 212 } 213 return params.PrivateAddressResults{PrivateAddress: addr.Value}, nil 214 215 case names.IsValidUnit(p.Target): 216 unit, err := c.api.stateAccessor.Unit(p.Target) 217 if err != nil { 218 return results, err 219 } 220 addr, err := unit.PrivateAddress() 221 if err != nil { 222 return results, errors.Annotatef(err, "error fetching address for unit %q", unit) 223 } 224 return params.PrivateAddressResults{PrivateAddress: addr.Value}, nil 225 } 226 return results, fmt.Errorf("unknown unit or machine %q", p.Target) 227 228 } 229 230 // GetModelConstraints returns the constraints for the model. 231 func (c *Client) GetModelConstraints() (params.GetConstraintsResults, error) { 232 if err := c.checkCanRead(); err != nil { 233 return params.GetConstraintsResults{}, err 234 } 235 236 cons, err := c.api.stateAccessor.ModelConstraints() 237 if err != nil { 238 return params.GetConstraintsResults{}, err 239 } 240 return params.GetConstraintsResults{cons}, nil 241 } 242 243 // SetModelConstraints sets the constraints for the model. 244 func (c *Client) SetModelConstraints(args params.SetConstraints) error { 245 if err := c.checkCanWrite(); err != nil { 246 return err 247 } 248 249 if err := c.check.ChangeAllowed(); err != nil { 250 return errors.Trace(err) 251 } 252 return c.api.stateAccessor.SetModelConstraints(args.Constraints) 253 } 254 255 // AddMachines adds new machines with the supplied parameters. 256 func (c *Client) AddMachines(args params.AddMachines) (params.AddMachinesResults, error) { 257 if err := c.checkCanWrite(); err != nil { 258 return params.AddMachinesResults{}, err 259 } 260 261 return c.AddMachinesV2(args) 262 } 263 264 // AddMachinesV2 adds new machines with the supplied parameters. 265 func (c *Client) AddMachinesV2(args params.AddMachines) (params.AddMachinesResults, error) { 266 results := params.AddMachinesResults{ 267 Machines: make([]params.AddMachinesResult, len(args.MachineParams)), 268 } 269 if err := c.check.ChangeAllowed(); err != nil { 270 return results, errors.Trace(err) 271 } 272 for i, p := range args.MachineParams { 273 m, err := c.addOneMachine(p) 274 results.Machines[i].Error = common.ServerError(err) 275 if err == nil { 276 results.Machines[i].Machine = m.Id() 277 } 278 } 279 return results, nil 280 } 281 282 // InjectMachines injects a machine into state with provisioned status. 283 func (c *Client) InjectMachines(args params.AddMachines) (params.AddMachinesResults, error) { 284 if err := c.checkCanWrite(); err != nil { 285 return params.AddMachinesResults{}, err 286 } 287 288 return c.AddMachines(args) 289 } 290 291 func (c *Client) addOneMachine(p params.AddMachineParams) (*state.Machine, error) { 292 if p.ParentId != "" && p.ContainerType == "" { 293 return nil, fmt.Errorf("parent machine specified without container type") 294 } 295 if p.ContainerType != "" && p.Placement != nil { 296 return nil, fmt.Errorf("container type and placement are mutually exclusive") 297 } 298 if p.Placement != nil { 299 // Extract container type and parent from container placement directives. 300 containerType, err := instance.ParseContainerType(p.Placement.Scope) 301 if err == nil { 302 p.ContainerType = containerType 303 p.ParentId = p.Placement.Directive 304 p.Placement = nil 305 } 306 } 307 308 if p.ContainerType != "" || p.Placement != nil { 309 // Guard against dubious client by making sure that 310 // the following attributes can only be set when we're 311 // not using placement. 312 p.InstanceId = "" 313 p.Nonce = "" 314 p.HardwareCharacteristics = instance.HardwareCharacteristics{} 315 p.Addrs = nil 316 } 317 318 if p.Series == "" { 319 conf, err := c.api.stateAccessor.ModelConfig() 320 if err != nil { 321 return nil, err 322 } 323 p.Series = config.PreferredSeries(conf) 324 } 325 326 var placementDirective string 327 if p.Placement != nil { 328 env, err := c.api.stateAccessor.Model() 329 if err != nil { 330 return nil, err 331 } 332 // For 1.21 we should support both UUID and name, and with 1.22 333 // just support UUID 334 if p.Placement.Scope != env.Name() && p.Placement.Scope != env.UUID() { 335 return nil, fmt.Errorf("invalid model name %q", p.Placement.Scope) 336 } 337 placementDirective = p.Placement.Directive 338 } 339 340 jobs, err := common.StateJobs(p.Jobs) 341 if err != nil { 342 return nil, err 343 } 344 template := state.MachineTemplate{ 345 Series: p.Series, 346 Constraints: p.Constraints, 347 InstanceId: p.InstanceId, 348 Jobs: jobs, 349 Nonce: p.Nonce, 350 HardwareCharacteristics: p.HardwareCharacteristics, 351 Addresses: params.NetworkAddresses(p.Addrs...), 352 Placement: placementDirective, 353 } 354 if p.ContainerType == "" { 355 return c.api.stateAccessor.AddOneMachine(template) 356 } 357 if p.ParentId != "" { 358 return c.api.stateAccessor.AddMachineInsideMachine(template, p.ParentId, p.ContainerType) 359 } 360 return c.api.stateAccessor.AddMachineInsideNewMachine(template, template, p.ContainerType) 361 } 362 363 // ProvisioningScript returns a shell script that, when run, 364 // provisions a machine agent on the machine executing the script. 365 func (c *Client) ProvisioningScript(args params.ProvisioningScriptParams) (params.ProvisioningScriptResult, error) { 366 if err := c.checkCanWrite(); err != nil { 367 return params.ProvisioningScriptResult{}, err 368 } 369 370 var result params.ProvisioningScriptResult 371 icfg, err := InstanceConfig(c.api.state(), args.MachineId, args.Nonce, args.DataDir) 372 if err != nil { 373 return result, common.ServerError(errors.Annotate( 374 err, "getting instance config", 375 )) 376 } 377 378 // Until DisablePackageCommands is retired, for backwards 379 // compatibility, we must respect the client's request and 380 // override any model settings the user may have specified. 381 // If the client does specify this setting, it will only ever be 382 // true. False indicates the client doesn't care and we should use 383 // what's specified in the environment config. 384 if args.DisablePackageCommands { 385 icfg.EnableOSRefreshUpdate = false 386 icfg.EnableOSUpgrade = false 387 } else if cfg, err := c.api.stateAccessor.ModelConfig(); err != nil { 388 return result, common.ServerError(errors.Annotate( 389 err, "getting model config", 390 )) 391 } else { 392 icfg.EnableOSUpgrade = cfg.EnableOSUpgrade() 393 icfg.EnableOSRefreshUpdate = cfg.EnableOSRefreshUpdate() 394 } 395 396 result.Script, err = manual.ProvisioningScript(icfg) 397 if err != nil { 398 return result, common.ServerError(errors.Annotate( 399 err, "getting provisioning script", 400 )) 401 } 402 return result, nil 403 } 404 405 // DestroyMachines removes a given set of machines. 406 func (c *Client) DestroyMachines(args params.DestroyMachines) error { 407 if err := c.checkCanWrite(); err != nil { 408 return err 409 } 410 411 if err := c.check.RemoveAllowed(); !args.Force && err != nil { 412 return errors.Trace(err) 413 } 414 415 return common.DestroyMachines(c.api.stateAccessor, args.Force, args.MachineNames...) 416 } 417 418 // ModelInfo returns information about the current model. 419 func (c *Client) ModelInfo() (params.ModelInfo, error) { 420 if err := c.checkCanWrite(); err != nil { 421 return params.ModelInfo{}, err 422 } 423 state := c.api.stateAccessor 424 conf, err := state.ModelConfig() 425 if err != nil { 426 return params.ModelInfo{}, err 427 } 428 model, err := state.Model() 429 if err != nil { 430 return params.ModelInfo{}, err 431 } 432 info := params.ModelInfo{ 433 DefaultSeries: config.PreferredSeries(conf), 434 CloudTag: names.NewCloudTag(model.Cloud()).String(), 435 CloudRegion: model.CloudRegion(), 436 ProviderType: conf.Type(), 437 Name: conf.Name(), 438 UUID: model.UUID(), 439 OwnerTag: model.Owner().String(), 440 Life: params.Life(model.Life().String()), 441 } 442 if tag, ok := model.CloudCredential(); ok { 443 info.CloudCredentialTag = tag.String() 444 } 445 return info, nil 446 } 447 448 func modelInfo(st *state.State, user permission.UserAccess) (params.ModelUserInfo, error) { 449 return common.ModelUserInfo(user, st) 450 } 451 452 // ModelUserInfo returns information on all users in the model. 453 func (c *Client) ModelUserInfo() (params.ModelUserInfoResults, error) { 454 var results params.ModelUserInfoResults 455 if err := c.checkCanRead(); err != nil { 456 return results, err 457 } 458 459 env, err := c.api.stateAccessor.Model() 460 if err != nil { 461 return results, errors.Trace(err) 462 } 463 users, err := env.Users() 464 if err != nil { 465 return results, errors.Trace(err) 466 } 467 468 for _, user := range users { 469 var result params.ModelUserInfoResult 470 userInfo, err := modelInfo(c.api.state(), user) 471 if err != nil { 472 result.Error = common.ServerError(err) 473 } else { 474 result.Result = &userInfo 475 } 476 results.Results = append(results.Results, result) 477 } 478 return results, nil 479 } 480 481 // AgentVersion returns the current version that the API server is running. 482 func (c *Client) AgentVersion() (params.AgentVersionResult, error) { 483 if err := c.checkCanRead(); err != nil { 484 return params.AgentVersionResult{}, err 485 } 486 487 return params.AgentVersionResult{Version: jujuversion.Current}, nil 488 } 489 490 // SetModelAgentVersion sets the model agent version. 491 func (c *Client) SetModelAgentVersion(args params.SetModelAgentVersion) error { 492 if err := c.checkCanWrite(); err != nil { 493 return err 494 } 495 496 if err := c.check.ChangeAllowed(); err != nil { 497 return errors.Trace(err) 498 } 499 // Before changing the agent version to trigger an upgrade or downgrade, 500 // we'll do a very basic check to ensure the environment is accessible. 501 env, err := c.newEnviron() 502 if err != nil { 503 return errors.Trace(err) 504 } 505 if err := environs.CheckProviderAPI(env); err != nil { 506 return err 507 } 508 return c.api.stateAccessor.SetModelAgentVersion(args.Version) 509 } 510 511 // AbortCurrentUpgrade aborts and archives the current upgrade 512 // synchronisation record, if any. 513 func (c *Client) AbortCurrentUpgrade() error { 514 if err := c.checkCanWrite(); err != nil { 515 return err 516 } 517 518 if err := c.check.ChangeAllowed(); err != nil { 519 return errors.Trace(err) 520 } 521 return c.api.stateAccessor.AbortCurrentUpgrade() 522 } 523 524 // FindTools returns a List containing all tools matching the given parameters. 525 func (c *Client) FindTools(args params.FindToolsParams) (params.FindToolsResult, error) { 526 if err := c.checkCanWrite(); err != nil { 527 return params.FindToolsResult{}, err 528 } 529 530 return c.api.toolsFinder.FindTools(args) 531 } 532 533 func (c *Client) AddCharm(args params.AddCharm) error { 534 if err := c.checkCanWrite(); err != nil { 535 return err 536 } 537 538 return application.AddCharmWithAuthorization(c.api.state(), params.AddCharmWithAuthorization{ 539 URL: args.URL, 540 Channel: args.Channel, 541 }) 542 } 543 544 // AddCharmWithAuthorization adds the given charm URL (which must include revision) to 545 // the model, if it does not exist yet. Local charms are not 546 // supported, only charm store URLs. See also AddLocalCharm(). 547 // 548 // The authorization macaroon, args.CharmStoreMacaroon, may be 549 // omitted, in which case this call is equivalent to AddCharm. 550 func (c *Client) AddCharmWithAuthorization(args params.AddCharmWithAuthorization) error { 551 if err := c.checkCanWrite(); err != nil { 552 return err 553 } 554 555 return application.AddCharmWithAuthorization(c.api.state(), args) 556 } 557 558 // ResolveCharm resolves the best available charm URLs with series, for charm 559 // locations without a series specified. 560 func (c *Client) ResolveCharms(args params.ResolveCharms) (params.ResolveCharmResults, error) { 561 if err := c.checkCanWrite(); err != nil { 562 return params.ResolveCharmResults{}, err 563 } 564 565 return application.ResolveCharms(c.api.state(), args) 566 } 567 568 // RetryProvisioning marks a provisioning error as transient on the machines. 569 func (c *Client) RetryProvisioning(p params.Entities) (params.ErrorResults, error) { 570 if err := c.checkCanWrite(); err != nil { 571 return params.ErrorResults{}, err 572 } 573 574 if err := c.check.ChangeAllowed(); err != nil { 575 return params.ErrorResults{}, errors.Trace(err) 576 } 577 entityStatus := make([]params.EntityStatusArgs, len(p.Entities)) 578 for i, entity := range p.Entities { 579 entityStatus[i] = params.EntityStatusArgs{Tag: entity.Tag, Data: map[string]interface{}{"transient": true}} 580 } 581 return c.api.statusSetter.UpdateStatus(params.SetStatus{ 582 Entities: entityStatus, 583 }) 584 } 585 586 // APIHostPorts returns the API host/port addresses stored in state. 587 func (c *Client) APIHostPorts() (result params.APIHostPortsResult, err error) { 588 if err := c.checkCanWrite(); err != nil { 589 return result, err 590 } 591 592 var servers [][]network.HostPort 593 if servers, err = c.api.stateAccessor.APIHostPorts(); err != nil { 594 return params.APIHostPortsResult{}, err 595 } 596 result.Servers = params.FromNetworkHostsPorts(servers) 597 return result, nil 598 }