github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/state/apiserver/provisioner/provisioner.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provisioner 5 6 import ( 7 "fmt" 8 9 "github.com/juju/names" 10 "github.com/juju/utils/set" 11 12 "github.com/juju/juju/constraints" 13 "github.com/juju/juju/container" 14 "github.com/juju/juju/instance" 15 "github.com/juju/juju/state" 16 "github.com/juju/juju/state/api/params" 17 "github.com/juju/juju/state/apiserver/common" 18 "github.com/juju/juju/state/watcher" 19 ) 20 21 // ProvisionerAPI provides access to the Provisioner API facade. 22 type ProvisionerAPI struct { 23 *common.Remover 24 *common.StatusSetter 25 *common.DeadEnsurer 26 *common.PasswordChanger 27 *common.LifeGetter 28 *common.StateAddresser 29 *common.APIAddresser 30 *common.ToolsGetter 31 *common.EnvironWatcher 32 *common.EnvironMachinesWatcher 33 *common.InstanceIdGetter 34 35 st *state.State 36 resources *common.Resources 37 authorizer common.Authorizer 38 getAuthFunc common.GetAuthFunc 39 getCanWatchMachines common.GetAuthFunc 40 } 41 42 // NewProvisionerAPI creates a new server-side ProvisionerAPI facade. 43 func NewProvisionerAPI( 44 st *state.State, 45 resources *common.Resources, 46 authorizer common.Authorizer, 47 ) (*ProvisionerAPI, error) { 48 if !authorizer.AuthMachineAgent() && !authorizer.AuthEnvironManager() { 49 return nil, common.ErrPerm 50 } 51 getAuthFunc := func() (common.AuthFunc, error) { 52 isEnvironManager := authorizer.AuthEnvironManager() 53 isMachineAgent := authorizer.AuthMachineAgent() 54 authEntityTag := authorizer.GetAuthTag() 55 56 return func(tag string) bool { 57 if isMachineAgent && tag == authEntityTag { 58 // A machine agent can always access its own machine. 59 return true 60 } 61 _, id, err := names.ParseTag(tag, names.MachineTagKind) 62 if err != nil { 63 return false 64 } 65 parentId := state.ParentId(id) 66 if parentId == "" { 67 // All top-level machines are accessible by the 68 // environment manager. 69 return isEnvironManager 70 } 71 // All containers with the authenticated machine as a 72 // parent are accessible by it. 73 return isMachineAgent && names.MachineTag(parentId) == authEntityTag 74 }, nil 75 } 76 // Both provisioner types can watch the environment. 77 getCanWatch := common.AuthAlways(true) 78 // Only the environment provisioner can read secrets. 79 getCanReadSecrets := common.AuthAlways(authorizer.AuthEnvironManager()) 80 return &ProvisionerAPI{ 81 Remover: common.NewRemover(st, false, getAuthFunc), 82 StatusSetter: common.NewStatusSetter(st, getAuthFunc), 83 DeadEnsurer: common.NewDeadEnsurer(st, getAuthFunc), 84 PasswordChanger: common.NewPasswordChanger(st, getAuthFunc), 85 LifeGetter: common.NewLifeGetter(st, getAuthFunc), 86 StateAddresser: common.NewStateAddresser(st), 87 APIAddresser: common.NewAPIAddresser(st, resources), 88 ToolsGetter: common.NewToolsGetter(st, getAuthFunc), 89 EnvironWatcher: common.NewEnvironWatcher(st, resources, getCanWatch, getCanReadSecrets), 90 EnvironMachinesWatcher: common.NewEnvironMachinesWatcher(st, resources, getCanReadSecrets), 91 InstanceIdGetter: common.NewInstanceIdGetter(st, getAuthFunc), 92 st: st, 93 resources: resources, 94 authorizer: authorizer, 95 getAuthFunc: getAuthFunc, 96 getCanWatchMachines: getCanReadSecrets, 97 }, nil 98 } 99 100 func (p *ProvisionerAPI) getMachine(canAccess common.AuthFunc, tag string) (*state.Machine, error) { 101 if !canAccess(tag) { 102 return nil, common.ErrPerm 103 } 104 entity, err := p.st.FindEntity(tag) 105 if err != nil { 106 return nil, err 107 } 108 // The authorization function guarantees that the tag represents a 109 // machine. 110 return entity.(*state.Machine), nil 111 } 112 113 func (p *ProvisionerAPI) watchOneMachineContainers(arg params.WatchContainer) (params.StringsWatchResult, error) { 114 nothing := params.StringsWatchResult{} 115 canAccess, err := p.getAuthFunc() 116 if err != nil { 117 return nothing, err 118 } 119 if !canAccess(arg.MachineTag) { 120 return nothing, common.ErrPerm 121 } 122 _, id, err := names.ParseTag(arg.MachineTag, names.MachineTagKind) 123 if err != nil { 124 return nothing, err 125 } 126 machine, err := p.st.Machine(id) 127 if err != nil { 128 return nothing, err 129 } 130 var watch state.StringsWatcher 131 if arg.ContainerType != "" { 132 watch = machine.WatchContainers(instance.ContainerType(arg.ContainerType)) 133 } else { 134 watch = machine.WatchAllContainers() 135 } 136 // Consume the initial event and forward it to the result. 137 if changes, ok := <-watch.Changes(); ok { 138 return params.StringsWatchResult{ 139 StringsWatcherId: p.resources.Register(watch), 140 Changes: changes, 141 }, nil 142 } 143 return nothing, watcher.MustErr(watch) 144 } 145 146 // WatchContainers starts a StringsWatcher to watch containers deployed to 147 // any machine passed in args. 148 func (p *ProvisionerAPI) WatchContainers(args params.WatchContainers) (params.StringsWatchResults, error) { 149 result := params.StringsWatchResults{ 150 Results: make([]params.StringsWatchResult, len(args.Params)), 151 } 152 for i, arg := range args.Params { 153 watcherResult, err := p.watchOneMachineContainers(arg) 154 result.Results[i] = watcherResult 155 result.Results[i].Error = common.ServerError(err) 156 } 157 return result, nil 158 } 159 160 // WatchAllContainers starts a StringsWatcher to watch all containers deployed to 161 // any machine passed in args. 162 func (p *ProvisionerAPI) WatchAllContainers(args params.WatchContainers) (params.StringsWatchResults, error) { 163 return p.WatchContainers(args) 164 } 165 166 // SetSupportedContainers updates the list of containers supported by the machines passed in args. 167 func (p *ProvisionerAPI) SetSupportedContainers( 168 args params.MachineContainersParams) (params.ErrorResults, error) { 169 170 result := params.ErrorResults{ 171 Results: make([]params.ErrorResult, len(args.Params)), 172 } 173 for i, arg := range args.Params { 174 canAccess, err := p.getAuthFunc() 175 if err != nil { 176 return result, err 177 } 178 machine, err := p.getMachine(canAccess, arg.MachineTag) 179 if err != nil { 180 result.Results[i].Error = common.ServerError(err) 181 continue 182 } 183 if len(arg.ContainerTypes) == 0 { 184 err = machine.SupportsNoContainers() 185 } else { 186 err = machine.SetSupportedContainers(arg.ContainerTypes) 187 } 188 if err != nil { 189 result.Results[i].Error = common.ServerError(err) 190 } 191 } 192 return result, nil 193 } 194 195 // ContainerManagerConfig returns information from the environment config that is 196 // needed for configuring the container manager. 197 func (p *ProvisionerAPI) ContainerManagerConfig(args params.ContainerManagerConfigParams) (params.ContainerManagerConfig, error) { 198 var result params.ContainerManagerConfig 199 config, err := p.st.EnvironConfig() 200 if err != nil { 201 return result, err 202 } 203 cfg := make(map[string]string) 204 cfg[container.ConfigName] = "juju" 205 switch args.Type { 206 case instance.LXC: 207 if useLxcClone, ok := config.LXCUseClone(); ok { 208 cfg["use-clone"] = fmt.Sprint(useLxcClone) 209 } 210 if useLxcCloneAufs, ok := config.LXCUseCloneAUFS(); ok { 211 cfg["use-aufs"] = fmt.Sprint(useLxcCloneAufs) 212 } 213 } 214 result.ManagerConfig = cfg 215 return result, nil 216 } 217 218 // ContainerConfig returns information from the environment config that is 219 // needed for container cloud-init. 220 func (p *ProvisionerAPI) ContainerConfig() (params.ContainerConfig, error) { 221 result := params.ContainerConfig{} 222 config, err := p.st.EnvironConfig() 223 if err != nil { 224 return result, err 225 } 226 result.ProviderType = config.Type() 227 result.AuthorizedKeys = config.AuthorizedKeys() 228 result.SSLHostnameVerification = config.SSLHostnameVerification() 229 result.Proxy = config.ProxySettings() 230 result.AptProxy = config.AptProxySettings() 231 return result, nil 232 } 233 234 // Status returns the status of each given machine entity. 235 func (p *ProvisionerAPI) Status(args params.Entities) (params.StatusResults, error) { 236 result := params.StatusResults{ 237 Results: make([]params.StatusResult, len(args.Entities)), 238 } 239 canAccess, err := p.getAuthFunc() 240 if err != nil { 241 return result, err 242 } 243 for i, entity := range args.Entities { 244 machine, err := p.getMachine(canAccess, entity.Tag) 245 if err == nil { 246 r := &result.Results[i] 247 r.Status, r.Info, r.Data, err = machine.Status() 248 } 249 result.Results[i].Error = common.ServerError(err) 250 } 251 return result, nil 252 } 253 254 // MachinesWithTransientErrors returns status data for machines with provisioning 255 // errors which are transient. 256 func (p *ProvisionerAPI) MachinesWithTransientErrors() (params.StatusResults, error) { 257 results := params.StatusResults{} 258 canAccessFunc, err := p.getAuthFunc() 259 if err != nil { 260 return results, err 261 } 262 // TODO (wallyworld) - add state.State API for more efficient machines query 263 machines, err := p.st.AllMachines() 264 if err != nil { 265 return results, err 266 } 267 for _, machine := range machines { 268 if !canAccessFunc(machine.Tag()) { 269 continue 270 } 271 if _, provisionedErr := machine.InstanceId(); provisionedErr == nil { 272 // Machine may have been provisioned but machiner hasn't set the 273 // status to Started yet. 274 continue 275 } 276 result := params.StatusResult{} 277 if result.Status, result.Info, result.Data, err = machine.Status(); err != nil { 278 continue 279 } 280 if result.Status != params.StatusError { 281 continue 282 } 283 // Transient errors are marked as such in the status data. 284 if transient, ok := result.Data["transient"].(bool); !ok || !transient { 285 continue 286 } 287 result.Id = machine.Id() 288 result.Life = params.Life(machine.Life().String()) 289 results.Results = append(results.Results, result) 290 } 291 return results, nil 292 } 293 294 // Series returns the deployed series for each given machine entity. 295 func (p *ProvisionerAPI) Series(args params.Entities) (params.StringResults, error) { 296 result := params.StringResults{ 297 Results: make([]params.StringResult, len(args.Entities)), 298 } 299 canAccess, err := p.getAuthFunc() 300 if err != nil { 301 return result, err 302 } 303 for i, entity := range args.Entities { 304 machine, err := p.getMachine(canAccess, entity.Tag) 305 if err == nil { 306 result.Results[i].Result = machine.Series() 307 } 308 result.Results[i].Error = common.ServerError(err) 309 } 310 return result, nil 311 } 312 313 // ProvisioningInfo returns the provisioning information for each given machine entity. 314 func (p *ProvisionerAPI) ProvisioningInfo(args params.Entities) (params.ProvisioningInfoResults, error) { 315 result := params.ProvisioningInfoResults{ 316 Results: make([]params.ProvisioningInfoResult, len(args.Entities)), 317 } 318 canAccess, err := p.getAuthFunc() 319 if err != nil { 320 return result, err 321 } 322 for i, entity := range args.Entities { 323 machine, err := p.getMachine(canAccess, entity.Tag) 324 if err == nil { 325 result.Results[i].Result, err = getProvisioningInfo(machine) 326 } 327 result.Results[i].Error = common.ServerError(err) 328 } 329 return result, nil 330 } 331 332 func getProvisioningInfo(m *state.Machine) (*params.ProvisioningInfo, error) { 333 cons, err := m.Constraints() 334 if err != nil { 335 return nil, err 336 } 337 // TODO(dimitern) For now, since network names and 338 // provider ids are the same, we return what we got 339 // from state. In the future, when networks can be 340 // added before provisioning, we should convert both 341 // slices from juju network names to provider-specific 342 // ids before returning them. 343 networks, err := m.RequestedNetworks() 344 if err != nil { 345 return nil, err 346 } 347 return ¶ms.ProvisioningInfo{ 348 Constraints: cons, 349 Series: m.Series(), 350 Placement: m.Placement(), 351 Networks: networks, 352 }, nil 353 } 354 355 // DistributionGroup returns, for each given machine entity, 356 // a slice of instance.Ids that belong to the same distribution 357 // group as that machine. This information may be used to 358 // distribute instances for high availability. 359 func (p *ProvisionerAPI) DistributionGroup(args params.Entities) (params.DistributionGroupResults, error) { 360 result := params.DistributionGroupResults{ 361 Results: make([]params.DistributionGroupResult, len(args.Entities)), 362 } 363 canAccess, err := p.getAuthFunc() 364 if err != nil { 365 return result, err 366 } 367 for i, entity := range args.Entities { 368 machine, err := p.getMachine(canAccess, entity.Tag) 369 if err == nil { 370 // If the machine is an environment manager, return 371 // environment manager instances. Otherwise, return 372 // instances with services in common with the machine 373 // being provisioned. 374 if machine.IsManager() { 375 result.Results[i].Result, err = environManagerInstances(p.st) 376 } else { 377 result.Results[i].Result, err = commonServiceInstances(p.st, machine) 378 } 379 } 380 result.Results[i].Error = common.ServerError(err) 381 } 382 return result, nil 383 } 384 385 // environManagerInstances returns all environ manager instances. 386 func environManagerInstances(st *state.State) ([]instance.Id, error) { 387 info, err := st.StateServerInfo() 388 if err != nil { 389 return nil, err 390 } 391 instances := make([]instance.Id, 0, len(info.MachineIds)) 392 for _, id := range info.MachineIds { 393 machine, err := st.Machine(id) 394 if err != nil { 395 return nil, err 396 } 397 instanceId, err := machine.InstanceId() 398 if err == nil { 399 instances = append(instances, instanceId) 400 } else if !state.IsNotProvisionedError(err) { 401 return nil, err 402 } 403 } 404 return instances, nil 405 } 406 407 // commonServiceInstances returns instances with 408 // services in common with the specified machine. 409 func commonServiceInstances(st *state.State, m *state.Machine) ([]instance.Id, error) { 410 units, err := m.Units() 411 if err != nil { 412 return nil, err 413 } 414 var instanceIdSet set.Strings 415 for _, unit := range units { 416 if !unit.IsPrincipal() { 417 continue 418 } 419 instanceIds, err := state.ServiceInstances(st, unit.ServiceName()) 420 if err != nil { 421 return nil, err 422 } 423 for _, instanceId := range instanceIds { 424 instanceIdSet.Add(string(instanceId)) 425 } 426 } 427 instanceIds := make([]instance.Id, instanceIdSet.Size()) 428 // Sort values to simplify testing. 429 for i, instanceId := range instanceIdSet.SortedValues() { 430 instanceIds[i] = instance.Id(instanceId) 431 } 432 return instanceIds, nil 433 } 434 435 // Constraints returns the constraints for each given machine entity. 436 func (p *ProvisionerAPI) Constraints(args params.Entities) (params.ConstraintsResults, error) { 437 result := params.ConstraintsResults{ 438 Results: make([]params.ConstraintsResult, len(args.Entities)), 439 } 440 canAccess, err := p.getAuthFunc() 441 if err != nil { 442 return result, err 443 } 444 for i, entity := range args.Entities { 445 machine, err := p.getMachine(canAccess, entity.Tag) 446 if err == nil { 447 var cons constraints.Value 448 cons, err = machine.Constraints() 449 if err == nil { 450 result.Results[i].Constraints = cons 451 } 452 } 453 result.Results[i].Error = common.ServerError(err) 454 } 455 return result, nil 456 } 457 458 func networkParamsToStateParams(networks []params.Network, ifaces []params.NetworkInterface) ( 459 []state.NetworkInfo, []state.NetworkInterfaceInfo, error, 460 ) { 461 stateNetworks := make([]state.NetworkInfo, len(networks)) 462 for i, network := range networks { 463 _, networkName, err := names.ParseTag(network.Tag, names.NetworkTagKind) 464 if err != nil { 465 return nil, nil, err 466 } 467 stateNetworks[i] = state.NetworkInfo{ 468 Name: networkName, 469 ProviderId: network.ProviderId, 470 CIDR: network.CIDR, 471 VLANTag: network.VLANTag, 472 } 473 } 474 stateInterfaces := make([]state.NetworkInterfaceInfo, len(ifaces)) 475 for i, iface := range ifaces { 476 _, networkName, err := names.ParseTag(iface.NetworkTag, names.NetworkTagKind) 477 if err != nil { 478 return nil, nil, err 479 } 480 stateInterfaces[i] = state.NetworkInterfaceInfo{ 481 MACAddress: iface.MACAddress, 482 NetworkName: networkName, 483 InterfaceName: iface.InterfaceName, 484 IsVirtual: iface.IsVirtual, 485 } 486 } 487 return stateNetworks, stateInterfaces, nil 488 } 489 490 // RequestedNetworks returns the requested networks for each given 491 // machine entity. Each entry in both lists is returned with its 492 // provider specific id. 493 func (p *ProvisionerAPI) RequestedNetworks(args params.Entities) (params.RequestedNetworksResults, error) { 494 result := params.RequestedNetworksResults{ 495 Results: make([]params.RequestedNetworkResult, len(args.Entities)), 496 } 497 canAccess, err := p.getAuthFunc() 498 if err != nil { 499 return result, err 500 } 501 for i, entity := range args.Entities { 502 machine, err := p.getMachine(canAccess, entity.Tag) 503 if err == nil { 504 var networks []string 505 networks, err = machine.RequestedNetworks() 506 if err == nil { 507 // TODO(dimitern) For now, since network names and 508 // provider ids are the same, we return what we got 509 // from state. In the future, when networks can be 510 // added before provisioning, we should convert both 511 // slices from juju network names to provider-specific 512 // ids before returning them. 513 result.Results[i].Networks = networks 514 } 515 } 516 result.Results[i].Error = common.ServerError(err) 517 } 518 return result, nil 519 } 520 521 // SetProvisioned sets the provider specific instance id, nonce and 522 // metadata for each given machine. Once set, the instance id cannot 523 // be changed. 524 // 525 // TODO(dimitern) This is not used anymore (as of 1.19.0) and is 526 // retained only for backwards-compatibility. It should be removed as 527 // deprecated. SetInstanceInfo is used instead. 528 func (p *ProvisionerAPI) SetProvisioned(args params.SetProvisioned) (params.ErrorResults, error) { 529 result := params.ErrorResults{ 530 Results: make([]params.ErrorResult, len(args.Machines)), 531 } 532 canAccess, err := p.getAuthFunc() 533 if err != nil { 534 return result, err 535 } 536 for i, arg := range args.Machines { 537 machine, err := p.getMachine(canAccess, arg.Tag) 538 if err == nil { 539 err = machine.SetProvisioned(arg.InstanceId, arg.Nonce, arg.Characteristics) 540 } 541 result.Results[i].Error = common.ServerError(err) 542 } 543 return result, nil 544 } 545 546 // SetInstanceInfo sets the provider specific machine id, nonce, 547 // metadata and network info for each given machine. Once set, the 548 // instance id cannot be changed. 549 func (p *ProvisionerAPI) SetInstanceInfo(args params.InstancesInfo) (params.ErrorResults, error) { 550 result := params.ErrorResults{ 551 Results: make([]params.ErrorResult, len(args.Machines)), 552 } 553 canAccess, err := p.getAuthFunc() 554 if err != nil { 555 return result, err 556 } 557 for i, arg := range args.Machines { 558 machine, err := p.getMachine(canAccess, arg.Tag) 559 if err == nil { 560 var networks []state.NetworkInfo 561 var interfaces []state.NetworkInterfaceInfo 562 networks, interfaces, err = networkParamsToStateParams(arg.Networks, arg.Interfaces) 563 if err == nil { 564 err = machine.SetInstanceInfo( 565 arg.InstanceId, arg.Nonce, arg.Characteristics, 566 networks, interfaces) 567 } 568 if err != nil { 569 // Give the user more context about the error. 570 err = fmt.Errorf("aborted instance %q: %v", arg.InstanceId, err) 571 } 572 } 573 result.Results[i].Error = common.ServerError(err) 574 } 575 return result, nil 576 } 577 578 // WatchMachineErrorRetry returns a NotifyWatcher that notifies when 579 // the provisioner should retry provisioning machines with transient errors. 580 func (p *ProvisionerAPI) WatchMachineErrorRetry() (params.NotifyWatchResult, error) { 581 result := params.NotifyWatchResult{} 582 canWatch, err := p.getCanWatchMachines() 583 if err != nil { 584 return params.NotifyWatchResult{}, err 585 } 586 if !canWatch("") { 587 return result, common.ErrPerm 588 } 589 watch := newWatchMachineErrorRetry() 590 // Consume any initial event and forward it to the result. 591 if _, ok := <-watch.Changes(); ok { 592 result.NotifyWatcherId = p.resources.Register(watch) 593 } else { 594 return result, watcher.MustErr(watch) 595 } 596 return result, nil 597 }