Commit 6c25f371 authored by James DeFelice's avatar James DeFelice

update to latest mesos-go for master failover improvements

parent 9ceed60b
...@@ -757,43 +757,43 @@ ...@@ -757,43 +757,43 @@
}, },
{ {
"ImportPath": "github.com/mesos/mesos-go/auth", "ImportPath": "github.com/mesos/mesos-go/auth",
"Comment": "before-0.26-protos-14-g4a7554a", "Comment": "before-0.26-protos-29-gb755e34",
"Rev": "4a7554aad396c70d19c9fc3469980547c9f117ae" "Rev": "b755e34cdb14a6de15ccb50605786c9ecacbfc1f"
}, },
{ {
"ImportPath": "github.com/mesos/mesos-go/detector", "ImportPath": "github.com/mesos/mesos-go/detector",
"Comment": "before-0.26-protos-14-g4a7554a", "Comment": "before-0.26-protos-29-gb755e34",
"Rev": "4a7554aad396c70d19c9fc3469980547c9f117ae" "Rev": "b755e34cdb14a6de15ccb50605786c9ecacbfc1f"
}, },
{ {
"ImportPath": "github.com/mesos/mesos-go/executor", "ImportPath": "github.com/mesos/mesos-go/executor",
"Comment": "before-0.26-protos-14-g4a7554a", "Comment": "before-0.26-protos-29-gb755e34",
"Rev": "4a7554aad396c70d19c9fc3469980547c9f117ae" "Rev": "b755e34cdb14a6de15ccb50605786c9ecacbfc1f"
}, },
{ {
"ImportPath": "github.com/mesos/mesos-go/mesosproto", "ImportPath": "github.com/mesos/mesos-go/mesosproto",
"Comment": "before-0.26-protos-14-g4a7554a", "Comment": "before-0.26-protos-29-gb755e34",
"Rev": "4a7554aad396c70d19c9fc3469980547c9f117ae" "Rev": "b755e34cdb14a6de15ccb50605786c9ecacbfc1f"
}, },
{ {
"ImportPath": "github.com/mesos/mesos-go/mesosutil", "ImportPath": "github.com/mesos/mesos-go/mesosutil",
"Comment": "before-0.26-protos-14-g4a7554a", "Comment": "before-0.26-protos-29-gb755e34",
"Rev": "4a7554aad396c70d19c9fc3469980547c9f117ae" "Rev": "b755e34cdb14a6de15ccb50605786c9ecacbfc1f"
}, },
{ {
"ImportPath": "github.com/mesos/mesos-go/messenger", "ImportPath": "github.com/mesos/mesos-go/messenger",
"Comment": "before-0.26-protos-14-g4a7554a", "Comment": "before-0.26-protos-29-gb755e34",
"Rev": "4a7554aad396c70d19c9fc3469980547c9f117ae" "Rev": "b755e34cdb14a6de15ccb50605786c9ecacbfc1f"
}, },
{ {
"ImportPath": "github.com/mesos/mesos-go/scheduler", "ImportPath": "github.com/mesos/mesos-go/scheduler",
"Comment": "before-0.26-protos-14-g4a7554a", "Comment": "before-0.26-protos-29-gb755e34",
"Rev": "4a7554aad396c70d19c9fc3469980547c9f117ae" "Rev": "b755e34cdb14a6de15ccb50605786c9ecacbfc1f"
}, },
{ {
"ImportPath": "github.com/mesos/mesos-go/upid", "ImportPath": "github.com/mesos/mesos-go/upid",
"Comment": "before-0.26-protos-14-g4a7554a", "Comment": "before-0.26-protos-29-gb755e34",
"Rev": "4a7554aad396c70d19c9fc3469980547c9f117ae" "Rev": "b755e34cdb14a6de15ccb50605786c9ecacbfc1f"
}, },
{ {
"ImportPath": "github.com/miekg/dns", "ImportPath": "github.com/miekg/dns",
......
...@@ -3,6 +3,7 @@ package auth ...@@ -3,6 +3,7 @@ package auth
import ( import (
"errors" "errors"
"fmt" "fmt"
"time"
"github.com/mesos/mesos-go/auth/callback" "github.com/mesos/mesos-go/auth/callback"
"github.com/mesos/mesos-go/upid" "github.com/mesos/mesos-go/upid"
...@@ -38,8 +39,14 @@ func Login(ctx context.Context, handler callback.Handler) error { ...@@ -38,8 +39,14 @@ func Login(ctx context.Context, handler callback.Handler) error {
type loginKeyType int type loginKeyType int
const ( const (
loginProviderNameKey loginKeyType = iota // name of login provider to use // name of login provider to use
parentUpidKey // upid.UPID of some parent process loginProviderNameKey loginKeyType = iota
// upid.UPID of some parent process
parentUpidKey
// time.Duration that limits the overall duration of an auth attempt
timeoutKey
) )
// Return a context that inherits all values from the parent ctx and specifies // Return a context that inherits all values from the parent ctx and specifies
...@@ -74,7 +81,20 @@ func ParentUPIDFrom(ctx context.Context) (pid upid.UPID, ok bool) { ...@@ -74,7 +81,20 @@ func ParentUPIDFrom(ctx context.Context) (pid upid.UPID, ok bool) {
func ParentUPID(ctx context.Context) (upid *upid.UPID) { func ParentUPID(ctx context.Context) (upid *upid.UPID) {
if upid, ok := ParentUPIDFrom(ctx); ok { if upid, ok := ParentUPIDFrom(ctx); ok {
return &upid return &upid
} else {
return nil
} }
return nil
}
func TimeoutFrom(ctx context.Context) (d time.Duration, ok bool) {
d, ok = ctx.Value(timeoutKey).(time.Duration)
return
}
func Timeout(ctx context.Context) (d time.Duration) {
d, _ = TimeoutFrom(ctx)
return
}
func WithTimeout(ctx context.Context, d time.Duration) context.Context {
return context.WithValue(ctx, timeoutKey, d)
} }
...@@ -22,6 +22,7 @@ It has these top-level messages: ...@@ -22,6 +22,7 @@ It has these top-level messages:
InternalMasterChangeDetected InternalMasterChangeDetected
InternalTryAuthentication InternalTryAuthentication
InternalAuthenticationResult InternalAuthenticationResult
InternalNetworkError
FrameworkID FrameworkID
OfferID OfferID
SlaveID SlaveID
......
...@@ -75,3 +75,29 @@ func (m *InternalAuthenticationResult) GetPid() string { ...@@ -75,3 +75,29 @@ func (m *InternalAuthenticationResult) GetPid() string {
} }
return "" return ""
} }
type InternalNetworkError struct {
// master pid that this event pertains to
Pid *string `protobuf:"bytes,1,req,name=pid" json:"pid,omitempty"`
// driver session UUID
Session *string `protobuf:"bytes,2,opt,name=session" json:"session,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *InternalNetworkError) Reset() { *m = InternalNetworkError{} }
func (m *InternalNetworkError) String() string { return proto.CompactTextString(m) }
func (*InternalNetworkError) ProtoMessage() {}
func (m *InternalNetworkError) GetPid() string {
if m != nil && m.Pid != nil {
return *m.Pid
}
return ""
}
func (m *InternalNetworkError) GetSession() string {
if m != nil && m.Session != nil {
return *m.Session
}
return ""
}
...@@ -21,3 +21,10 @@ message InternalAuthenticationResult { ...@@ -21,3 +21,10 @@ message InternalAuthenticationResult {
// master pid that this result pertains to // master pid that this result pertains to
required string pid = 3; required string pid = 3;
} }
message InternalNetworkError {
// master pid that this event pertains to
required string pid = 1;
// driver session UUID
optional string session = 2;
}
...@@ -20,9 +20,6 @@ import ( ...@@ -20,9 +20,6 @@ import (
) )
const ( const (
DefaultReadTimeout = 5 * time.Second
DefaultWriteTimeout = 5 * time.Second
// writeFlushPeriod is the amount of time we're willing to wait for a single // writeFlushPeriod is the amount of time we're willing to wait for a single
// response buffer to be fully written to the underlying TCP connection; after // response buffer to be fully written to the underlying TCP connection; after
// this amount of time the remaining bytes of the response are discarded. see // this amount of time the remaining bytes of the response are discarded. see
...@@ -43,13 +40,8 @@ func (did *decoderID) next() decoderID { ...@@ -43,13 +40,8 @@ func (did *decoderID) next() decoderID {
var ( var (
errHijackFailed = errors.New("failed to hijack http connection") errHijackFailed = errors.New("failed to hijack http connection")
did decoderID // decoder ID counter did decoderID // decoder ID counter
closedChan = make(chan struct{})
) )
func init() {
close(closedChan)
}
type Decoder interface { type Decoder interface {
Requests() <-chan *Request Requests() <-chan *Request
Err() <-chan error Err() <-chan error
...@@ -99,8 +91,8 @@ func DecodeHTTP(w http.ResponseWriter, r *http.Request) Decoder { ...@@ -99,8 +91,8 @@ func DecodeHTTP(w http.ResponseWriter, r *http.Request) Decoder {
req: r, req: r,
shouldQuit: make(chan struct{}), shouldQuit: make(chan struct{}),
forceQuit: make(chan struct{}), forceQuit: make(chan struct{}),
readTimeout: DefaultReadTimeout, readTimeout: ReadTimeout,
writeTimeout: DefaultWriteTimeout, writeTimeout: WriteTimeout,
idtag: id.String(), idtag: id.String(),
outCh: make(chan *bytes.Buffer), outCh: make(chan *bytes.Buffer),
} }
......
...@@ -30,6 +30,7 @@ import ( ...@@ -30,6 +30,7 @@ import (
mesos "github.com/mesos/mesos-go/mesosproto" mesos "github.com/mesos/mesos-go/mesosproto"
"github.com/mesos/mesos-go/mesosproto/scheduler" "github.com/mesos/mesos-go/mesosproto/scheduler"
"github.com/mesos/mesos-go/mesosutil/process" "github.com/mesos/mesos-go/mesosutil/process"
"github.com/mesos/mesos-go/messenger/sessionid"
"github.com/mesos/mesos-go/upid" "github.com/mesos/mesos-go/upid"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
...@@ -52,16 +53,19 @@ type Messenger interface { ...@@ -52,16 +53,19 @@ type Messenger interface {
UPID() upid.UPID UPID() upid.UPID
} }
type errorHandlerFunc func(context.Context, *Message, error) error
type dispatchFunc func(errorHandlerFunc)
// MesosMessenger is an implementation of the Messenger interface. // MesosMessenger is an implementation of the Messenger interface.
type MesosMessenger struct { type MesosMessenger struct {
upid upid.UPID upid upid.UPID
encodingQueue chan *Message sendingQueue chan dispatchFunc
sendingQueue chan *Message
installedMessages map[string]reflect.Type installedMessages map[string]reflect.Type
installedHandlers map[string]MessageHandler installedHandlers map[string]MessageHandler
stop chan struct{} stop chan struct{}
stopOnce sync.Once stopOnce sync.Once
tr Transporter tr Transporter
guardHandlers sync.RWMutex // protect simultaneous changes to messages/handlers maps
} }
// ForHostname creates a new default messenger (HTTP), using UPIDBindingAddress to // ForHostname creates a new default messenger (HTTP), using UPIDBindingAddress to
...@@ -137,18 +141,17 @@ func UPIDBindingAddress(hostname string, bindingAddress net.IP) (string, error) ...@@ -137,18 +141,17 @@ func UPIDBindingAddress(hostname string, bindingAddress net.IP) (string, error)
} }
// NewMesosMessenger creates a new mesos messenger. // NewMesosMessenger creates a new mesos messenger.
func NewHttp(upid upid.UPID) *MesosMessenger { func NewHttp(upid upid.UPID, opts ...httpOpt) *MesosMessenger {
return NewHttpWithBindingAddress(upid, nil) return NewHttpWithBindingAddress(upid, nil, opts...)
} }
func NewHttpWithBindingAddress(upid upid.UPID, address net.IP) *MesosMessenger { func NewHttpWithBindingAddress(upid upid.UPID, address net.IP, opts ...httpOpt) *MesosMessenger {
return New(upid, NewHTTPTransporter(upid, address)) return New(NewHTTPTransporter(upid, address, opts...))
} }
func New(upid upid.UPID, t Transporter) *MesosMessenger { func New(t Transporter) *MesosMessenger {
return &MesosMessenger{ return &MesosMessenger{
encodingQueue: make(chan *Message, defaultQueueSize), sendingQueue: make(chan dispatchFunc, defaultQueueSize),
sendingQueue: make(chan *Message, defaultQueueSize),
installedMessages: make(map[string]reflect.Type), installedMessages: make(map[string]reflect.Type),
installedHandlers: make(map[string]MessageHandler), installedHandlers: make(map[string]MessageHandler),
tr: t, tr: t,
...@@ -168,6 +171,10 @@ func (m *MesosMessenger) Install(handler MessageHandler, msg proto.Message) erro ...@@ -168,6 +171,10 @@ func (m *MesosMessenger) Install(handler MessageHandler, msg proto.Message) erro
if _, ok := m.installedMessages[name]; ok { if _, ok := m.installedMessages[name]; ok {
return fmt.Errorf("Message %v is already installed", name) return fmt.Errorf("Message %v is already installed", name)
} }
m.guardHandlers.Lock()
defer m.guardHandlers.Unlock()
m.installedMessages[name] = mtype.Elem() m.installedMessages[name] = mtype.Elem()
m.installedHandlers[name] = handler m.installedHandlers[name] = handler
m.tr.Install(name) m.tr.Install(name)
...@@ -184,12 +191,27 @@ func (m *MesosMessenger) Send(ctx context.Context, upid *upid.UPID, msg proto.Me ...@@ -184,12 +191,27 @@ func (m *MesosMessenger) Send(ctx context.Context, upid *upid.UPID, msg proto.Me
} else if *upid == m.upid { } else if *upid == m.upid {
return fmt.Errorf("Send the message to self") return fmt.Errorf("Send the message to self")
} }
b, err := proto.Marshal(msg)
if err != nil {
return err
}
name := getMessageName(msg) name := getMessageName(msg)
log.V(2).Infof("Sending message %v to %v\n", name, upid) log.V(2).Infof("Sending message %v to %v\n", name, upid)
wrapped := &Message{upid, name, msg, b}
d := dispatchFunc(func(rf errorHandlerFunc) {
err := m.tr.Send(ctx, wrapped)
err = rf(ctx, wrapped, err)
if err != nil {
m.reportError("send", wrapped, err)
}
})
select { select {
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
case m.encodingQueue <- &Message{upid, name, msg, nil}: case m.sendingQueue <- d:
return nil return nil
} }
} }
...@@ -206,14 +228,18 @@ func (m *MesosMessenger) Route(ctx context.Context, upid *upid.UPID, msg proto.M ...@@ -206,14 +228,18 @@ func (m *MesosMessenger) Route(ctx context.Context, upid *upid.UPID, msg proto.M
return m.Send(ctx, upid, msg) return m.Send(ctx, upid, msg)
} }
// TODO(jdef) this has an unfortunate performance impact for self-messaging. implement
// something more reasonable here.
data, err := proto.Marshal(msg)
if err != nil {
return err
}
name := getMessageName(msg) name := getMessageName(msg)
return m.tr.Inject(ctx, &Message{upid, name, msg, data}) log.V(2).Infof("routing message %q to self", name)
_, handler, ok := m.messageBinding(name)
if !ok {
return fmt.Errorf("failed to route message, no message binding for %q", name)
}
// the implication of this is that messages can be delivered to self even if the
// messenger has been stopped. is that OK?
go handler(upid, msg)
return nil
} }
// Start starts the messenger; expects to be called once and only once. // Start starts the messenger; expects to be called once and only once.
...@@ -231,7 +257,6 @@ func (m *MesosMessenger) Start() error { ...@@ -231,7 +257,6 @@ func (m *MesosMessenger) Start() error {
m.upid = pid m.upid = pid
go m.sendLoop() go m.sendLoop()
go m.encodeLoop()
go m.decodeLoop() go m.decodeLoop()
// wait for a listener error or a stop signal; either way stop the messenger // wait for a listener error or a stop signal; either way stop the messenger
...@@ -267,7 +292,7 @@ func (m *MesosMessenger) Stop() (err error) { ...@@ -267,7 +292,7 @@ func (m *MesosMessenger) Stop() (err error) {
defer close(m.stop) defer close(m.stop)
} }
log.Info("stopping messenger..") log.Infof("stopping messenger %v..", m.upid)
//TODO(jdef) don't hardcode the graceful flag here //TODO(jdef) don't hardcode the graceful flag here
if err2 := m.tr.Stop(true); err2 != nil && err2 != errTerminal { if err2 := m.tr.Stop(true); err2 != nil && err2 != errTerminal {
...@@ -283,53 +308,14 @@ func (m *MesosMessenger) UPID() upid.UPID { ...@@ -283,53 +308,14 @@ func (m *MesosMessenger) UPID() upid.UPID {
return m.upid return m.upid
} }
func (m *MesosMessenger) encodeLoop() { func (m *MesosMessenger) reportError(action string, msg *Message, err error) {
for { // log message transmission errors but don't shoot the messenger.
select { // this approach essentially drops all undelivered messages on the floor.
case <-m.stop: name := ""
return if msg != nil {
case msg := <-m.encodingQueue: name = msg.Name
e := func() error {
//TODO(jdef) implement timeout for context
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
b, err := proto.Marshal(msg.ProtoMessage)
if err != nil {
return err
}
msg.Bytes = b
select {
case <-ctx.Done():
return ctx.Err()
case m.sendingQueue <- msg:
return nil
}
}()
if e != nil {
m.reportError(fmt.Errorf("Failed to enqueue message %v: %v", msg, e))
}
}
}
}
func (m *MesosMessenger) reportError(err error) {
log.V(2).Info(err)
//TODO(jdef) implement timeout for context
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
c := make(chan error, 1)
pid := m.upid
go func() { c <- m.Route(ctx, &pid, &mesos.FrameworkErrorMessage{Message: proto.String(err.Error())}) }()
select {
case <-ctx.Done():
<-c // wait for Route to return
case e := <-c:
if e != nil {
log.Errorf("failed to report error %v due to: %v", err, e)
}
} }
log.Errorf("failed to %s message %q: %+v", action, name, err)
} }
func (m *MesosMessenger) sendLoop() { func (m *MesosMessenger) sendLoop() {
...@@ -337,27 +323,28 @@ func (m *MesosMessenger) sendLoop() { ...@@ -337,27 +323,28 @@ func (m *MesosMessenger) sendLoop() {
select { select {
case <-m.stop: case <-m.stop:
return return
case msg := <-m.sendingQueue: case f := <-m.sendingQueue:
e := func() error { f(errorHandlerFunc(func(ctx context.Context, msg *Message, err error) error {
//TODO(jdef) implement timeout for context if _, ok := err.(*networkError); ok {
ctx, cancel := context.WithCancel(context.TODO()) // if transport reports a network error, then
defer cancel() // we're probably disconnected from the remote process?
pid := msg.UPID.String()
c := make(chan error, 1) neterr := &mesos.InternalNetworkError{Pid: &pid}
go func() { c <- m.tr.Send(ctx, msg) }() sessionID, ok := sessionid.FromContext(ctx)
if ok {
select { neterr.Session = &sessionID
case <-ctx.Done(): }
// Transport layer must use the context to detect cancelled requests. log.V(1).Infof("routing network error for pid %q session %q", pid, sessionID)
<-c // wait for Send to return err2 := m.Route(ctx, &m.upid, neterr)
return ctx.Err() if err2 != nil {
case err := <-c: log.Error(err2)
return err } else {
log.V(1).Infof("swallowing raw error because we're reporting a networkError: %v", err)
return nil
}
} }
}() return err
if e != nil { }))
m.reportError(fmt.Errorf("Failed to send message %v: %v", msg.Name, e))
}
} }
} }
} }
...@@ -379,17 +366,42 @@ func (m *MesosMessenger) decodeLoop() { ...@@ -379,17 +366,42 @@ func (m *MesosMessenger) decodeLoop() {
panic(fmt.Sprintf("unexpected transport error: %v", err)) panic(fmt.Sprintf("unexpected transport error: %v", err))
} }
} }
log.V(2).Infof("Receiving message %v from %v\n", msg.Name, msg.UPID) log.V(2).Infof("Receiving message %v from %v\n", msg.Name, msg.UPID)
msg.ProtoMessage = reflect.New(m.installedMessages[msg.Name]).Interface().(proto.Message) protoMessage, handler, found := m.messageBinding(msg.Name)
if !found {
log.Warningf("no message binding for message %q", msg.Name)
continue
}
msg.ProtoMessage = protoMessage
if err := proto.Unmarshal(msg.Bytes, msg.ProtoMessage); err != nil { if err := proto.Unmarshal(msg.Bytes, msg.ProtoMessage); err != nil {
log.Errorf("Failed to unmarshal message %v: %v\n", msg, err) log.Errorf("Failed to unmarshal message %v: %v\n", msg, err)
continue continue
} }
// TODO(yifan): Catch panic.
m.installedHandlers[msg.Name](msg.UPID, msg.ProtoMessage) handler(msg.UPID, msg.ProtoMessage)
} }
} }
func (m *MesosMessenger) messageBinding(name string) (proto.Message, MessageHandler, bool) {
m.guardHandlers.RLock()
defer m.guardHandlers.RUnlock()
gotype, ok := m.installedMessages[name]
if !ok {
return nil, nil, false
}
handler, ok := m.installedHandlers[name]
if !ok {
return nil, nil, false
}
protoMessage := reflect.New(gotype).Interface().(proto.Message)
return protoMessage, handler, true
}
// getMessageName returns the name of the message in the mesos manner. // getMessageName returns the name of the message in the mesos manner.
func getMessageName(msg proto.Message) string { func getMessageName(msg proto.Message) string {
var msgName string var msgName string
......
package sessionid
import (
"golang.org/x/net/context"
)
type key int
const sessionIDKey = 0
func NewContext(ctx context.Context, sessionID string) context.Context {
return context.WithValue(ctx, sessionIDKey, sessionID)
}
func FromContext(ctx context.Context) (string, bool) {
sessionID, ok := ctx.Value(sessionIDKey).(string)
return sessionID, ok
}
...@@ -33,11 +33,6 @@ type Transporter interface { ...@@ -33,11 +33,6 @@ type Transporter interface {
//Will stop receiving when transport is stopped. //Will stop receiving when transport is stopped.
Recv() (*Message, error) Recv() (*Message, error)
//Inject injects a message to the incoming queue. Must use context to
//determine cancelled requests. Injection is aborted if the transport
//is stopped.
Inject(ctx context.Context, msg *Message) error
//Install mount an handler based on incoming message name. //Install mount an handler based on incoming message name.
Install(messageName string) Install(messageName string)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment