github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/api/controller/controller.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package controller 5 6 import ( 7 "encoding/json" 8 9 "github.com/juju/errors" 10 "gopkg.in/juju/names.v2" 11 "gopkg.in/macaroon.v1" 12 13 "github.com/juju/juju/api" 14 "github.com/juju/juju/api/base" 15 "github.com/juju/juju/api/common" 16 "github.com/juju/juju/api/common/cloudspec" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/permission" 20 ) 21 22 // Client provides methods that the Juju client command uses to interact 23 // with the Juju controller. 24 type Client struct { 25 base.ClientFacade 26 facade base.FacadeCaller 27 *common.ControllerConfigAPI 28 *cloudspec.CloudSpecAPI 29 } 30 31 // NewClient creates a new `Client` based on an existing authenticated API 32 // connection. 33 func NewClient(st base.APICallCloser) *Client { 34 frontend, backend := base.NewClientFacade(st, "Controller") 35 return &Client{ 36 ClientFacade: frontend, 37 facade: backend, 38 ControllerConfigAPI: common.NewControllerConfig(backend), 39 CloudSpecAPI: cloudspec.NewCloudSpecAPI(backend), 40 } 41 } 42 43 // AllModels allows controller administrators to get the list of all the 44 // models in the controller. 45 func (c *Client) AllModels() ([]base.UserModel, error) { 46 var models params.UserModelList 47 err := c.facade.FacadeCall("AllModels", nil, &models) 48 if err != nil { 49 return nil, errors.Trace(err) 50 } 51 result := make([]base.UserModel, len(models.UserModels)) 52 for i, model := range models.UserModels { 53 owner, err := names.ParseUserTag(model.OwnerTag) 54 if err != nil { 55 return nil, errors.Annotatef(err, "OwnerTag %q at position %d", model.OwnerTag, i) 56 } 57 result[i] = base.UserModel{ 58 Name: model.Name, 59 UUID: model.UUID, 60 Owner: owner.Canonical(), 61 LastConnection: model.LastConnection, 62 } 63 } 64 return result, nil 65 } 66 67 // ModelConfig returns all model settings for the 68 // controller model. 69 func (c *Client) ModelConfig() (map[string]interface{}, error) { 70 result := params.ModelConfigResults{} 71 err := c.facade.FacadeCall("ModelConfig", nil, &result) 72 values := make(map[string]interface{}) 73 for name, val := range result.Config { 74 values[name] = val.Value 75 } 76 return values, err 77 } 78 79 // HostedConfig contains the model config and the cloud spec for that 80 // model such that direct access to the provider can be used. 81 type HostedConfig struct { 82 Name string 83 Owner names.UserTag 84 Config map[string]interface{} 85 CloudSpec environs.CloudSpec 86 Error error 87 } 88 89 // HostedModelsConfig returns all model settings for the 90 // controller model. 91 func (c *Client) HostedModelConfigs() ([]HostedConfig, error) { 92 result := params.HostedModelConfigsResults{} 93 err := c.facade.FacadeCall("HostedModelConfigs", nil, &result) 94 if err != nil { 95 return nil, errors.Trace(err) 96 } 97 // If we get to here, we have some values. Each value may or 98 // may not have an error, but it should at least have a name 99 // and owner. 100 hostedConfigs := make([]HostedConfig, len(result.Models)) 101 for i, modelConfig := range result.Models { 102 hostedConfigs[i].Name = modelConfig.Name 103 tag, err := names.ParseUserTag(modelConfig.OwnerTag) 104 if err != nil { 105 hostedConfigs[i].Error = errors.Trace(err) 106 continue 107 } 108 hostedConfigs[i].Owner = tag 109 if modelConfig.Error != nil { 110 hostedConfigs[i].Error = errors.Trace(modelConfig.Error) 111 continue 112 } 113 hostedConfigs[i].Config = modelConfig.Config 114 spec, err := c.MakeCloudSpec(modelConfig.CloudSpec) 115 if err != nil { 116 hostedConfigs[i].Error = errors.Trace(err) 117 continue 118 } 119 hostedConfigs[i].CloudSpec = spec 120 } 121 return hostedConfigs, err 122 } 123 124 // DestroyController puts the controller model into a "dying" state, 125 // and removes all non-manager machine instances. Underlying DestroyModel 126 // calls will fail if there are any manually-provisioned non-manager machines 127 // in state. 128 func (c *Client) DestroyController(destroyModels bool) error { 129 args := params.DestroyControllerArgs{ 130 DestroyModels: destroyModels, 131 } 132 return c.facade.FacadeCall("DestroyController", args, nil) 133 } 134 135 // ListBlockedModels returns a list of all models within the controller 136 // which have at least one block in place. 137 func (c *Client) ListBlockedModels() ([]params.ModelBlockInfo, error) { 138 result := params.ModelBlockInfoList{} 139 err := c.facade.FacadeCall("ListBlockedModels", nil, &result) 140 return result.Models, err 141 } 142 143 // RemoveBlocks removes all the blocks in the controller. 144 func (c *Client) RemoveBlocks() error { 145 args := params.RemoveBlocksArgs{All: true} 146 return c.facade.FacadeCall("RemoveBlocks", args, nil) 147 } 148 149 // WatchAllModels returns an AllWatcher, from which you can request 150 // the Next collection of Deltas (for all models). 151 func (c *Client) WatchAllModels() (*api.AllWatcher, error) { 152 var info params.AllWatcherId 153 if err := c.facade.FacadeCall("WatchAllModels", nil, &info); err != nil { 154 return nil, err 155 } 156 return api.NewAllModelWatcher(c.facade.RawAPICaller(), &info.AllWatcherId), nil 157 } 158 159 // ModelStatus returns a status summary for each model tag passed in. 160 func (c *Client) ModelStatus(tags ...names.ModelTag) ([]base.ModelStatus, error) { 161 result := params.ModelStatusResults{} 162 models := make([]params.Entity, len(tags)) 163 for i, tag := range tags { 164 models[i] = params.Entity{Tag: tag.String()} 165 } 166 req := params.Entities{ 167 Entities: models, 168 } 169 if err := c.facade.FacadeCall("ModelStatus", req, &result); err != nil { 170 return nil, err 171 } 172 173 results := make([]base.ModelStatus, len(result.Results)) 174 for i, r := range result.Results { 175 model, err := names.ParseModelTag(r.ModelTag) 176 if err != nil { 177 return nil, errors.Annotatef(err, "ModelTag %q at position %d", r.ModelTag, i) 178 } 179 owner, err := names.ParseUserTag(r.OwnerTag) 180 if err != nil { 181 return nil, errors.Annotatef(err, "OwnerTag %q at position %d", r.OwnerTag, i) 182 } 183 184 results[i] = base.ModelStatus{ 185 UUID: model.Id(), 186 Life: string(r.Life), 187 Owner: owner.Canonical(), 188 HostedMachineCount: r.HostedMachineCount, 189 ServiceCount: r.ApplicationCount, 190 TotalMachineCount: len(r.Machines), 191 } 192 results[i].Machines = make([]base.Machine, len(r.Machines)) 193 for j, mm := range r.Machines { 194 if mm.Hardware != nil && mm.Hardware.Cores != nil { 195 results[i].CoreCount += int(*mm.Hardware.Cores) 196 } 197 results[i].Machines[j] = base.Machine{ 198 Id: mm.Id, 199 InstanceId: mm.InstanceId, 200 HasVote: mm.HasVote, 201 WantsVote: mm.WantsVote, 202 Status: mm.Status, 203 } 204 } 205 } 206 return results, nil 207 } 208 209 // GrantController grants a user access to the controller. 210 func (c *Client) GrantController(user, access string) error { 211 return c.modifyControllerUser(params.GrantControllerAccess, user, access) 212 } 213 214 // RevokeController revokes a user's access to the controller. 215 func (c *Client) RevokeController(user, access string) error { 216 return c.modifyControllerUser(params.RevokeControllerAccess, user, access) 217 } 218 219 func (c *Client) modifyControllerUser(action params.ControllerAction, user, access string) error { 220 var args params.ModifyControllerAccessRequest 221 222 if !names.IsValidUser(user) { 223 return errors.Errorf("invalid username: %q", user) 224 } 225 userTag := names.NewUserTag(user) 226 227 args.Changes = []params.ModifyControllerAccess{{ 228 UserTag: userTag.String(), 229 Action: action, 230 Access: access, 231 }} 232 233 var result params.ErrorResults 234 err := c.facade.FacadeCall("ModifyControllerAccess", args, &result) 235 if err != nil { 236 return errors.Trace(err) 237 } 238 if len(result.Results) != len(args.Changes) { 239 return errors.Errorf("expected %d results, got %d", len(args.Changes), len(result.Results)) 240 } 241 242 return result.Combine() 243 } 244 245 // GetControllerAccess returns the access level the user has on the controller. 246 func (c *Client) GetControllerAccess(user string) (permission.Access, error) { 247 if !names.IsValidUser(user) { 248 return "", errors.Errorf("invalid username: %q", user) 249 } 250 entities := params.Entities{Entities: []params.Entity{{names.NewUserTag(user).String()}}} 251 var results params.UserAccessResults 252 err := c.facade.FacadeCall("GetControllerAccess", entities, &results) 253 if err != nil { 254 return "", errors.Trace(err) 255 } 256 if len(results.Results) != 1 { 257 return "", errors.Errorf("expected 1 result, got %d", len(results.Results)) 258 } 259 if err := results.Results[0].Error; err != nil { 260 return "", errors.Trace(err) 261 } 262 return permission.Access(results.Results[0].Result.Access), nil 263 } 264 265 // MigrationSpec holds the details required to start the migration of 266 // a single model. 267 type MigrationSpec struct { 268 ModelUUID string 269 TargetControllerUUID string 270 TargetAddrs []string 271 TargetCACert string 272 TargetUser string 273 TargetPassword string 274 TargetMacaroons []macaroon.Slice 275 ExternalControl bool 276 SkipInitialPrechecks bool 277 } 278 279 // Validate performs sanity checks on the migration configuration it 280 // holds. 281 func (s *MigrationSpec) Validate() error { 282 if !names.IsValidModel(s.ModelUUID) { 283 return errors.NotValidf("model UUID") 284 } 285 if !names.IsValidModel(s.TargetControllerUUID) { 286 return errors.NotValidf("controller UUID") 287 } 288 if len(s.TargetAddrs) < 1 { 289 return errors.NotValidf("empty target API addresses") 290 } 291 if s.TargetCACert == "" { 292 return errors.NotValidf("empty target CA cert") 293 } 294 if !names.IsValidUser(s.TargetUser) { 295 return errors.NotValidf("target user") 296 } 297 if s.TargetPassword == "" && len(s.TargetMacaroons) == 0 { 298 return errors.NotValidf("missing authentication secrets") 299 } 300 return nil 301 } 302 303 // InitiateMigration attempts to start a migration for the specified 304 // model, returning the migration's ID. 305 // 306 // The API server supports starting multiple migrations in one request 307 // but we don't need that at the client side yet (and may never) so 308 // this call just supports starting one migration at a time. 309 func (c *Client) InitiateMigration(spec MigrationSpec) (string, error) { 310 if err := spec.Validate(); err != nil { 311 return "", errors.Trace(err) 312 } 313 314 macsJSON, err := macaroonsToJSON(spec.TargetMacaroons) 315 if err != nil { 316 return "", errors.Trace(err) 317 } 318 319 args := params.InitiateMigrationArgs{ 320 Specs: []params.MigrationSpec{{ 321 ModelTag: names.NewModelTag(spec.ModelUUID).String(), 322 TargetInfo: params.MigrationTargetInfo{ 323 ControllerTag: names.NewControllerTag(spec.TargetControllerUUID).String(), 324 Addrs: spec.TargetAddrs, 325 CACert: spec.TargetCACert, 326 AuthTag: names.NewUserTag(spec.TargetUser).String(), 327 Password: spec.TargetPassword, 328 Macaroons: string(macsJSON), 329 }, 330 ExternalControl: spec.ExternalControl, 331 SkipInitialPrechecks: spec.SkipInitialPrechecks, 332 }}, 333 } 334 response := params.InitiateMigrationResults{} 335 if err := c.facade.FacadeCall("InitiateMigration", args, &response); err != nil { 336 return "", errors.Trace(err) 337 } 338 if len(response.Results) != 1 { 339 return "", errors.New("unexpected number of results returned") 340 } 341 result := response.Results[0] 342 if result.Error != nil { 343 return "", errors.Trace(result.Error) 344 } 345 return result.MigrationId, nil 346 } 347 348 func macaroonsToJSON(macs []macaroon.Slice) (string, error) { 349 if len(macs) == 0 { 350 return "", nil 351 } 352 out, err := json.Marshal(macs) 353 if err != nil { 354 return "", errors.Annotate(err, "marshalling macaroons") 355 } 356 return string(out), nil 357 }