github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/cmd/modelcmd/apiopener.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package modelcmd
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/utils/clock"
    11  
    12  	"github.com/juju/juju/api"
    13  	"github.com/juju/juju/jujuclient"
    14  )
    15  
    16  var ErrConnTimedOut = errors.New("open connection timed out")
    17  
    18  // APIOpener provides a way to open a connection to the Juju
    19  // API Server through the named connection.
    20  type APIOpener interface {
    21  	Open(store jujuclient.ClientStore, controllerName, accountName, modelName string) (api.Connection, error)
    22  }
    23  
    24  type OpenFunc func(jujuclient.ClientStore, string, string, string) (api.Connection, error)
    25  
    26  func (f OpenFunc) Open(store jujuclient.ClientStore, controllerName, accountName, modelName string) (api.Connection, error) {
    27  	return f(store, controllerName, accountName, modelName)
    28  }
    29  
    30  type timeoutOpener struct {
    31  	opener  APIOpener
    32  	clock   clock.Clock
    33  	timeout time.Duration
    34  }
    35  
    36  // NewTimeoutOpener will call the opener when Open is called, but if the
    37  // opener does not return by the specified timeout, ErrConnTimeOut is
    38  // returned.
    39  func NewTimeoutOpener(opener APIOpener, clock clock.Clock, timeout time.Duration) APIOpener {
    40  	return &timeoutOpener{
    41  		opener:  opener,
    42  		clock:   clock,
    43  		timeout: timeout,
    44  	}
    45  }
    46  
    47  func (t *timeoutOpener) Open(store jujuclient.ClientStore, controllerName, accountName, modelName string) (api.Connection, error) {
    48  	// Make the channels buffered so the created goroutine is guaranteed
    49  	// not go get blocked trying to send down the channel.
    50  	apic := make(chan api.Connection, 1)
    51  	errc := make(chan error, 1)
    52  	go func() {
    53  		api, dialErr := t.opener.Open(store, controllerName, accountName, modelName)
    54  		if dialErr != nil {
    55  			errc <- dialErr
    56  			return
    57  		}
    58  
    59  		select {
    60  		case apic <- api:
    61  			// sent fine
    62  		default:
    63  			// couldn't send, was blocked by the dummy value, must have timed out.
    64  			api.Close()
    65  		}
    66  	}()
    67  
    68  	var apiRoot api.Connection
    69  	select {
    70  	case err := <-errc:
    71  		return nil, err
    72  	case apiRoot = <-apic:
    73  	case <-t.clock.After(t.timeout):
    74  		select {
    75  		case apic <- nil:
    76  			// Fill up the buffer on the apic to indicate to the other goroutine
    77  			// that we have timed out.
    78  		case apiRoot = <-apic:
    79  			// We hit that weird edge case where we have both timed out and
    80  			// returned a viable apiRoot at exactly the same time, and the other
    81  			// goroutine managed to send back the apiRoot before we pushed the
    82  			// dummy value.  If this is the case, then we are good, return the
    83  			// apiRoot
    84  			return apiRoot, nil
    85  		}
    86  		return nil, ErrConnTimedOut
    87  	}
    88  
    89  	return apiRoot, nil
    90  }