github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/api/state.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package api
     5  
     6  import (
     7  	"net"
     8  	"net/url"
     9  	"strconv"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/version"
    13  	"gopkg.in/juju/names.v2"
    14  	"gopkg.in/macaroon-bakery.v1/httpbakery"
    15  	"gopkg.in/macaroon.v1"
    16  
    17  	"github.com/juju/juju/api/base"
    18  	"github.com/juju/juju/api/charmrevisionupdater"
    19  	"github.com/juju/juju/api/cleaner"
    20  	"github.com/juju/juju/api/discoverspaces"
    21  	"github.com/juju/juju/api/imagemetadata"
    22  	"github.com/juju/juju/api/instancepoller"
    23  	"github.com/juju/juju/api/keyupdater"
    24  	"github.com/juju/juju/api/reboot"
    25  	"github.com/juju/juju/api/unitassigner"
    26  	"github.com/juju/juju/api/uniter"
    27  	"github.com/juju/juju/api/upgrader"
    28  	"github.com/juju/juju/apiserver/params"
    29  	"github.com/juju/juju/network"
    30  )
    31  
    32  // Login authenticates as the entity with the given name and password
    33  // or macaroons. Subsequent requests on the state will act as that entity.
    34  // This method is usually called automatically by Open. The machine nonce
    35  // should be empty unless logging in as a machine agent.
    36  func (st *state) Login(tag names.Tag, password, nonce string, macaroons []macaroon.Slice) error {
    37  	var result params.LoginResult
    38  	request := &params.LoginRequest{
    39  		AuthTag:     tagToString(tag),
    40  		Credentials: password,
    41  		Nonce:       nonce,
    42  		Macaroons:   macaroons,
    43  	}
    44  	if password == "" {
    45  		// Add any macaroons from the cookie jar that might work for
    46  		// authenticating the login request.
    47  		request.Macaroons = append(request.Macaroons,
    48  			httpbakery.MacaroonsForURL(st.bakeryClient.Client.Jar, st.cookieURL)...,
    49  		)
    50  	}
    51  	err := st.APICall("Admin", 3, "", "Login", request, &result)
    52  	if err != nil {
    53  		var resp params.RedirectInfoResult
    54  		if params.IsRedirect(err) {
    55  			// We've been asked to redirect. Find out the redirection info.
    56  			// If the rpc packet allowed us to return arbitrary information in
    57  			// an error, we'd probably put this information in the Login response,
    58  			// but we can't do that currently.
    59  			if err := st.APICall("Admin", 3, "", "RedirectInfo", nil, &resp); err != nil {
    60  				return errors.Annotatef(err, "cannot get redirect addresses")
    61  			}
    62  			return &RedirectError{
    63  				Servers: params.NetworkHostsPorts(resp.Servers),
    64  				CACert:  resp.CACert,
    65  			}
    66  		}
    67  		return errors.Trace(err)
    68  	}
    69  	if result.DischargeRequired != nil {
    70  		// The result contains a discharge-required
    71  		// macaroon. We discharge it and retry
    72  		// the login request with the original macaroon
    73  		// and its discharges.
    74  		if result.DischargeRequiredReason == "" {
    75  			result.DischargeRequiredReason = "no reason given for discharge requirement"
    76  		}
    77  		if err := st.bakeryClient.HandleError(st.cookieURL, &httpbakery.Error{
    78  			Message: result.DischargeRequiredReason,
    79  			Code:    httpbakery.ErrDischargeRequired,
    80  			Info: &httpbakery.ErrorInfo{
    81  				Macaroon:     result.DischargeRequired,
    82  				MacaroonPath: "/",
    83  			},
    84  		}); err != nil {
    85  			cause := errors.Cause(err)
    86  			if httpbakery.IsInteractionError(cause) {
    87  				// Just inform the user of the reason for the
    88  				// failure, e.g. because the username/password
    89  				// they presented was invalid.
    90  				err = cause.(*httpbakery.InteractionError).Reason
    91  			}
    92  			return errors.Trace(err)
    93  		}
    94  		// Add the macaroons that have been saved by HandleError to our login request.
    95  		request.Macaroons = httpbakery.MacaroonsForURL(st.bakeryClient.Client.Jar, st.cookieURL)
    96  		result = params.LoginResult{} // zero result
    97  		err = st.APICall("Admin", 3, "", "Login", request, &result)
    98  		if err != nil {
    99  			return errors.Trace(err)
   100  		}
   101  		if result.DischargeRequired != nil {
   102  			return errors.Errorf("login with discharged macaroons failed: %s", result.DischargeRequiredReason)
   103  		}
   104  	}
   105  
   106  	var controllerAccess string
   107  	var modelAccess string
   108  	if result.UserInfo != nil {
   109  		tag, err = names.ParseTag(result.UserInfo.Identity)
   110  		if err != nil {
   111  			return errors.Trace(err)
   112  		}
   113  		controllerAccess = result.UserInfo.ControllerAccess
   114  		modelAccess = result.UserInfo.ModelAccess
   115  	}
   116  	servers := params.NetworkHostsPorts(result.Servers)
   117  	if err = st.setLoginResult(loginResultParams{
   118  		tag:              tag,
   119  		modelTag:         result.ModelTag,
   120  		controllerTag:    result.ControllerTag,
   121  		servers:          servers,
   122  		facades:          result.Facades,
   123  		modelAccess:      modelAccess,
   124  		controllerAccess: controllerAccess,
   125  	}); err != nil {
   126  		return errors.Trace(err)
   127  	}
   128  	st.serverVersion, err = version.Parse(result.ServerVersion)
   129  	if err != nil {
   130  		return errors.Trace(err)
   131  	}
   132  	return nil
   133  }
   134  
   135  type loginResultParams struct {
   136  	tag              names.Tag
   137  	modelTag         string
   138  	controllerTag    string
   139  	modelAccess      string
   140  	controllerAccess string
   141  	servers          [][]network.HostPort
   142  	facades          []params.FacadeVersions
   143  }
   144  
   145  func (st *state) setLoginResult(p loginResultParams) error {
   146  	st.authTag = p.tag
   147  	var modelTag names.ModelTag
   148  	if p.modelTag != "" {
   149  		var err error
   150  		modelTag, err = names.ParseModelTag(p.modelTag)
   151  		if err != nil {
   152  			return errors.Annotatef(err, "invalid model tag in login result")
   153  		}
   154  	}
   155  	if modelTag.Id() != st.modelTag.Id() {
   156  		return errors.Errorf("mismatched model tag in login result (got %q want %q)", modelTag.Id(), st.modelTag.Id())
   157  	}
   158  	ctag, err := names.ParseControllerTag(p.controllerTag)
   159  	if err != nil {
   160  		return errors.Annotatef(err, "invalid controller tag %q returned from login", p.controllerTag)
   161  	}
   162  	st.controllerTag = ctag
   163  	st.controllerAccess = p.controllerAccess
   164  	st.modelAccess = p.modelAccess
   165  
   166  	hostPorts, err := addAddress(p.servers, st.addr)
   167  	if err != nil {
   168  		if clerr := st.Close(); clerr != nil {
   169  			err = errors.Annotatef(err, "error closing state: %v", clerr)
   170  		}
   171  		return err
   172  	}
   173  	st.hostPorts = hostPorts
   174  
   175  	st.facadeVersions = make(map[string][]int, len(p.facades))
   176  	for _, facade := range p.facades {
   177  		st.facadeVersions[facade.Name] = facade.Versions
   178  	}
   179  
   180  	st.setLoggedIn()
   181  	return nil
   182  }
   183  
   184  // AuthTag returns the tag of the authorized user of the state API connection.
   185  func (st *state) AuthTag() names.Tag {
   186  	return st.authTag
   187  }
   188  
   189  // ModelAccess returns the access level of authorized user to the model.
   190  func (st *state) ModelAccess() string {
   191  	return st.modelAccess
   192  }
   193  
   194  // ControllerAccess returns the access level of authorized user to the model.
   195  func (st *state) ControllerAccess() string {
   196  	return st.controllerAccess
   197  }
   198  
   199  // CookieURL returns the URL that HTTP cookies for the API will be
   200  // associated with.
   201  func (st *state) CookieURL() *url.URL {
   202  	copy := *st.cookieURL
   203  	return &copy
   204  }
   205  
   206  // slideAddressToFront moves the address at the location (serverIndex, addrIndex) to be
   207  // the first address of the first server.
   208  func slideAddressToFront(servers [][]network.HostPort, serverIndex, addrIndex int) {
   209  	server := servers[serverIndex]
   210  	hostPort := server[addrIndex]
   211  	// Move the matching address to be the first in this server
   212  	for ; addrIndex > 0; addrIndex-- {
   213  		server[addrIndex] = server[addrIndex-1]
   214  	}
   215  	server[0] = hostPort
   216  	for ; serverIndex > 0; serverIndex-- {
   217  		servers[serverIndex] = servers[serverIndex-1]
   218  	}
   219  	servers[0] = server
   220  }
   221  
   222  // addAddress appends a new server derived from the given
   223  // address to servers if the address is not already found
   224  // there.
   225  func addAddress(servers [][]network.HostPort, addr string) ([][]network.HostPort, error) {
   226  	for i, server := range servers {
   227  		for j, hostPort := range server {
   228  			if hostPort.NetAddr() == addr {
   229  				slideAddressToFront(servers, i, j)
   230  				return servers, nil
   231  			}
   232  		}
   233  	}
   234  	host, portString, err := net.SplitHostPort(addr)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  	port, err := strconv.Atoi(portString)
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  	result := make([][]network.HostPort, 0, len(servers)+1)
   243  	result = append(result, network.NewHostPorts(port, host))
   244  	result = append(result, servers...)
   245  	return result, nil
   246  }
   247  
   248  // Client returns an object that can be used
   249  // to access client-specific functionality.
   250  func (st *state) Client() *Client {
   251  	frontend, backend := base.NewClientFacade(st, "Client")
   252  	return &Client{ClientFacade: frontend, facade: backend, st: st}
   253  }
   254  
   255  // UnitAssigner returns a version of the state that provides functionality
   256  // required by the unitassigner worker.
   257  func (st *state) UnitAssigner() unitassigner.API {
   258  	return unitassigner.New(st)
   259  }
   260  
   261  // Uniter returns a version of the state that provides functionality
   262  // required by the uniter worker.
   263  func (st *state) Uniter() (*uniter.State, error) {
   264  	unitTag, ok := st.authTag.(names.UnitTag)
   265  	if !ok {
   266  		return nil, errors.Errorf("expected UnitTag, got %T %v", st.authTag, st.authTag)
   267  	}
   268  	return uniter.NewState(st, unitTag), nil
   269  }
   270  
   271  // Upgrader returns access to the Upgrader API
   272  func (st *state) Upgrader() *upgrader.State {
   273  	return upgrader.NewState(st)
   274  }
   275  
   276  // Reboot returns access to the Reboot API
   277  func (st *state) Reboot() (reboot.State, error) {
   278  	switch tag := st.authTag.(type) {
   279  	case names.MachineTag:
   280  		return reboot.NewState(st, tag), nil
   281  	default:
   282  		return nil, errors.Errorf("expected names.MachineTag, got %T", tag)
   283  	}
   284  }
   285  
   286  // DiscoverSpaces returns access to the DiscoverSpacesAPI.
   287  func (st *state) DiscoverSpaces() *discoverspaces.API {
   288  	return discoverspaces.NewAPI(st)
   289  }
   290  
   291  // KeyUpdater returns access to the KeyUpdater API
   292  func (st *state) KeyUpdater() *keyupdater.State {
   293  	return keyupdater.NewState(st)
   294  }
   295  
   296  // InstancePoller returns access to the InstancePoller API
   297  func (st *state) InstancePoller() *instancepoller.API {
   298  	return instancepoller.NewAPI(st)
   299  }
   300  
   301  // CharmRevisionUpdater returns access to the CharmRevisionUpdater API
   302  func (st *state) CharmRevisionUpdater() *charmrevisionupdater.State {
   303  	return charmrevisionupdater.NewState(st)
   304  }
   305  
   306  // Cleaner returns a version of the state that provides access to the cleaner API
   307  func (st *state) Cleaner() *cleaner.API {
   308  	return cleaner.NewAPI(st)
   309  }
   310  
   311  // ServerVersion holds the version of the API server that we are connected to.
   312  // It is possible that this version is Zero if the server does not report this
   313  // during login. The second result argument indicates if the version number is
   314  // set.
   315  func (st *state) ServerVersion() (version.Number, bool) {
   316  	return st.serverVersion, st.serverVersion != version.Zero
   317  }
   318  
   319  // MetadataUpdater returns access to the imageMetadata API
   320  func (st *state) MetadataUpdater() *imagemetadata.Client {
   321  	return imagemetadata.NewClient(st)
   322  }