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

     1  // Copyright 2012-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package api
     5  
     6  import (
     7  	"bufio"
     8  	"crypto/tls"
     9  	"crypto/x509"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io"
    13  	"net/http"
    14  	"net/url"
    15  	"strings"
    16  	"sync/atomic"
    17  	"time"
    18  
    19  	"github.com/juju/errors"
    20  	"github.com/juju/loggo"
    21  	"github.com/juju/retry"
    22  	"github.com/juju/utils"
    23  	"github.com/juju/utils/clock"
    24  	"github.com/juju/utils/parallel"
    25  	"github.com/juju/version"
    26  	"golang.org/x/net/websocket"
    27  	"gopkg.in/juju/names.v2"
    28  	"gopkg.in/macaroon-bakery.v1/httpbakery"
    29  	"gopkg.in/macaroon.v1"
    30  
    31  	"github.com/juju/juju/api/base"
    32  	"github.com/juju/juju/apiserver/observer"
    33  	"github.com/juju/juju/apiserver/params"
    34  	"github.com/juju/juju/network"
    35  	"github.com/juju/juju/rpc"
    36  	"github.com/juju/juju/rpc/jsoncodec"
    37  )
    38  
    39  // PingPeriod defines how often the internal connection health check
    40  // will run.
    41  const PingPeriod = 1 * time.Minute
    42  
    43  // pingTimeout defines how long a health check can take before we
    44  // consider it to have failed.
    45  const pingTimeout = 30 * time.Second
    46  
    47  var logger = loggo.GetLogger("juju.api")
    48  
    49  type rpcConnection interface {
    50  	Call(req rpc.Request, params, response interface{}) error
    51  	Dead() <-chan struct{}
    52  	Close() error
    53  }
    54  
    55  // state is the internal implementation of the Connection interface.
    56  type state struct {
    57  	client rpcConnection
    58  	conn   *websocket.Conn
    59  	clock  clock.Clock
    60  
    61  	// addr is the address used to connect to the API server.
    62  	addr string
    63  
    64  	// cookieURL is the URL that HTTP cookies for the API
    65  	// will be associated with (specifically macaroon auth cookies).
    66  	cookieURL *url.URL
    67  
    68  	// modelTag holds the model tag.
    69  	// It is empty if there is no model tag associated with the connection.
    70  	modelTag names.ModelTag
    71  
    72  	// controllerTag holds the controller's tag once we're connected.
    73  	controllerTag names.ControllerTag
    74  
    75  	// serverVersion holds the version of the API server that we are
    76  	// connected to.  It is possible that this version is 0 if the
    77  	// server does not report this during login.
    78  	serverVersion version.Number
    79  
    80  	// hostPorts is the API server addresses returned from Login,
    81  	// which the client may cache and use for failover.
    82  	hostPorts [][]network.HostPort
    83  
    84  	// facadeVersions holds the versions of all facades as reported by
    85  	// Login
    86  	facadeVersions map[string][]int
    87  
    88  	// pingFacadeVersion is the version to use for the pinger. This is lazily
    89  	// set at initialization to avoid a race in our tests. See
    90  	// http://pad.lv/1614732 for more details regarding the race.
    91  	pingerFacadeVersion int
    92  
    93  	// authTag holds the authenticated entity's tag after login.
    94  	authTag names.Tag
    95  
    96  	// mpdelAccess holds the access level of the user to the connected model.
    97  	modelAccess string
    98  
    99  	// controllerAccess holds the access level of the user to the connected controller.
   100  	controllerAccess string
   101  
   102  	// broken is a channel that gets closed when the connection is
   103  	// broken.
   104  	broken chan struct{}
   105  
   106  	// closed is a channel that gets closed when State.Close is called.
   107  	closed chan struct{}
   108  
   109  	// loggedIn holds whether the client has successfully logged
   110  	// in. It's a int32 so that the atomic package can be used to
   111  	// access it safely.
   112  	loggedIn int32
   113  
   114  	// tag, password, macaroons and nonce hold the cached login
   115  	// credentials. These are only valid if loggedIn is 1.
   116  	tag       string
   117  	password  string
   118  	macaroons []macaroon.Slice
   119  	nonce     string
   120  
   121  	// serverRootAddress holds the cached API server address and port used
   122  	// to login.
   123  	serverRootAddress string
   124  
   125  	// serverScheme is the URI scheme of the API Server
   126  	serverScheme string
   127  
   128  	// tlsConfig holds the TLS config appropriate for making SSL
   129  	// connections to the API endpoints.
   130  	tlsConfig *tls.Config
   131  
   132  	// certPool holds the cert pool that is used to authenticate the tls
   133  	// connections to the API.
   134  	certPool *x509.CertPool
   135  
   136  	// bakeryClient holds the client that will be used to
   137  	// authorize macaroon based login requests.
   138  	bakeryClient *httpbakery.Client
   139  }
   140  
   141  // RedirectError is returned from Open when the controller
   142  // needs to inform the client that the model is hosted
   143  // on a different set of API addresses.
   144  type RedirectError struct {
   145  	// Servers holds the sets of addresses of the redirected
   146  	// servers.
   147  	Servers [][]network.HostPort
   148  
   149  	// CACert holds the certificate of the remote server.
   150  	CACert string
   151  }
   152  
   153  func (e *RedirectError) Error() string {
   154  	return fmt.Sprintf("redirection to alternative server required")
   155  }
   156  
   157  // Open establishes a connection to the API server using the Info
   158  // given, returning a State instance which can be used to make API
   159  // requests.
   160  //
   161  // If the model is hosted on a different server, Open
   162  // will return an error with a *RedirectError cause
   163  // holding the details of another server to connect to.
   164  //
   165  // See Connect for details of the connection mechanics.
   166  func Open(info *Info, opts DialOpts) (Connection, error) {
   167  	return open(info, opts, clock.WallClock)
   168  }
   169  
   170  // open is the unexported version of open that also includes
   171  // an explicit clock instance argument.
   172  func open(
   173  	info *Info,
   174  	opts DialOpts,
   175  	clock clock.Clock,
   176  ) (Connection, error) {
   177  	if err := info.Validate(); err != nil {
   178  		return nil, errors.Annotate(err, "validating info for opening an API connection")
   179  	}
   180  	if clock == nil {
   181  		return nil, errors.NotValidf("nil clock")
   182  	}
   183  	conn, tlsConfig, err := connectWebsocket(info, opts)
   184  	if err != nil {
   185  		return nil, errors.Trace(err)
   186  	}
   187  
   188  	client := rpc.NewConn(jsoncodec.NewWebsocket(conn), observer.None())
   189  	client.Start()
   190  
   191  	bakeryClient := opts.BakeryClient
   192  	if bakeryClient == nil {
   193  		bakeryClient = httpbakery.NewClient()
   194  	} else {
   195  		// Make a copy of the bakery client and its HTTP client
   196  		c := *opts.BakeryClient
   197  		bakeryClient = &c
   198  		httpc := *bakeryClient.Client
   199  		bakeryClient.Client = &httpc
   200  	}
   201  	apiHost := conn.Config().Location.Host
   202  	// Technically when there's no CACert, we don't need this
   203  	// machinery, because we could just use http.DefaultTransport
   204  	// for everything, but it's easier just to leave it in place.
   205  	bakeryClient.Client.Transport = &hostSwitchingTransport{
   206  		primaryHost: apiHost,
   207  		primary:     utils.NewHttpTLSTransport(tlsConfig),
   208  		fallback:    http.DefaultTransport,
   209  	}
   210  
   211  	st := &state{
   212  		client: client,
   213  		conn:   conn,
   214  		clock:  clock,
   215  		addr:   apiHost,
   216  		cookieURL: &url.URL{
   217  			Scheme: "https",
   218  			Host:   conn.Config().Location.Host,
   219  			Path:   "/",
   220  		},
   221  		pingerFacadeVersion: facadeVersions["Pinger"],
   222  		serverScheme:        "https",
   223  		serverRootAddress:   conn.Config().Location.Host,
   224  		// We populate the username and password before
   225  		// login because, when doing HTTP requests, we'll want
   226  		// to use the same username and password for authenticating
   227  		// those. If login fails, we discard the connection.
   228  		tag:          tagToString(info.Tag),
   229  		password:     info.Password,
   230  		macaroons:    info.Macaroons,
   231  		nonce:        info.Nonce,
   232  		tlsConfig:    tlsConfig,
   233  		bakeryClient: bakeryClient,
   234  		modelTag:     info.ModelTag,
   235  	}
   236  	if !info.SkipLogin {
   237  		if err := st.Login(info.Tag, info.Password, info.Nonce, info.Macaroons); err != nil {
   238  			conn.Close()
   239  			return nil, errors.Trace(err)
   240  		}
   241  	}
   242  
   243  	st.broken = make(chan struct{})
   244  	st.closed = make(chan struct{})
   245  
   246  	go (&monitor{
   247  		clock:       clock,
   248  		ping:        st.Ping,
   249  		pingPeriod:  PingPeriod,
   250  		pingTimeout: pingTimeout,
   251  		closed:      st.closed,
   252  		dead:        client.Dead(),
   253  		broken:      st.broken,
   254  	}).run()
   255  	return st, nil
   256  }
   257  
   258  // hostSwitchingTransport provides an http.RoundTripper
   259  // that chooses an actual RoundTripper to use
   260  // depending on the destination host.
   261  //
   262  // This makes it possible to use a different set of root
   263  // CAs for the API and all other hosts.
   264  type hostSwitchingTransport struct {
   265  	primaryHost string
   266  	primary     http.RoundTripper
   267  	fallback    http.RoundTripper
   268  }
   269  
   270  // RoundTrip implements http.RoundTripper.RoundTrip.
   271  func (t *hostSwitchingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
   272  	if req.URL.Host == t.primaryHost {
   273  		return t.primary.RoundTrip(req)
   274  	}
   275  	return t.fallback.RoundTrip(req)
   276  }
   277  
   278  // connectWebsocket establishes a websocket connection to the RPC
   279  // API websocket on the API server using Info. If multiple API addresses
   280  // are provided in Info they will be tried concurrently - the first successful
   281  // connection wins.
   282  //
   283  // It also returns the TLS configuration that it has derived from the Info.
   284  func connectWebsocket(info *Info, opts DialOpts) (*websocket.Conn, *tls.Config, error) {
   285  	if len(info.Addrs) == 0 {
   286  		return nil, nil, errors.New("no API addresses to connect to")
   287  	}
   288  	tlsConfig := utils.SecureTLSConfig()
   289  	tlsConfig.InsecureSkipVerify = opts.InsecureSkipVerify
   290  
   291  	if info.CACert != "" && !tlsConfig.InsecureSkipVerify {
   292  		// We want to be specific here (rather than just using "anything".
   293  		// See commit 7fc118f015d8480dfad7831788e4b8c0432205e8 (PR 899).
   294  		tlsConfig.ServerName = "juju-apiserver"
   295  		certPool, err := CreateCertPool(info.CACert)
   296  		if err != nil {
   297  			return nil, nil, errors.Annotate(err, "cert pool creation failed")
   298  		}
   299  		tlsConfig.RootCAs = certPool
   300  	}
   301  	path, err := apiPath(info.ModelTag, "/api")
   302  	if err != nil {
   303  		return nil, nil, errors.Trace(err)
   304  	}
   305  	conn, err := dialWebSocket(info.Addrs, path, tlsConfig, opts)
   306  	if err != nil {
   307  		return nil, nil, errors.Trace(err)
   308  	}
   309  	logger.Infof("connection established to %q", conn.RemoteAddr())
   310  	return conn, tlsConfig, nil
   311  }
   312  
   313  // dialWebSocket dials a websocket with one of the provided addresses, the
   314  // specified URL path, TLS configuration, and dial options. Each of the
   315  // specified addresses will be attempted concurrently, and the first
   316  // successful connection will be returned.
   317  func dialWebSocket(addrs []string, path string, tlsConfig *tls.Config, opts DialOpts) (*websocket.Conn, error) {
   318  	// Dial all addresses at reasonable intervals.
   319  	try := parallel.NewTry(0, nil)
   320  	defer try.Kill()
   321  	for _, addr := range addrs {
   322  		err := dialWebsocket(addr, path, opts, tlsConfig, try)
   323  		if err == parallel.ErrStopped {
   324  			break
   325  		}
   326  		if err != nil {
   327  			return nil, errors.Trace(err)
   328  		}
   329  		select {
   330  		case <-time.After(opts.DialAddressInterval):
   331  		case <-try.Dead():
   332  		}
   333  	}
   334  	try.Close()
   335  	result, err := try.Result()
   336  	if err != nil {
   337  		return nil, errors.Trace(err)
   338  	}
   339  	return result.(*websocket.Conn), nil
   340  }
   341  
   342  // ConnectStream implements StreamConnector.ConnectStream.
   343  func (st *state) ConnectStream(path string, attrs url.Values) (base.Stream, error) {
   344  	if !st.isLoggedIn() {
   345  		return nil, errors.New("cannot use ConnectStream without logging in")
   346  	}
   347  	// We use the standard "macaraq" macaroon authentication dance here.
   348  	// That is, we attach any macaroons we have to the initial request,
   349  	// and if that succeeds, all's good. If it fails with a DischargeRequired
   350  	// error, the response will contain a macaroon that, when discharged,
   351  	// may allow access, so we discharge it (using bakery.Client.HandleError)
   352  	// and try the request again.
   353  	conn, err := st.connectStream(path, attrs)
   354  	if err == nil {
   355  		return conn, err
   356  	}
   357  	if params.ErrCode(err) != params.CodeDischargeRequired {
   358  		return nil, errors.Trace(err)
   359  	}
   360  	if err := st.bakeryClient.HandleError(st.cookieURL, bakeryError(err)); err != nil {
   361  		return nil, errors.Trace(err)
   362  	}
   363  	// Try again with the discharged macaroon.
   364  	conn, err = st.connectStream(path, attrs)
   365  	if err != nil {
   366  		return nil, errors.Trace(err)
   367  	}
   368  	return conn, nil
   369  }
   370  
   371  // connectStream is the internal version of ConnectStream. It differs from
   372  // ConnectStream only in that it will not retry the connection if it encounters
   373  // discharge-required error.
   374  func (st *state) connectStream(path string, attrs url.Values) (base.Stream, error) {
   375  	path, err := apiPath(st.modelTag, path)
   376  	if err != nil {
   377  		return nil, errors.Trace(err)
   378  	}
   379  	target := url.URL{
   380  		Scheme:   "wss",
   381  		Host:     st.addr,
   382  		Path:     path,
   383  		RawQuery: attrs.Encode(),
   384  	}
   385  	cfg, err := websocket.NewConfig(target.String(), "http://localhost/")
   386  	if st.tag != "" {
   387  		cfg.Header = utils.BasicAuthHeader(st.tag, st.password)
   388  	}
   389  	if st.nonce != "" {
   390  		cfg.Header.Set(params.MachineNonceHeader, st.nonce)
   391  	}
   392  	// Add any cookies because they will not be sent to websocket
   393  	// connections by default.
   394  	st.addCookiesToHeader(cfg.Header)
   395  
   396  	cfg.TlsConfig = st.tlsConfig
   397  	connection, err := websocketDialConfig(cfg)
   398  	if err != nil {
   399  		return nil, err
   400  	}
   401  	if err := readInitialStreamError(connection); err != nil {
   402  		return nil, errors.Trace(err)
   403  	}
   404  	return connection, nil
   405  }
   406  
   407  // readInitialStreamError reads the initial error response
   408  // from a stream connection and returns it.
   409  func readInitialStreamError(conn io.Reader) error {
   410  	// We can use bufio here because the websocket guarantees that a
   411  	// single read will not read more than a single frame; there is
   412  	// no guarantee that a single read might not read less than the
   413  	// whole frame though, so using a single Read call is not
   414  	// correct. By using ReadSlice rather than ReadBytes, we
   415  	// guarantee that the error can't be too big (>4096 bytes).
   416  	line, err := bufio.NewReader(conn).ReadSlice('\n')
   417  	if err != nil {
   418  		return errors.Annotate(err, "unable to read initial response")
   419  	}
   420  	var errResult params.ErrorResult
   421  	if err := json.Unmarshal(line, &errResult); err != nil {
   422  		return errors.Annotate(err, "unable to unmarshal initial response")
   423  	}
   424  	if errResult.Error != nil {
   425  		return errResult.Error
   426  	}
   427  	return nil
   428  }
   429  
   430  // addCookiesToHeader adds any cookies associated with the
   431  // API host to the given header. This is necessary because
   432  // otherwise cookies are not sent to websocket endpoints.
   433  func (st *state) addCookiesToHeader(h http.Header) {
   434  	// net/http only allows adding cookies to a request,
   435  	// but when it sends a request to a non-http endpoint,
   436  	// it doesn't add the cookies, so make a request, starting
   437  	// with the given header, add the cookies to use, then
   438  	// throw away the request but keep the header.
   439  	req := &http.Request{
   440  		Header: h,
   441  	}
   442  	cookies := st.bakeryClient.Client.Jar.Cookies(st.cookieURL)
   443  	for _, c := range cookies {
   444  		req.AddCookie(c)
   445  	}
   446  }
   447  
   448  // apiEndpoint returns a URL that refers to the given API slash-prefixed
   449  // endpoint path and query parameters.
   450  func (st *state) apiEndpoint(path, query string) (*url.URL, error) {
   451  	path, err := apiPath(st.modelTag, path)
   452  	if err != nil {
   453  		return nil, errors.Trace(err)
   454  	}
   455  	return &url.URL{
   456  		Scheme:   st.serverScheme,
   457  		Host:     st.Addr(),
   458  		Path:     path,
   459  		RawQuery: query,
   460  	}, nil
   461  }
   462  
   463  // Ping implements api.Connection.
   464  func (s *state) Ping() error {
   465  	return s.APICall("Pinger", s.pingerFacadeVersion, "", "Ping", nil, nil)
   466  }
   467  
   468  // apiPath returns the given API endpoint path relative
   469  // to the given model tag.
   470  func apiPath(modelTag names.ModelTag, path string) (string, error) {
   471  	if !strings.HasPrefix(path, "/") {
   472  		return "", errors.Errorf("cannot make API path from non-slash-prefixed path %q", path)
   473  	}
   474  	modelUUID := modelTag.Id()
   475  	if modelUUID == "" {
   476  		return path, nil
   477  	}
   478  	return "/model/" + modelUUID + path, nil
   479  }
   480  
   481  // tagToString returns the value of a tag's String method, or "" if the tag is nil.
   482  func tagToString(tag names.Tag) string {
   483  	if tag == nil {
   484  		return ""
   485  	}
   486  	return tag.String()
   487  }
   488  
   489  func dialWebsocket(addr, path string, opts DialOpts, tlsConfig *tls.Config, try *parallel.Try) error {
   490  	// origin is required by the WebSocket API, used for "origin policy"
   491  	// in websockets. We pass localhost to satisfy the API; it is
   492  	// inconsequential to us.
   493  	const origin = "http://localhost/"
   494  	cfg, err := websocket.NewConfig("wss://"+addr+path, origin)
   495  	if err != nil {
   496  		return errors.Trace(err)
   497  	}
   498  	cfg.TlsConfig = tlsConfig
   499  	return try.Start(newWebsocketDialer(cfg, opts))
   500  }
   501  
   502  // newWebsocketDialer returns a function that
   503  // can be passed to utils/parallel.Try.Start.
   504  var newWebsocketDialer = createWebsocketDialer
   505  
   506  func createWebsocketDialer(cfg *websocket.Config, opts DialOpts) func(<-chan struct{}) (io.Closer, error) {
   507  	// TODO(katco): 2016-08-09: lp:1611427
   508  	openAttempt := utils.AttemptStrategy{
   509  		Total: opts.Timeout,
   510  		Delay: opts.RetryDelay,
   511  	}
   512  	return func(stop <-chan struct{}) (io.Closer, error) {
   513  		for a := openAttempt.Start(); a.Next(); {
   514  			select {
   515  			case <-stop:
   516  				return nil, parallel.ErrStopped
   517  			default:
   518  			}
   519  			logger.Infof("dialing %q", cfg.Location)
   520  			conn, err := websocket.DialConfig(cfg)
   521  			if err == nil {
   522  				return conn, nil
   523  			}
   524  			if !a.HasNext() || isX509Error(err) {
   525  				// We won't reconnect when there's an X509 error
   526  				// because we're not going to succeed if we retry
   527  				// in that case.
   528  				logger.Infof("error dialing %q: %v", cfg.Location, err)
   529  				return nil, errors.Annotatef(err, "unable to connect to API")
   530  			}
   531  		}
   532  		panic("unreachable")
   533  	}
   534  }
   535  
   536  // isX509Error reports whether the given websocket error
   537  // results from an X509 problem.
   538  func isX509Error(err error) bool {
   539  	wsErr, ok := errors.Cause(err).(*websocket.DialError)
   540  	if !ok {
   541  		return false
   542  	}
   543  	switch wsErr.Err.(type) {
   544  	case x509.HostnameError,
   545  		x509.InsecureAlgorithmError,
   546  		x509.UnhandledCriticalExtension,
   547  		x509.UnknownAuthorityError,
   548  		x509.ConstraintViolationError,
   549  		x509.SystemRootsError:
   550  		return true
   551  	}
   552  	switch err {
   553  	case x509.ErrUnsupportedAlgorithm,
   554  		x509.IncorrectPasswordError:
   555  		return true
   556  	}
   557  	return false
   558  }
   559  
   560  type hasErrorCode interface {
   561  	ErrorCode() string
   562  }
   563  
   564  // APICall places a call to the remote machine.
   565  //
   566  // This fills out the rpc.Request on the given facade, version for a given
   567  // object id, and the specific RPC method. It marshalls the Arguments, and will
   568  // unmarshall the result into the response object that is supplied.
   569  func (s *state) APICall(facade string, version int, id, method string, args, response interface{}) error {
   570  	retrySpec := retry.CallArgs{
   571  		Func: func() error {
   572  			return s.client.Call(rpc.Request{
   573  				Type:    facade,
   574  				Version: version,
   575  				Id:      id,
   576  				Action:  method,
   577  			}, args, response)
   578  		},
   579  		IsFatalError: func(err error) bool {
   580  			err = errors.Cause(err)
   581  			ec, ok := err.(hasErrorCode)
   582  			if !ok {
   583  				return true
   584  			}
   585  			return ec.ErrorCode() != params.CodeRetry
   586  		},
   587  		Delay:       100 * time.Millisecond,
   588  		MaxDelay:    1500 * time.Millisecond,
   589  		MaxDuration: 10 * time.Second,
   590  		BackoffFunc: retry.DoubleDelay,
   591  		Clock:       s.clock,
   592  	}
   593  	err := retry.Call(retrySpec)
   594  	return errors.Trace(err)
   595  }
   596  
   597  func (s *state) Close() error {
   598  	err := s.client.Close()
   599  	select {
   600  	case <-s.closed:
   601  	default:
   602  		close(s.closed)
   603  	}
   604  	<-s.broken
   605  	return err
   606  }
   607  
   608  // Broken implements api.Connection.
   609  func (s *state) Broken() <-chan struct{} {
   610  	return s.broken
   611  }
   612  
   613  // IsBroken implements api.Connection.
   614  func (s *state) IsBroken() bool {
   615  	select {
   616  	case <-s.broken:
   617  		return true
   618  	default:
   619  	}
   620  	if err := s.Ping(); err != nil {
   621  		logger.Debugf("connection ping failed: %v", err)
   622  		return true
   623  	}
   624  	return false
   625  }
   626  
   627  // Addr returns the address used to connect to the API server.
   628  func (s *state) Addr() string {
   629  	return s.addr
   630  }
   631  
   632  // ModelTag implements base.APICaller.ModelTag.
   633  func (s *state) ModelTag() (names.ModelTag, bool) {
   634  	return s.modelTag, s.modelTag.Id() != ""
   635  }
   636  
   637  // ControllerTag implements base.APICaller.ControllerTag.
   638  func (s *state) ControllerTag() names.ControllerTag {
   639  	return s.controllerTag
   640  }
   641  
   642  // APIHostPorts returns addresses that may be used to connect
   643  // to the API server, including the address used to connect.
   644  //
   645  // The addresses are scoped (public, cloud-internal, etc.), so
   646  // the client may choose which addresses to attempt. For the
   647  // Juju CLI, all addresses must be attempted, as the CLI may
   648  // be invoked both within and outside the model (think
   649  // private clouds).
   650  func (s *state) APIHostPorts() [][]network.HostPort {
   651  	// NOTE: We're making a copy of s.hostPorts before returning it,
   652  	// for safety.
   653  	hostPorts := make([][]network.HostPort, len(s.hostPorts))
   654  	for i, server := range s.hostPorts {
   655  		hostPorts[i] = append([]network.HostPort{}, server...)
   656  	}
   657  	return hostPorts
   658  }
   659  
   660  // AllFacadeVersions returns what versions we know about for all facades
   661  func (s *state) AllFacadeVersions() map[string][]int {
   662  	facades := make(map[string][]int, len(s.facadeVersions))
   663  	for name, versions := range s.facadeVersions {
   664  		facades[name] = append([]int{}, versions...)
   665  	}
   666  	return facades
   667  }
   668  
   669  // BestFacadeVersion compares the versions of facades that we know about, and
   670  // the versions available from the server, and reports back what version is the
   671  // 'best available' to use.
   672  // TODO(jam) this is the eventual implementation of what version of a given
   673  // Facade we will want to use. It needs to line up the versions that the server
   674  // reports to us, with the versions that our client knows how to use.
   675  func (s *state) BestFacadeVersion(facade string) int {
   676  	return bestVersion(facadeVersions[facade], s.facadeVersions[facade])
   677  }
   678  
   679  // serverRoot returns the cached API server address and port used
   680  // to login, prefixed with "<URI scheme>://" (usually https).
   681  func (s *state) serverRoot() string {
   682  	return s.serverScheme + "://" + s.serverRootAddress
   683  }
   684  
   685  func (s *state) isLoggedIn() bool {
   686  	return atomic.LoadInt32(&s.loggedIn) == 1
   687  }
   688  
   689  func (s *state) setLoggedIn() {
   690  	atomic.StoreInt32(&s.loggedIn, 1)
   691  }