155 lines
4.3 KiB
Go
155 lines
4.3 KiB
Go
package detector
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
log "github.com/golang/glog"
|
|
mesos "github.com/mesos/mesos-go/mesosproto"
|
|
util "github.com/mesos/mesos-go/mesosutil"
|
|
"github.com/mesos/mesos-go/upid"
|
|
)
|
|
|
|
var (
|
|
pluginLock sync.Mutex
|
|
plugins = map[string]PluginFactory{}
|
|
EmptySpecError = errors.New("empty master specification")
|
|
|
|
defaultFactory = PluginFactory(func(spec string) (Master, error) {
|
|
if len(spec) == 0 {
|
|
return nil, EmptySpecError
|
|
}
|
|
if strings.Index(spec, "@") < 0 {
|
|
spec = "master@" + spec
|
|
}
|
|
if pid, err := upid.Parse(spec); err == nil {
|
|
return NewStandalone(CreateMasterInfo(pid)), nil
|
|
} else {
|
|
return nil, err
|
|
}
|
|
})
|
|
)
|
|
|
|
type PluginFactory func(string) (Master, error)
|
|
|
|
// associates a plugin implementation with a Master specification prefix.
|
|
// packages that provide plugins are expected to invoke this func within
|
|
// their init() implementation. schedulers that wish to support plugins may
|
|
// anonymously import ("_") a package the auto-registers said plugins.
|
|
func Register(prefix string, f PluginFactory) error {
|
|
if prefix == "" {
|
|
return fmt.Errorf("illegal prefix: '%v'", prefix)
|
|
}
|
|
if f == nil {
|
|
return fmt.Errorf("nil plugin factories are not allowed")
|
|
}
|
|
|
|
pluginLock.Lock()
|
|
defer pluginLock.Unlock()
|
|
|
|
if _, found := plugins[prefix]; found {
|
|
return fmt.Errorf("detection plugin already registered for prefix '%s'", prefix)
|
|
}
|
|
plugins[prefix] = f
|
|
return nil
|
|
}
|
|
|
|
// Create a new detector given the provided specification. Examples are:
|
|
//
|
|
// - file://{path_to_local_file}
|
|
// - {ipaddress}:{port}
|
|
// - master@{ip_address}:{port}
|
|
// - master({id})@{ip_address}:{port}
|
|
//
|
|
// Support for the file:// prefix is intentionally hardcoded so that it may
|
|
// not be inadvertently overridden by a custom plugin implementation. Custom
|
|
// plugins are supported via the Register and MatchingPlugin funcs.
|
|
//
|
|
// Furthermore it is expected that master detectors returned from this func
|
|
// are not yet running and will only begin to spawn requisite background
|
|
// processing upon, or some time after, the first invocation of their Detect.
|
|
//
|
|
func New(spec string) (m Master, err error) {
|
|
if strings.HasPrefix(spec, "file://") {
|
|
var body []byte
|
|
path := spec[7:]
|
|
body, err = ioutil.ReadFile(path)
|
|
if err != nil {
|
|
log.V(1).Infof("failed to read from file at '%s'", path)
|
|
} else {
|
|
m, err = New(string(body))
|
|
}
|
|
} else if f, ok := MatchingPlugin(spec); ok {
|
|
m, err = f(spec)
|
|
} else {
|
|
m, err = defaultFactory(spec)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func MatchingPlugin(spec string) (PluginFactory, bool) {
|
|
pluginLock.Lock()
|
|
defer pluginLock.Unlock()
|
|
|
|
for prefix, f := range plugins {
|
|
if strings.HasPrefix(spec, prefix) {
|
|
return f, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// Super-useful utility func that attempts to build a mesos.MasterInfo from a
|
|
// upid.UPID specification. An attempt is made to determine the IP address of
|
|
// the UPID's Host and any errors during such resolution will result in a nil
|
|
// returned result. A nil result is also returned upon errors parsing the Port
|
|
// specification of the UPID.
|
|
//
|
|
// TODO(jdef) make this a func of upid.UPID so that callers can invoke somePid.MasterInfo()?
|
|
//
|
|
func CreateMasterInfo(pid *upid.UPID) *mesos.MasterInfo {
|
|
if pid == nil {
|
|
return nil
|
|
}
|
|
port, err := strconv.Atoi(pid.Port)
|
|
if err != nil {
|
|
log.Errorf("failed to parse port: %v", err)
|
|
return nil
|
|
}
|
|
//TODO(jdef) what about (future) ipv6 support?
|
|
var ipv4 net.IP
|
|
if ipv4 = net.ParseIP(pid.Host); ipv4 != nil {
|
|
// This is needed for the people cross-compiling from macos to linux.
|
|
// The cross-compiled version of net.LookupIP() fails to handle plain IPs.
|
|
// See https://github.com/mesos/mesos-go/pull/117
|
|
} else if addrs, err := net.LookupIP(pid.Host); err == nil {
|
|
for _, ip := range addrs {
|
|
if ip = ip.To4(); ip != nil {
|
|
ipv4 = ip
|
|
break
|
|
}
|
|
}
|
|
if ipv4 == nil {
|
|
log.Errorf("host does not resolve to an IPv4 address: %v", pid.Host)
|
|
return nil
|
|
}
|
|
} else {
|
|
log.Errorf("failed to lookup IPs for host '%v': %v", pid.Host, err)
|
|
return nil
|
|
}
|
|
packedip := binary.BigEndian.Uint32(ipv4) // network byte order is big-endian
|
|
mi := util.NewMasterInfo(pid.ID, packedip, uint32(port))
|
|
mi.Pid = proto.String(pid.String())
|
|
if pid.Host != "" {
|
|
mi.Hostname = proto.String(pid.Host)
|
|
}
|
|
return mi
|
|
}
|