github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/juju/api.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package juju 5 6 import ( 7 "time" 8 9 "github.com/juju/errors" 10 "github.com/juju/loggo" 11 "gopkg.in/juju/names.v2" 12 13 "github.com/juju/juju/api" 14 "github.com/juju/juju/jujuclient" 15 "github.com/juju/juju/network" 16 ) 17 18 var logger = loggo.GetLogger("juju.juju") 19 20 // The following are variables so that they can be 21 // changed by tests. 22 var ( 23 providerConnectDelay = 2 * time.Second 24 ) 25 26 type apiStateCachedInfo struct { 27 api.Connection 28 // If cachedInfo is non-nil, it indicates that the info has been 29 // newly retrieved, and should be cached in the config store. 30 cachedInfo *api.Info 31 } 32 33 // NewAPIConnectionParams contains the parameters for creating a new Juju API 34 // connection. 35 type NewAPIConnectionParams struct { 36 // ControllerName is the name of the controller to connect to. 37 ControllerName string 38 39 // Store is the jujuclient.ClientStore from which the controller's 40 // details will be fetched, and updated on address changes. 41 Store jujuclient.ClientStore 42 43 // OpenAPI is the function that will be used to open API connections. 44 OpenAPI api.OpenFunc 45 46 // DialOpts contains the options used to dial the API connection. 47 DialOpts api.DialOpts 48 49 // AccountDetails contains the account details to use for logging 50 // in to the Juju API. If this is nil, then no login will take 51 // place. If AccountDetails.Password and AccountDetails.Macaroon 52 // are zero, the login will be as an external user. 53 AccountDetails *jujuclient.AccountDetails 54 55 // ModelUUID is an optional model UUID. If specified, the API connection 56 // will be scoped to the model with that UUID; otherwise it will be 57 // scoped to the controller. 58 ModelUUID string 59 } 60 61 // NewAPIConnection returns an api.Connection to the specified Juju controller, 62 // with specified account credentials, optionally scoped to the specified model 63 // name. 64 func NewAPIConnection(args NewAPIConnectionParams) (api.Connection, error) { 65 apiInfo, controller, err := connectionInfo(args) 66 if err != nil { 67 return nil, errors.Annotatef(err, "cannot work out how to connect") 68 } 69 if len(apiInfo.Addrs) == 0 { 70 return nil, errors.New("no API addresses") 71 } 72 logger.Infof("connecting to API addresses: %v", apiInfo.Addrs) 73 st, err := args.OpenAPI(apiInfo, args.DialOpts) 74 if err != nil { 75 redirErr, ok := errors.Cause(err).(*api.RedirectError) 76 if !ok { 77 return nil, errors.Trace(err) 78 } 79 // We've been told to connect to a different API server, 80 // so do so. Note that we don't copy the account details 81 // because the account on the redirected server may well 82 // be different - we'll use macaroon authentication 83 // directly without sending account details. 84 // Copy the API info because it's possible that the 85 // apiConfigConnect is still using it concurrently. 86 apiInfo = &api.Info{ 87 ModelTag: apiInfo.ModelTag, 88 Addrs: network.HostPortsToStrings(usableHostPorts(redirErr.Servers)), 89 CACert: redirErr.CACert, 90 } 91 st, err = args.OpenAPI(apiInfo, args.DialOpts) 92 if err != nil { 93 return nil, errors.Annotatef(err, "cannot connect to redirected address") 94 } 95 // TODO(rog) update cached model addresses. 96 // TODO(rog) should we do something with the logged-in username? 97 return st, nil 98 } 99 addrConnectedTo, err := serverAddress(st.Addr()) 100 if err != nil { 101 return nil, errors.Trace(err) 102 } 103 // Update API addresses if they've changed. Error is non-fatal. 104 // Note that in the redirection case, we won't update the addresses 105 // of the controller we first connected to. This shouldn't be 106 // a problem in practice because the intended scenario for 107 // controllers that redirect involves them having well known 108 // public addresses that won't change over time. 109 hostPorts := st.APIHostPorts() 110 agentVersion := "" 111 if v, ok := st.ServerVersion(); ok { 112 agentVersion = v.String() 113 } 114 params := UpdateControllerParams{ 115 AgentVersion: agentVersion, 116 AddrConnectedTo: []network.HostPort{addrConnectedTo}, 117 CurrentHostPorts: hostPorts, 118 } 119 err = updateControllerDetailsFromLogin(args.Store, args.ControllerName, controller, params) 120 if err != nil { 121 logger.Errorf("cannot cache API addresses: %v", err) 122 } 123 124 // Process the account details obtained from login. 125 var accountDetails *jujuclient.AccountDetails 126 user, ok := st.AuthTag().(names.UserTag) 127 if !apiInfo.SkipLogin { 128 if ok { 129 if accountDetails, err = args.Store.AccountDetails(args.ControllerName); err != nil { 130 if !errors.IsNotFound(err) { 131 logger.Errorf("cannot load local account information: %v", err) 132 } 133 } else { 134 accountDetails.LastKnownAccess = st.ControllerAccess() 135 } 136 } 137 if ok && !user.IsLocal() && apiInfo.Tag == nil { 138 // We used macaroon auth to login; save the username 139 // that we've logged in as. 140 accountDetails = &jujuclient.AccountDetails{ 141 User: user.Id(), 142 LastKnownAccess: st.ControllerAccess(), 143 } 144 } else if apiInfo.Tag == nil { 145 logger.Errorf("unexpected logged-in username %v", st.AuthTag()) 146 } 147 } 148 if accountDetails != nil { 149 if err := args.Store.UpdateAccount(args.ControllerName, *accountDetails); err != nil { 150 logger.Errorf("cannot update account information: %v", err) 151 } 152 } 153 return st, nil 154 } 155 156 // connectionInfo returns connection information suitable for 157 // connecting to the controller and model specified in the given 158 // parameters. If there are no addresses known for the controller, 159 // it may return a *api.Info with no APIEndpoints, but all other 160 // information will be populated. 161 func connectionInfo(args NewAPIConnectionParams) (*api.Info, *jujuclient.ControllerDetails, error) { 162 controller, err := args.Store.ControllerByName(args.ControllerName) 163 if err != nil { 164 return nil, nil, errors.Annotate(err, "cannot get controller details") 165 } 166 apiInfo := &api.Info{ 167 Addrs: controller.APIEndpoints, 168 CACert: controller.CACert, 169 } 170 if args.ModelUUID != "" { 171 apiInfo.ModelTag = names.NewModelTag(args.ModelUUID) 172 } 173 if args.AccountDetails == nil { 174 apiInfo.SkipLogin = true 175 return apiInfo, controller, nil 176 } 177 account := args.AccountDetails 178 if account.User != "" { 179 userTag := names.NewUserTag(account.User) 180 if userTag.IsLocal() { 181 apiInfo.Tag = userTag 182 } 183 } 184 if args.AccountDetails.Password != "" { 185 // If a password is available, we always use that. 186 // If no password is recorded, we'll attempt to 187 // authenticate using macaroons. 188 apiInfo.Password = account.Password 189 } 190 return apiInfo, controller, nil 191 } 192 193 func isAPIError(err error) bool { 194 type errorCoder interface { 195 ErrorCode() string 196 } 197 _, ok := errors.Cause(err).(errorCoder) 198 return ok 199 } 200 201 var resolveOrDropHostnames = network.ResolveOrDropHostnames 202 203 // PrepareEndpointsForCaching performs the necessary operations on the 204 // given API hostPorts so they are suitable for saving into the 205 // controller.yaml file, taking into account the addrConnectedTo 206 // and the existing config store info: 207 // 208 // 1. Collapses hostPorts into a single slice. 209 // 2. Filters out machine-local and link-local addresses. 210 // 3. Removes any duplicates 211 // 4. Call network.SortHostPorts() on the list. 212 // 5. Puts the addrConnectedTo on top. 213 // 6. Compares the result against info.APIEndpoint.Hostnames. 214 // 7. If the addresses differ, call network.ResolveOrDropHostnames() 215 // on the list and perform all steps again from step 1. 216 // 8. Compare the list of resolved addresses against the cached info 217 // APIEndpoint.Addresses, and if changed return both addresses and 218 // hostnames as strings (so they can be cached on APIEndpoint) and 219 // set haveChanged to true. 220 // 9. If the hostnames haven't changed, return two empty slices and set 221 // haveChanged to false. No DNS resolution is performed to save time. 222 // 223 // This is used right after bootstrap to saved the initial API 224 // endpoints, as well as on each CLI connection to verify if the 225 // saved endpoints need updating. 226 // 227 // TODO(rogpeppe) this function mixes too many concerns - the 228 // logic is difficult to follow and has non-obvious properties. 229 func PrepareEndpointsForCaching( 230 controllerDetails jujuclient.ControllerDetails, 231 hostPorts [][]network.HostPort, 232 addrConnectedTo ...network.HostPort, 233 ) (addrs, unresolvedAddrs []string, haveChanged bool) { 234 processHostPorts := func(allHostPorts [][]network.HostPort) []network.HostPort { 235 uniqueHPs := usableHostPorts(allHostPorts) 236 network.SortHostPorts(uniqueHPs) 237 for _, addr := range addrConnectedTo { 238 uniqueHPs = network.EnsureFirstHostPort(addr, uniqueHPs) 239 } 240 return uniqueHPs 241 } 242 243 apiHosts := processHostPorts(hostPorts) 244 hostsStrings := network.HostPortsToStrings(apiHosts) 245 needResolving := false 246 247 // Verify if the unresolved addresses have changed. 248 if len(apiHosts) > 0 && len(controllerDetails.UnresolvedAPIEndpoints) > 0 { 249 if addrsChanged(hostsStrings, controllerDetails.UnresolvedAPIEndpoints) { 250 logger.Debugf( 251 "API hostnames changed from %v to %v - resolving hostnames", 252 controllerDetails.UnresolvedAPIEndpoints, hostsStrings, 253 ) 254 needResolving = true 255 } 256 } else if len(apiHosts) > 0 { 257 // No cached hostnames, most likely right after bootstrap. 258 logger.Debugf("API hostnames %v - resolving hostnames", hostsStrings) 259 needResolving = true 260 } 261 if !needResolving { 262 // We're done - nothing changed. 263 logger.Debugf("API hostnames unchanged - not resolving") 264 return nil, nil, false 265 } 266 // Perform DNS resolution and check against APIEndpoints.Addresses. 267 resolved := resolveOrDropHostnames(apiHosts) 268 apiAddrs := processHostPorts([][]network.HostPort{resolved}) 269 addrsStrings := network.HostPortsToStrings(apiAddrs) 270 if len(apiAddrs) > 0 && len(controllerDetails.APIEndpoints) > 0 { 271 if addrsChanged(addrsStrings, controllerDetails.APIEndpoints) { 272 logger.Infof( 273 "API addresses changed from %v to %v", 274 controllerDetails.APIEndpoints, addrsStrings, 275 ) 276 return addrsStrings, hostsStrings, true 277 } 278 } else if len(apiAddrs) > 0 { 279 // No cached addresses, most likely right after bootstrap. 280 logger.Infof("new API addresses to cache %v", addrsStrings) 281 return addrsStrings, hostsStrings, true 282 } 283 // No changes. 284 logger.Debugf("API addresses unchanged") 285 return nil, nil, false 286 } 287 288 // usableHostPorts returns hps with unusable and non-unique 289 // host-ports filtered out. 290 func usableHostPorts(hps [][]network.HostPort) []network.HostPort { 291 collapsed := network.CollapseHostPorts(hps) 292 usable := network.FilterUnusableHostPorts(collapsed) 293 unique := network.DropDuplicatedHostPorts(usable) 294 return unique 295 } 296 297 // addrsChanged returns true iff the two 298 // slices are not equal. Order is important. 299 func addrsChanged(a, b []string) bool { 300 if len(a) != len(b) { 301 return true 302 } 303 for i := range a { 304 if a[i] != b[i] { 305 return true 306 } 307 } 308 return false 309 } 310 311 // UpdateControllerParams holds values used to update a controller details 312 // after bootstrap or a login operation. 313 type UpdateControllerParams struct { 314 // AgentVersion is the version of the controller agent. 315 AgentVersion string 316 317 // CurrentHostPorts are the available api addresses. 318 CurrentHostPorts [][]network.HostPort 319 320 // AddrConnectedTo are the previously known api addresses. 321 AddrConnectedTo []network.HostPort 322 323 // ModelCount (when set) is the number of models visible to the user. 324 ModelCount *int 325 326 // ControllerMachineCount (when set) is the total number of controller machines in the environment. 327 ControllerMachineCount *int 328 329 // MachineCount (when set) is the total number of machines in the models. 330 MachineCount *int 331 } 332 333 // UpdateControllerDetailsFromLogin writes any new api addresses and other relevant details 334 // to the client controller file. 335 // Controller may be specified by a UUID or name, and must already exist. 336 func UpdateControllerDetailsFromLogin( 337 store jujuclient.ControllerStore, controllerName string, 338 params UpdateControllerParams, 339 ) error { 340 controllerDetails, err := store.ControllerByName(controllerName) 341 if err != nil { 342 return errors.Trace(err) 343 } 344 return updateControllerDetailsFromLogin(store, controllerName, controllerDetails, params) 345 } 346 347 func updateControllerDetailsFromLogin( 348 store jujuclient.ControllerStore, 349 controllerName string, controllerDetails *jujuclient.ControllerDetails, 350 params UpdateControllerParams, 351 ) error { 352 // Get the new endpoint addresses. 353 addrs, unresolvedAddrs, addrsChanged := PrepareEndpointsForCaching(*controllerDetails, params.CurrentHostPorts, params.AddrConnectedTo...) 354 agentChanged := params.AgentVersion != controllerDetails.AgentVersion 355 if !addrsChanged && !agentChanged && params.ModelCount == nil && params.MachineCount == nil && params.ControllerMachineCount == nil { 356 return nil 357 } 358 359 // Write the new controller data. 360 if addrsChanged { 361 controllerDetails.APIEndpoints = addrs 362 controllerDetails.UnresolvedAPIEndpoints = unresolvedAddrs 363 } 364 if agentChanged { 365 controllerDetails.AgentVersion = params.AgentVersion 366 } 367 if params.ModelCount != nil { 368 controllerDetails.ModelCount = params.ModelCount 369 } 370 if params.MachineCount != nil { 371 controllerDetails.MachineCount = params.MachineCount 372 } 373 if params.ControllerMachineCount != nil { 374 controllerDetails.ControllerMachineCount = *params.ControllerMachineCount 375 } 376 err := store.UpdateController(controllerName, *controllerDetails) 377 return errors.Trace(err) 378 } 379 380 // serverAddress returns the given string address:port as network.HostPort. 381 // 382 // TODO(axw) fix the tests that pass invalid addresses, and drop this. 383 var serverAddress = func(hostPort string) (network.HostPort, error) { 384 addrConnectedTo, err := network.ParseHostPorts(hostPort) 385 if err != nil { 386 // Should never happen, since we've just connected with it. 387 return network.HostPort{}, errors.Annotatef(err, "invalid API address %q", hostPort) 388 } 389 return addrConnectedTo[0], nil 390 }