github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 }