Commit a3430ad0 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #47232 from gyliu513/remove-mesos-cp

Automatic merge from submit-queue Removed mesos as cloud provider from Kubernetes. **What this PR does / why we need it**: **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #47205 **Special notes for your reviewer**: **Release note**: ```release-note Move Mesos Cloud Provider out of Kubernetes Repo ```
parents 15919758 9ef82665
......@@ -43,7 +43,6 @@ var cloudproviders = []string{
"azure",
"cloudstack",
"gce",
"mesos",
"openstack",
"ovirt",
"photon",
......
......@@ -16,7 +16,6 @@ go_library(
"//pkg/cloudprovider/providers/azure:go_default_library",
"//pkg/cloudprovider/providers/cloudstack:go_default_library",
"//pkg/cloudprovider/providers/gce:go_default_library",
"//pkg/cloudprovider/providers/mesos:go_default_library",
"//pkg/cloudprovider/providers/openstack:go_default_library",
"//pkg/cloudprovider/providers/ovirt:go_default_library",
"//pkg/cloudprovider/providers/photon:go_default_library",
......@@ -41,7 +40,6 @@ filegroup(
"//pkg/cloudprovider/providers/cloudstack:all-srcs",
"//pkg/cloudprovider/providers/fake:all-srcs",
"//pkg/cloudprovider/providers/gce:all-srcs",
"//pkg/cloudprovider/providers/mesos:all-srcs",
"//pkg/cloudprovider/providers/openstack:all-srcs",
"//pkg/cloudprovider/providers/ovirt:all-srcs",
"//pkg/cloudprovider/providers/photon:all-srcs",
......
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"client.go",
"config.go",
"mesos.go",
"plugins.go",
],
tags = ["automanaged"],
deps = [
"//pkg/cloudprovider:go_default_library",
"//pkg/controller:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/mesos/mesos-go/detector:go_default_library",
"//vendor/github.com/mesos/mesos-go/detector/zoo:go_default_library",
"//vendor/github.com/mesos/mesos-go/mesosproto:go_default_library",
"//vendor/golang.org/x/net/context:go_default_library",
"//vendor/gopkg.in/gcfg.v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"client_test.go",
"config_test.go",
"mesos_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/cloudprovider:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/mesos/mesos-go/detector:go_default_library",
"//vendor/github.com/mesos/mesos-go/mesosutil:go_default_library",
"//vendor/golang.org/x/net/context:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mesos
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"testing"
"time"
log "github.com/golang/glog"
"github.com/mesos/mesos-go/detector"
"github.com/mesos/mesos-go/mesosutil"
"golang.org/x/net/context"
utilnet "k8s.io/apimachinery/pkg/util/net"
)
// Test data
const (
TEST_MASTER_ID = "master-12345"
TEST_MASTER_IP = 177048842 // 10.141.141.10
TEST_MASTER_PORT = 5050
TEST_STATE_JSON = `
{
"version": "0.22.0",
"unregistered_frameworks": [],
"started_tasks": 0,
"start_time": 1429456501.61141,
"staged_tasks": 0,
"slaves": [
{
"resources": {
"ports": "[31000-32000]",
"mem": 15360,
"disk": 470842,
"cpus": 8
},
"registered_time": 1429456502.46999,
"pid": "slave(1)@mesos1.internal.example.org.fail:5050",
"id": "20150419-081501-16777343-5050-16383-S2",
"hostname": "mesos1.internal.example.org.fail",
"attributes": {},
"active": true
},
{
"resources": {
"ports": "[31000-32000]",
"mem": 15360,
"disk": 470842,
"cpus": 8
},
"registered_time": 1429456502.4144,
"pid": "slave(1)@mesos2.internal.example.org.fail:5050",
"id": "20150419-081501-16777343-5050-16383-S1",
"hostname": "mesos2.internal.example.org.fail",
"attributes": {},
"active": true
},
{
"resources": {
"ports": "[31000-32000]",
"mem": 15360,
"disk": 470842,
"cpus": 8
},
"registered_time": 1429456502.02879,
"pid": "slave(1)@mesos3.internal.example.org.fail:5050",
"id": "20150419-081501-16777343-5050-16383-S0",
"hostname": "mesos3.internal.example.org.fail",
"attributes": {},
"active": true
}
],
"pid": "master@mesos-master0.internal.example.org.fail:5050",
"orphan_tasks": [],
"lost_tasks": 0,
"leader": "master@mesos-master0.internal.example.org.fail:5050",
"killed_tasks": 0,
"failed_tasks": 0,
"elected_time": 1429456501.61638,
"deactivated_slaves": 0,
"completed_frameworks": [],
"build_user": "buildbot",
"build_time": 1425085311,
"build_date": "2015-02-27 17:01:51",
"activated_slaves": 3,
"finished_tasks": 0,
"flags": {
"zk_session_timeout": "10secs",
"work_dir": "/somepath/mesos/local/Lc9arz",
"webui_dir": "/usr/local/share/mesos/webui",
"version": "false",
"user_sorter": "drf",
"slave_reregister_timeout": "10mins",
"logbufsecs": "0",
"log_auto_initialize": "true",
"initialize_driver_logging": "true",
"framework_sorter": "drf",
"authenticators": "crammd5",
"authenticate_slaves": "false",
"authenticate": "false",
"allocation_interval": "1secs",
"logging_level": "INFO",
"quiet": "false",
"recovery_slave_removal_limit": "100%",
"registry": "replicated_log",
"registry_fetch_timeout": "1mins",
"registry_store_timeout": "5secs",
"registry_strict": "false",
"root_submissions": "true"
},
"frameworks": [],
"git_branch": "refs/heads/0.22.0-rc1",
"git_sha": "46834faca67f877631e1beb7d61be5c080ec3dc2",
"git_tag": "0.22.0-rc1",
"hostname": "localhost",
"id": "20150419-081501-16777343-5050-16383"
}`
)
// Mocks
type FakeMasterDetector struct {
callback detector.MasterChanged
done chan struct{}
}
func newFakeMasterDetector() *FakeMasterDetector {
return &FakeMasterDetector{
done: make(chan struct{}),
}
}
func (md FakeMasterDetector) Cancel() {
close(md.done)
}
func (md FakeMasterDetector) Detect(cb detector.MasterChanged) error {
md.callback = cb
leadingMaster := mesosutil.NewMasterInfo(TEST_MASTER_ID, TEST_MASTER_IP, TEST_MASTER_PORT)
cb.OnMasterChanged(leadingMaster)
return nil
}
func (md FakeMasterDetector) Done() <-chan struct{} {
return md.done
}
// Auxiliary functions
func makeHttpMocks() (*httptest.Server, *http.Client, *http.Transport) {
httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.V(4).Infof("Mocking response for HTTP request: %#v", r)
if r.URL.Path == "/state.json" {
w.WriteHeader(200) // OK
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, TEST_STATE_JSON)
} else {
w.WriteHeader(400)
fmt.Fprintln(w, "Bad Request")
}
}))
// Intercept all client requests and feed them to the test server
transport := utilnet.SetTransportDefaults(&http.Transport{
Proxy: func(req *http.Request) (*url.URL, error) {
return url.Parse(httpServer.URL)
},
})
httpClient := &http.Client{Transport: transport}
return httpServer, httpClient, transport
}
// Tests
// test mesos.parseMesosState
func Test_parseMesosState(t *testing.T) {
state, err := parseMesosState([]byte(TEST_STATE_JSON))
if err != nil {
t.Fatalf("parseMesosState does not yield an error")
}
if state == nil {
t.Fatalf("parseMesosState yields a non-nil state")
}
if len(state.nodes) != 3 {
t.Fatalf("parseMesosState yields a state with 3 nodes")
}
}
// test mesos.listSlaves
func Test_listSlaves(t *testing.T) {
defer log.Flush()
md := FakeMasterDetector{}
httpServer, httpClient, httpTransport := makeHttpMocks()
defer httpServer.Close()
cacheTTL := 500 * time.Millisecond
mesosClient, err := createMesosClient(md, httpClient, httpTransport, cacheTTL)
if err != nil {
t.Fatalf("createMesosClient does not yield an error")
}
slaveNodes, err := mesosClient.listSlaves(context.TODO())
if err != nil {
t.Fatalf("listSlaves does not yield an error")
}
if len(slaveNodes) != 3 {
t.Fatalf("listSlaves yields a collection of size 3")
}
expectedHostnames := map[string]struct{}{
"mesos1.internal.example.org.fail": {},
"mesos2.internal.example.org.fail": {},
"mesos3.internal.example.org.fail": {},
}
actualHostnames := make(map[string]struct{})
for _, node := range slaveNodes {
actualHostnames[node.hostname] = struct{}{}
}
if !reflect.DeepEqual(expectedHostnames, actualHostnames) {
t.Fatalf("listSlaves yields a collection with the expected hostnames")
}
}
// test mesos.clusterName
func Test_clusterName(t *testing.T) {
defer log.Flush()
md := FakeMasterDetector{}
httpServer, httpClient, httpTransport := makeHttpMocks()
defer httpServer.Close()
cacheTTL := 500 * time.Millisecond
mesosClient, err := createMesosClient(md, httpClient, httpTransport, cacheTTL)
name, err := mesosClient.clusterName(context.TODO())
if err != nil {
t.Fatalf("clusterName does not yield an error")
}
if name != defaultClusterName {
t.Fatalf("clusterName yields the expected (default) value")
}
}
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mesos
import (
"io"
"time"
"gopkg.in/gcfg.v1"
)
const (
DefaultMesosMaster = "localhost:5050"
DefaultHttpClientTimeout = time.Duration(10) * time.Second
DefaultStateCacheTTL = time.Duration(5) * time.Second
)
// Example Mesos cloud provider configuration file:
//
// [mesos-cloud]
// mesos-master = leader.mesos:5050
// http-client-timeout = 500ms
// state-cache-ttl = 1h
type ConfigWrapper struct {
Mesos_Cloud Config
}
type Config struct {
MesosMaster string `gcfg:"mesos-master"`
MesosHttpClientTimeout Duration `gcfg:"http-client-timeout"`
StateCacheTTL Duration `gcfg:"state-cache-ttl"`
}
type Duration struct {
Duration time.Duration `gcfg:"duration"`
}
func (d *Duration) UnmarshalText(data []byte) error {
underlying, err := time.ParseDuration(string(data))
if err == nil {
d.Duration = underlying
}
return err
}
func createDefaultConfig() *Config {
return &Config{
MesosMaster: DefaultMesosMaster,
MesosHttpClientTimeout: Duration{Duration: DefaultHttpClientTimeout},
StateCacheTTL: Duration{Duration: DefaultStateCacheTTL},
}
}
func readConfig(configReader io.Reader) (*Config, error) {
config := createDefaultConfig()
wrapper := &ConfigWrapper{Mesos_Cloud: *config}
if configReader != nil {
if err := gcfg.ReadInto(wrapper, configReader); err != nil {
return nil, err
}
config = &(wrapper.Mesos_Cloud)
}
return config, nil
}
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mesos
import (
"bytes"
"testing"
"time"
log "github.com/golang/glog"
)
// test mesos.createDefaultConfig
func Test_createDefaultConfig(t *testing.T) {
defer log.Flush()
config := createDefaultConfig()
if config.MesosMaster != DefaultMesosMaster {
t.Fatalf("Default config has the expected MesosMaster value")
}
if config.MesosHttpClientTimeout.Duration != DefaultHttpClientTimeout {
t.Fatalf("Default config has the expected MesosHttpClientTimeout value")
}
if config.StateCacheTTL.Duration != DefaultStateCacheTTL {
t.Fatalf("Default config has the expected StateCacheTTL value")
}
}
// test mesos.readConfig
func Test_readConfig(t *testing.T) {
defer log.Flush()
configString := `
[mesos-cloud]
mesos-master = leader.mesos:5050
http-client-timeout = 500ms
state-cache-ttl = 1h`
reader := bytes.NewBufferString(configString)
config, err := readConfig(reader)
if err != nil {
t.Fatalf("Reading configuration does not yield an error: %#v", err)
}
if config.MesosMaster != "leader.mesos:5050" {
t.Fatalf("Parsed config has the expected MesosMaster value")
}
if config.MesosHttpClientTimeout.Duration != time.Duration(500)*time.Millisecond {
t.Fatalf("Parsed config has the expected MesosHttpClientTimeout value")
}
if config.StateCacheTTL.Duration != time.Duration(1)*time.Hour {
t.Fatalf("Parsed config has the expected StateCacheTTL value")
}
}
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mesos
import (
"errors"
"fmt"
"io"
"net"
"regexp"
"golang.org/x/net/context"
log "github.com/golang/glog"
"github.com/mesos/mesos-go/detector"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/controller"
)
const (
ProviderName = "mesos"
// KubernetesExecutorName is shared between contrib/mesos and Mesos cloud provider.
// Because cloud provider -> contrib dependencies are forbidden, this constant
// is defined here, not in contrib.
KubernetesExecutorName = "Kubelet-Executor"
)
var (
CloudProvider *MesosCloud
noHostNameSpecified = errors.New("No hostname specified")
)
func init() {
cloudprovider.RegisterCloudProvider(
ProviderName,
func(configReader io.Reader) (cloudprovider.Interface, error) {
provider, err := newMesosCloud(configReader)
if err == nil {
CloudProvider = provider
}
return provider, err
})
}
type MesosCloud struct {
client *mesosClient
config *Config
}
func (c *MesosCloud) MasterURI() string {
return c.config.MesosMaster
}
func newMesosCloud(configReader io.Reader) (*MesosCloud, error) {
config, err := readConfig(configReader)
if err != nil {
return nil, err
}
log.V(1).Infof("new mesos cloud, master='%v'", config.MesosMaster)
if d, err := detector.New(config.MesosMaster); err != nil {
log.V(1).Infof("failed to create master detector: %v", err)
return nil, err
} else if cl, err := newMesosClient(d,
config.MesosHttpClientTimeout.Duration,
config.StateCacheTTL.Duration); err != nil {
log.V(1).Infof("failed to create mesos cloud client: %v", err)
return nil, err
} else {
return &MesosCloud{client: cl, config: config}, nil
}
}
// Initialize passes a Kubernetes clientBuilder interface to the cloud provider
func (c *MesosCloud) Initialize(clientBuilder controller.ControllerClientBuilder) {}
// Implementation of Instances.CurrentNodeName
func (c *MesosCloud) CurrentNodeName(hostname string) (types.NodeName, error) {
return types.NodeName(hostname), nil
}
func (c *MesosCloud) AddSSHKeyToAllInstances(user string, keyData []byte) error {
return errors.New("unimplemented")
}
// Instances returns a copy of the Mesos cloud Instances implementation.
// Mesos natively provides minimal cloud-type resources. More robust cloud
// support requires a combination of Mesos and cloud-specific knowledge.
func (c *MesosCloud) Instances() (cloudprovider.Instances, bool) {
return c, true
}
// LoadBalancer always returns nil, false in this implementation.
// Mesos does not provide any type of native load balancing by default,
// so this implementation always returns (nil, false).
func (c *MesosCloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
return nil, false
}
// Zones always returns nil, false in this implementation.
// Mesos does not provide any type of native region or zone awareness,
// so this implementation always returns (nil, false).
func (c *MesosCloud) Zones() (cloudprovider.Zones, bool) {
return nil, false
}
// Clusters returns a copy of the Mesos cloud Clusters implementation.
// Mesos does not provide support for multiple clusters.
func (c *MesosCloud) Clusters() (cloudprovider.Clusters, bool) {
return c, true
}
// Routes always returns nil, false in this implementation.
func (c *MesosCloud) Routes() (cloudprovider.Routes, bool) {
return nil, false
}
// ProviderName returns the cloud provider ID.
func (c *MesosCloud) ProviderName() string {
return ProviderName
}
// ScrubDNS filters DNS settings for pods.
func (c *MesosCloud) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) {
return nameservers, searches
}
// ListClusters lists the names of the available Mesos clusters.
func (c *MesosCloud) ListClusters() ([]string, error) {
// Always returns a single cluster (this one!)
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
name, err := c.client.clusterName(ctx)
return []string{name}, err
}
// Master gets back the address (either DNS name or IP address) of the leading Mesos master node for the cluster.
func (c *MesosCloud) Master(clusterName string) (string, error) {
clusters, err := c.ListClusters()
if err != nil {
return "", err
}
for _, name := range clusters {
if name == clusterName {
if c.client.master == "" {
return "", errors.New("The currently leading master is unknown.")
}
host, _, err := net.SplitHostPort(c.client.master)
if err != nil {
return "", err
}
return host, nil
}
}
return "", fmt.Errorf("The supplied cluster '%v' does not exist", clusterName)
}
// ipAddress returns an IP address of the specified instance.
func ipAddress(name string) (net.IP, error) {
if name == "" {
return nil, noHostNameSpecified
}
ipaddr := net.ParseIP(name)
if ipaddr != nil {
return ipaddr, nil
}
iplist, err := net.LookupIP(name)
if err != nil {
log.V(2).Infof("failed to resolve IP from host name '%v': %v", name, err)
return nil, err
}
ipaddr = iplist[0]
log.V(2).Infof("resolved host '%v' to '%v'", name, ipaddr)
return ipaddr, nil
}
// mapNodeNameToPrivateDNSName maps a k8s NodeName to an mesos hostname.
// This is a simple string cast
func mapNodeNameToHostname(nodeName types.NodeName) string {
return string(nodeName)
}
// ExternalID returns the cloud provider ID of the instance with the specified nodeName (deprecated).
func (c *MesosCloud) ExternalID(nodeName types.NodeName) (string, error) {
hostname := mapNodeNameToHostname(nodeName)
//TODO(jdef) use a timeout here? 15s?
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
nodes, err := c.client.listSlaves(ctx)
if err != nil {
return "", err
}
node := nodes[hostname]
if node == nil {
return "", cloudprovider.InstanceNotFound
}
ip, err := ipAddress(node.hostname)
if err != nil {
return "", err
}
return ip.String(), nil
}
// InstanceID returns the cloud provider ID of the instance with the specified nodeName.
func (c *MesosCloud) InstanceID(nodeName types.NodeName) (string, error) {
return "", nil
}
// InstanceTypeByProviderID returns the cloudprovider instance type of the node with the specified unique providerID
// This method will not be called from the node that is requesting this ID. i.e. metadata service
// and other local methods cannot be used here
func (c *MesosCloud) InstanceTypeByProviderID(providerID string) (string, error) {
return "", errors.New("unimplemented")
}
// InstanceType returns the type of the instance with the specified nodeName.
func (c *MesosCloud) InstanceType(nodeName types.NodeName) (string, error) {
return "", nil
}
func (c *MesosCloud) listNodes() (map[string]*slaveNode, error) {
//TODO(jdef) use a timeout here? 15s?
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
nodes, err := c.client.listSlaves(ctx)
if err != nil {
return nil, err
}
if len(nodes) == 0 {
log.V(2).Info("no slaves found, are any running?")
return nil, nil
}
return nodes, nil
}
// List lists instances that match 'filter' which is a regular expression
// which must match the entire instance name (fqdn).
func (c *MesosCloud) List(filter string) ([]types.NodeName, error) {
nodes, err := c.listNodes()
if err != nil {
return nil, err
}
filterRegex, err := regexp.Compile(filter)
if err != nil {
return nil, err
}
names := []types.NodeName{}
for _, node := range nodes {
if filterRegex.MatchString(node.hostname) {
names = append(names, types.NodeName(node.hostname))
}
}
return names, nil
}
// ListWithKubelet list those instance which have no running kubelet, i.e. the
// Kubernetes executor.
func (c *MesosCloud) ListWithoutKubelet() ([]string, error) {
nodes, err := c.listNodes()
if err != nil {
return nil, err
}
addr := make([]string, 0, len(nodes))
for _, n := range nodes {
if !n.kubeletRunning {
addr = append(addr, n.hostname)
}
}
return addr, nil
}
// NodeAddresses returns the addresses of the instance with the specified nodeName.
func (c *MesosCloud) NodeAddresses(nodeName types.NodeName) ([]v1.NodeAddress, error) {
name := mapNodeNameToHostname(nodeName)
ip, err := ipAddress(name)
if err != nil {
return nil, err
}
return []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: ip.String()},
{Type: v1.NodeExternalIP, Address: ip.String()},
}, nil
}
// NodeAddressesByProviderID returns the node addresses of an instances with the specified unique providerID
// This method will not be called from the node that is requesting this ID. i.e. metadata service
// and other local methods cannot be used here
func (c *MesosCloud) NodeAddressesByProviderID(providerID string) ([]v1.NodeAddress, error) {
return []v1.NodeAddress{}, errors.New("unimplemented")
}
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mesos
import (
"bytes"
"net"
"reflect"
"testing"
"time"
log "github.com/golang/glog"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/cloudprovider"
)
func TestIPAddress(t *testing.T) {
expected4 := net.IPv4(127, 0, 0, 1)
ip, err := ipAddress("127.0.0.1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(ip, expected4) {
t.Fatalf("expected %#v instead of %#v", expected4, ip)
}
expected6 := net.ParseIP("::1")
if expected6 == nil {
t.Fatalf("failed to parse ipv6 ::1")
}
ip, err = ipAddress("::1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(ip, expected6) {
t.Fatalf("expected %#v instead of %#v", expected6, ip)
}
ip, err = ipAddress("localhost")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(ip, expected4) && !reflect.DeepEqual(ip, expected6) {
t.Fatalf("expected %#v or %#v instead of %#v", expected4, expected6, ip)
}
_, err = ipAddress("")
if err != noHostNameSpecified {
t.Fatalf("expected error noHostNameSpecified but got none")
}
}
// test mesos.newMesosCloud with no config
func Test_newMesosCloud_NoConfig(t *testing.T) {
defer log.Flush()
mesosCloud, err := newMesosCloud(nil)
if err != nil {
t.Fatalf("Creating a new Mesos cloud provider without config does not yield an error: %#v", err)
}
if mesosCloud.client.httpClient.Timeout != DefaultHttpClientTimeout {
t.Fatalf("Creating a new Mesos cloud provider without config does not yield an error: %#v", err)
}
if mesosCloud.client.state.ttl != DefaultStateCacheTTL {
t.Fatalf("Mesos client with default config has the expected state cache TTL value")
}
}
// test mesos.newMesosCloud with custom config
func Test_newMesosCloud_WithConfig(t *testing.T) {
defer log.Flush()
configString := `
[mesos-cloud]
http-client-timeout = 500ms
state-cache-ttl = 1h`
reader := bytes.NewBufferString(configString)
mesosCloud, err := newMesosCloud(reader)
if err != nil {
t.Fatalf("Creating a new Mesos cloud provider with a custom config does not yield an error: %#v", err)
}
if mesosCloud.client.httpClient.Timeout != time.Duration(500)*time.Millisecond {
t.Fatalf("Mesos client with a custom config has the expected HTTP client timeout value")
}
if mesosCloud.client.state.ttl != time.Duration(1)*time.Hour {
t.Fatalf("Mesos client with a custom config has the expected state cache TTL value")
}
}
// tests for capability reporting functions
// test mesos.Instances
func Test_Instances(t *testing.T) {
defer log.Flush()
mesosCloud, _ := newMesosCloud(nil)
instances, supports_instances := mesosCloud.Instances()
if !supports_instances || instances == nil {
t.Fatalf("MesosCloud provides an implementation of Instances")
}
}
// test mesos.LoadBalancer
func Test_TcpLoadBalancer(t *testing.T) {
defer log.Flush()
mesosCloud, _ := newMesosCloud(nil)
lb, supports_lb := mesosCloud.LoadBalancer()
if supports_lb || lb != nil {
t.Fatalf("MesosCloud does not provide an implementation of LoadBalancer")
}
}
// test mesos.Zones
func Test_Zones(t *testing.T) {
defer log.Flush()
mesosCloud, _ := newMesosCloud(nil)
zones, supports_zones := mesosCloud.Zones()
if supports_zones || zones != nil {
t.Fatalf("MesosCloud does not provide an implementation of Zones")
}
}
// test mesos.Clusters
func Test_Clusters(t *testing.T) {
defer log.Flush()
mesosCloud, _ := newMesosCloud(nil)
clusters, supports_clusters := mesosCloud.Clusters()
if !supports_clusters || clusters == nil {
t.Fatalf("MesosCloud does not provide an implementation of Clusters")
}
}
// test mesos.MasterURI
func Test_MasterURI(t *testing.T) {
defer log.Flush()
mesosCloud, _ := newMesosCloud(nil)
uri := mesosCloud.MasterURI()
if uri != DefaultMesosMaster {
t.Fatalf("MasterURI returns the expected master URI (expected \"localhost\", actual \"%s\"", uri)
}
}
// test mesos.ListClusters
func Test_ListClusters(t *testing.T) {
defer log.Flush()
md := FakeMasterDetector{}
httpServer, httpClient, httpTransport := makeHttpMocks()
defer httpServer.Close()
cacheTTL := 500 * time.Millisecond
mesosClient, err := createMesosClient(md, httpClient, httpTransport, cacheTTL)
mesosCloud := &MesosCloud{client: mesosClient, config: createDefaultConfig()}
clusters, err := mesosCloud.ListClusters()
if err != nil {
t.Fatalf("ListClusters does not yield an error: %#v", err)
}
if len(clusters) != 1 {
t.Fatalf("ListClusters should return a list of size 1: (actual: %#v)", clusters)
}
expectedClusterNames := []string{"mesos"}
if !reflect.DeepEqual(clusters, expectedClusterNames) {
t.Fatalf("ListClusters should return the expected list of names: (expected: %#v, actual: %#v)",
expectedClusterNames,
clusters)
}
}
// test mesos.Master
func Test_Master(t *testing.T) {
defer log.Flush()
md := FakeMasterDetector{}
httpServer, httpClient, httpTransport := makeHttpMocks()
defer httpServer.Close()
cacheTTL := 500 * time.Millisecond
mesosClient, err := createMesosClient(md, httpClient, httpTransport, cacheTTL)
mesosCloud := &MesosCloud{client: mesosClient, config: createDefaultConfig()}
clusters, err := mesosCloud.ListClusters()
clusterName := clusters[0]
master, err := mesosCloud.Master(clusterName)
if err != nil {
t.Fatalf("Master does not yield an error: %#v", err)
}
expectedMaster := unpackIPv4(TEST_MASTER_IP)
if master != expectedMaster {
t.Fatalf("Master returns the unexpected value: (expected: %#v, actual: %#v", expectedMaster, master)
}
}
// test mesos.List
func Test_List(t *testing.T) {
defer log.Flush()
md := FakeMasterDetector{}
httpServer, httpClient, httpTransport := makeHttpMocks()
defer httpServer.Close()
cacheTTL := 500 * time.Millisecond
mesosClient, err := createMesosClient(md, httpClient, httpTransport, cacheTTL)
mesosCloud := &MesosCloud{client: mesosClient, config: createDefaultConfig()}
clusters, err := mesosCloud.List(".*") // recognizes the language of all strings
if err != nil {
t.Fatalf("List does not yield an error: %#v", err)
}
if len(clusters) != 3 {
t.Fatalf("List with a catch-all filter should return a list of size 3: (actual: %#v)", clusters)
}
clusters, err = mesosCloud.List("$^") // end-of-string followed by start-of-string: recognizes the empty language
if err != nil {
t.Fatalf("List does not yield an error: %#v", err)
}
if len(clusters) != 0 {
t.Fatalf("List with a reject-all filter should return a list of size 0: (actual: %#v)", clusters)
}
}
func Test_ExternalID(t *testing.T) {
defer log.Flush()
md := FakeMasterDetector{}
httpServer, httpClient, httpTransport := makeHttpMocks()
defer httpServer.Close()
cacheTTL := 500 * time.Millisecond
mesosClient, err := createMesosClient(md, httpClient, httpTransport, cacheTTL)
mesosCloud := &MesosCloud{client: mesosClient, config: createDefaultConfig()}
_, err = mesosCloud.ExternalID("unknown")
if err != cloudprovider.InstanceNotFound {
t.Fatalf("ExternalID did not return InstanceNotFound on an unknown instance")
}
slaveName := types.NodeName("mesos3.internal.example.org.fail")
id, err := mesosCloud.ExternalID(slaveName)
if id != "" {
t.Fatalf("ExternalID should not be able to resolve %q", slaveName)
}
if err == cloudprovider.InstanceNotFound {
t.Fatalf("ExternalID should find %q", slaveName)
}
}
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mesos
import (
_ "github.com/mesos/mesos-go/detector/zoo"
)
......@@ -22,7 +22,6 @@ import (
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/azure"
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/cloudstack"
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/mesos"
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/openstack"
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/ovirt"
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/photon"
......
......@@ -258,10 +258,6 @@ filegroup(
"//vendor/github.com/mailru/easyjson/jlexer:all-srcs",
"//vendor/github.com/mailru/easyjson/jwriter:all-srcs",
"//vendor/github.com/matttproud/golang_protobuf_extensions/pbutil:all-srcs",
"//vendor/github.com/mesos/mesos-go/detector:all-srcs",
"//vendor/github.com/mesos/mesos-go/mesosproto:all-srcs",
"//vendor/github.com/mesos/mesos-go/mesosutil:all-srcs",
"//vendor/github.com/mesos/mesos-go/upid:all-srcs",
"//vendor/github.com/miekg/coredns/middleware/etcd/msg:all-srcs",
"//vendor/github.com/miekg/dns:all-srcs",
"//vendor/github.com/mistifyio/go-zfs:all-srcs",
......@@ -292,7 +288,6 @@ filegroup(
"//vendor/github.com/robfig/cron:all-srcs",
"//vendor/github.com/rubiojr/go-vhd/vhd:all-srcs",
"//vendor/github.com/russross/blackfriday:all-srcs",
"//vendor/github.com/samuel/go-zookeeper/zk:all-srcs",
"//vendor/github.com/seccomp/libseccomp-golang:all-srcs",
"//vendor/github.com/shurcooL/sanitized_anchor_name:all-srcs",
"//vendor/github.com/spf13/afero:all-srcs",
......
Copyright 2013-2015, Mesosphere, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"factory.go",
"interface.go",
"standalone.go",
],
tags = ["automanaged"],
deps = [
"//vendor/github.com/gogo/protobuf/proto:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/mesos/mesos-go/mesosproto:go_default_library",
"//vendor/github.com/mesos/mesos-go/mesosutil:go_default_library",
"//vendor/github.com/mesos/mesos-go/upid:go_default_library",
"//vendor/golang.org/x/net/context:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//vendor/github.com/mesos/mesos-go/detector/zoo:all-srcs",
],
tags = ["automanaged"],
)
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
The detector package houses implementation of master detectors.
The default implementation is the zookeeper master detector.
It uses zookeeper to detect the lead Mesos master during startup/failover.
*/
package detector
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, _ ...Option) (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, ...Option) (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, options ...Option) (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), options...)
}
} else if f, ok := MatchingPlugin(spec); ok {
m, err = f(spec, options...)
} else {
m, err = defaultFactory(spec, options...)
}
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
}
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package detector
import (
mesos "github.com/mesos/mesos-go/mesosproto"
)
type MasterChanged interface {
// Invoked when the master changes
OnMasterChanged(*mesos.MasterInfo)
}
// AllMasters defines an optional interface that, if implemented by the same
// struct as implements MasterChanged, will receive an additional callbacks
// independently of leadership changes. it's possible that, as a result of a
// leadership change, both the OnMasterChanged and UpdatedMasters callbacks
// would be invoked.
//
// **NOTE:** Detector implementations are not required to support this optional
// interface. Please RTFM of the detector implementation that you want to use.
type AllMasters interface {
// UpdatedMasters is invoked upon a change in the membership of mesos
// masters, and is useful to clients that wish to know the entire set
// of Mesos masters currently running.
UpdatedMasters([]*mesos.MasterInfo)
}
// func/interface adapter
type OnMasterChanged func(*mesos.MasterInfo)
func (f OnMasterChanged) OnMasterChanged(mi *mesos.MasterInfo) {
f(mi)
}
// An abstraction of a Master detector which can be used to
// detect the leading master from a group.
type Master interface {
// Detect new master election. Every time a new master is elected, the
// detector will alert the observer. The first call to Detect is expected
// to kickstart any background detection processing (and not before then).
// If detection startup fails, or the listener cannot be added, then an
// error is returned.
Detect(MasterChanged) error
// returns a chan that, when closed, indicates the detector has terminated
Done() <-chan struct{}
// cancel the detector. it's ok to call this multiple times, or even if
// Detect() hasn't been invoked yet.
Cancel()
}
// functional option type for detectors
type Option func(interface{}) Option
package detector
import (
"encoding/binary"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"strconv"
"sync"
"time"
log "github.com/golang/glog"
mesos "github.com/mesos/mesos-go/mesosproto"
"github.com/mesos/mesos-go/upid"
"golang.org/x/net/context"
)
const (
defaultMesosHttpClientTimeout = 10 * time.Second //TODO(jdef) configurable via fiag?
defaultMesosLeaderSyncInterval = 30 * time.Second //TODO(jdef) configurable via fiag?
defaultMesosMasterPort = 5050
)
// enables easier unit testing
type fetcherFunc func(ctx context.Context, address string) (*upid.UPID, error)
type Standalone struct {
ch chan *mesos.MasterInfo
client *http.Client
tr *http.Transport
pollOnce sync.Once
initial *mesos.MasterInfo
done chan struct{}
cancelOnce sync.Once
leaderSyncInterval time.Duration
httpClientTimeout time.Duration
assumedMasterPort int
poller func(pf fetcherFunc)
fetchPid fetcherFunc
}
// Create a new stand alone master detector.
func NewStandalone(mi *mesos.MasterInfo) *Standalone {
log.V(2).Infof("creating new standalone detector for %+v", mi)
stand := &Standalone{
ch: make(chan *mesos.MasterInfo),
tr: &http.Transport{},
initial: mi,
done: make(chan struct{}),
leaderSyncInterval: defaultMesosLeaderSyncInterval,
httpClientTimeout: defaultMesosHttpClientTimeout,
assumedMasterPort: defaultMesosMasterPort,
}
stand.poller = stand._poller
stand.fetchPid = stand._fetchPid
return stand
}
func (s *Standalone) String() string {
return fmt.Sprintf("{initial: %+v}", s.initial)
}
// Detecting the new master.
func (s *Standalone) Detect(o MasterChanged) error {
log.V(2).Info("Detect()")
s.pollOnce.Do(func() {
log.V(1).Info("spinning up asyc master detector poller")
// delayed initialization allows unit tests to modify timeouts before detection starts
s.client = &http.Client{
Transport: s.tr,
Timeout: s.httpClientTimeout,
}
go s.poller(s.fetchPid)
})
if o != nil {
log.V(1).Info("spawning asyc master detector listener")
go func() {
log.V(2).Infof("waiting for polled to send updates")
pollWaiter:
for {
select {
case mi, ok := <-s.ch:
if !ok {
break pollWaiter
}
log.V(1).Infof("detected master change: %+v", mi)
o.OnMasterChanged(mi)
case <-s.done:
return
}
}
o.OnMasterChanged(nil)
}()
} else {
log.Warningf("detect called with a nil master change listener")
}
return nil
}
func (s *Standalone) Done() <-chan struct{} {
return s.done
}
func (s *Standalone) Cancel() {
s.cancelOnce.Do(func() { close(s.done) })
}
// poll for changes to master leadership via current leader's /state endpoint.
// we poll the `initial` leader, aborting if none was specified.
//
// TODO(jdef) follow the leader: change who we poll based on the prior leader
// TODO(jdef) somehow determine all masters in cluster from the /state?
//
func (s *Standalone) _poller(pf fetcherFunc) {
defer func() {
defer s.Cancel()
log.Warning("shutting down standalone master detection")
}()
if s.initial == nil {
log.Errorf("aborting master poller since initial master info is nil")
return
}
addr := s.initial.GetHostname()
if len(addr) == 0 {
if s.initial.GetIp() == 0 {
log.Warningf("aborted mater poller since initial master info has no host")
return
}
ip := make([]byte, 4)
binary.BigEndian.PutUint32(ip, s.initial.GetIp())
addr = net.IP(ip).To4().String()
}
port := uint32(s.assumedMasterPort)
if s.initial.Port != nil && *s.initial.Port != 0 {
port = *s.initial.Port
}
addr = net.JoinHostPort(addr, strconv.Itoa(int(port)))
log.V(1).Infof("polling for master leadership at '%v'", addr)
var lastpid *upid.UPID
for {
startedAt := time.Now()
ctx, cancel := context.WithTimeout(context.Background(), s.leaderSyncInterval)
if pid, err := pf(ctx, addr); err == nil {
if !pid.Equal(lastpid) {
log.V(2).Infof("detected leadership change from '%v' to '%v'", lastpid, pid)
lastpid = pid
elapsed := time.Now().Sub(startedAt)
mi := CreateMasterInfo(pid)
select {
case s.ch <- mi: // noop
case <-time.After(s.leaderSyncInterval - elapsed):
// no one heard the master change, oh well - poll again
goto continuePolling
case <-s.done:
cancel()
return
}
} else {
log.V(2).Infof("no change to master leadership: '%v'", lastpid)
}
} else if err == context.DeadlineExceeded {
if lastpid != nil {
lastpid = nil
select {
case s.ch <- nil: // lost master
case <-s.done: // no need to cancel ctx
return
}
}
goto continuePolling
} else {
select {
case <-s.done:
cancel()
return
default:
if err != context.Canceled {
log.Error(err)
}
}
}
if remaining := s.leaderSyncInterval - time.Now().Sub(startedAt); remaining > 0 {
log.V(3).Infof("master leader poller sleeping for %v", remaining)
time.Sleep(remaining)
}
continuePolling:
cancel()
}
}
// assumes that address is in host:port format
func (s *Standalone) _fetchPid(ctx context.Context, address string) (*upid.UPID, error) {
//TODO(jdef) need SSL support
uri := fmt.Sprintf("http://%s/state", address)
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
return nil, err
}
var pid *upid.UPID
err = s.httpDo(ctx, req, func(res *http.Response, err error) error {
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != 200 {
return fmt.Errorf("HTTP request failed with code %d: %v", res.StatusCode, res.Status)
}
blob, err1 := ioutil.ReadAll(res.Body)
if err1 != nil {
return err1
}
log.V(3).Infof("Got mesos state, content length %v", len(blob))
type State struct {
Leader string `json:"leader"` // ex: master(1)@10.22.211.18:5050
}
state := &State{}
err = json.Unmarshal(blob, state)
if err != nil {
return err
}
pid, err = upid.Parse(state.Leader)
return err
})
return pid, err
}
type responseHandler func(*http.Response, error) error
// hacked from https://blog.golang.org/context
func (s *Standalone) httpDo(ctx context.Context, req *http.Request, f responseHandler) error {
// Run the HTTP request in a goroutine and pass the response to f.
ch := make(chan error, 1)
go func() { ch <- f(s.client.Do(req)) }()
select {
case <-ctx.Done():
s.tr.CancelRequest(req)
<-ch // Wait for f to return.
return ctx.Err()
case err := <-ch:
return err
}
}
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"client2.go",
"detect.go",
"doc.go",
"plugin.go",
"types.go",
],
tags = ["automanaged"],
deps = [
"//vendor/github.com/gogo/protobuf/proto:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/mesos/mesos-go/detector:go_default_library",
"//vendor/github.com/mesos/mesos-go/mesosproto:go_default_library",
"//vendor/github.com/samuel/go-zookeeper/zk:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
package zoo
import (
"sync"
"time"
"github.com/samuel/go-zookeeper/zk"
)
const (
defaultSessionTimeout = 60 * time.Second
CurrentPath = "."
)
var zkSessionTimeout = defaultSessionTimeout
type client2 struct {
*zk.Conn
path string
done chan struct{} // signal chan, closes when the underlying connection terminates
stopOnce sync.Once
}
func connect2(hosts []string, path string) (*client2, error) {
c, ev, err := zk.Connect(hosts, zkSessionTimeout)
if err != nil {
return nil, err
}
done := make(chan struct{})
go func() {
// close the 'done' chan when the zk event chan closes (signals termination of zk connection)
defer close(done)
for {
if _, ok := <-ev; !ok {
return
}
}
}()
return &client2{
Conn: c,
path: path,
done: done,
}, nil
}
func (c *client2) Stopped() <-chan struct{} {
return c.done
}
func (c *client2) Stop() {
c.stopOnce.Do(c.Close)
}
func (c *client2) Data(path string) (data []byte, err error) {
data, _, err = c.Get(path)
return
}
func (c *client2) WatchChildren(path string) (string, <-chan []string, <-chan error) {
errCh := make(chan error, 1)
snap := make(chan []string)
watchPath := c.path
if path != "" && path != CurrentPath {
watchPath = watchPath + path
}
go func() {
defer close(errCh)
for {
children, _, ev, err := c.ChildrenW(watchPath)
if err != nil {
errCh <- err
return
}
select {
case snap <- children:
case <-c.done:
return
}
e := <-ev // wait for the next watch-related event
if e.Err != nil {
errCh <- e.Err
return
}
}
}()
return watchPath, snap, errCh
}
// Zookeeper-based mesos-master leaderhip detection.
// Implements support for optional detector.AllMasters interface.
package zoo
package zoo
import (
"github.com/mesos/mesos-go/detector"
)
func init() {
detector.Register("zk://", detector.PluginFactory(func(spec string, options ...detector.Option) (detector.Master, error) {
return NewMasterDetector(spec, options...)
}))
}
package zoo
import (
"github.com/samuel/go-zookeeper/zk"
)
// Connector Interface to facade zk.Conn type
// since github.com/samuel/go-zookeeper/zk does not provide an interface
// for the zk.Conn object, this allows for mocking and easier testing.
type Connector interface {
Close()
Children(string) ([]string, *zk.Stat, error)
ChildrenW(string) ([]string, *zk.Stat, <-chan zk.Event, error)
Get(string) ([]byte, *zk.Stat, error)
}
//Factory is an adapter to trap the creation of zk.Conn instances
//since the official zk API does not expose an interface for zk.Conn.
type Factory interface {
create() (Connector, <-chan zk.Event, error)
}
type asFactory func() (Connector, <-chan zk.Event, error)
func (f asFactory) create() (Connector, <-chan zk.Event, error) {
return f()
}
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"authentication.pb.go",
"internal.pb.go",
"mesos.pb.go",
"messages.pb.go",
],
tags = ["automanaged"],
deps = ["//vendor/github.com/gogo/protobuf/proto:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
PROTO_PATH := ${GOPATH}/src:.
PROTO_PATH := ${PROTO_PATH}:${GOPATH}/src/github.com/gogo/protobuf/protobuf
PROTO_PATH := ${PROTO_PATH}:${GOPATH}/src/github.com/gogo/protobuf/gogoproto
.PHONY: all
all:
protoc --proto_path=${PROTO_PATH} --gogo_out=. *.proto
protoc --proto_path=${PROTO_PATH} --gogo_out=. ./scheduler/*.proto
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package mesosproto;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option (gogoproto.gostring_all) = true;
option (gogoproto.equal_all) = true;
option (gogoproto.verbose_equal_all) = true;
option (gogoproto.goproto_stringer_all) = false;
option (gogoproto.stringer_all) = true;
option (gogoproto.populate_all) = true;
option (gogoproto.testgen_all) = true;
option (gogoproto.benchgen_all) = true;
option (gogoproto.marshaler_all) = true;
option (gogoproto.sizer_all) = true;
option (gogoproto.unmarshaler_all) = true;
message AuthenticateMessage {
required string pid = 1; // PID that needs to be authenticated.
}
message AuthenticationMechanismsMessage {
repeated string mechanisms = 1; // List of available SASL mechanisms.
}
message AuthenticationStartMessage {
required string mechanism = 1;
optional bytes data = 2;
}
message AuthenticationStepMessage {
required bytes data = 1;
}
message AuthenticationCompletedMessage {}
message AuthenticationFailedMessage {}
message AuthenticationErrorMessage {
optional string error = 1;
}
// Code generated by protoc-gen-gogo.
// source: internal.proto
// DO NOT EDIT!
package mesosproto
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
// discarding unused import gogoproto "github.com/gogo/protobuf/gogoproto"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// For use with detector callbacks
type InternalMasterChangeDetected struct {
// will be present if there's a new master, otherwise nil
Master *MasterInfo `protobuf:"bytes,1,opt,name=master" json:"master,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *InternalMasterChangeDetected) Reset() { *m = InternalMasterChangeDetected{} }
func (m *InternalMasterChangeDetected) String() string { return proto.CompactTextString(m) }
func (*InternalMasterChangeDetected) ProtoMessage() {}
func (m *InternalMasterChangeDetected) GetMaster() *MasterInfo {
if m != nil {
return m.Master
}
return nil
}
type InternalTryAuthentication struct {
XXX_unrecognized []byte `json:"-"`
}
func (m *InternalTryAuthentication) Reset() { *m = InternalTryAuthentication{} }
func (m *InternalTryAuthentication) String() string { return proto.CompactTextString(m) }
func (*InternalTryAuthentication) ProtoMessage() {}
type InternalAuthenticationResult struct {
// true only if the authentication process completed and login was successful
Success *bool `protobuf:"varint,1,req,name=success" json:"success,omitempty"`
// true if the authentication process completed, successfully or not
Completed *bool `protobuf:"varint,2,req,name=completed" json:"completed,omitempty"`
// master pid that this result pertains to
Pid *string `protobuf:"bytes,3,req,name=pid" json:"pid,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *InternalAuthenticationResult) Reset() { *m = InternalAuthenticationResult{} }
func (m *InternalAuthenticationResult) String() string { return proto.CompactTextString(m) }
func (*InternalAuthenticationResult) ProtoMessage() {}
func (m *InternalAuthenticationResult) GetSuccess() bool {
if m != nil && m.Success != nil {
return *m.Success
}
return false
}
func (m *InternalAuthenticationResult) GetCompleted() bool {
if m != nil && m.Completed != nil {
return *m.Completed
}
return false
}
func (m *InternalAuthenticationResult) GetPid() string {
if m != nil && m.Pid != nil {
return *m.Pid
}
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 ""
}
package mesosproto;
import "mesos.proto";
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
// For use with detector callbacks
message InternalMasterChangeDetected {
// will be present if there's a new master, otherwise nil
optional MasterInfo master = 1;
}
message InternalTryAuthentication {
// empty message, serves as a signal to the scheduler bindings
}
message InternalAuthenticationResult {
// true only if the authentication process completed and login was successful
required bool success = 1;
// true if the authentication process completed, successfully or not
required bool completed = 2;
// master pid that this result pertains to
required string pid = 3;
}
message InternalNetworkError {
// master pid that this event pertains to
required string pid = 1;
// driver session UUID
optional string session = 2;
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"constants.go",
"mesosprotoutil.go",
"node.go",
],
tags = ["automanaged"],
deps = [
"//vendor/github.com/gogo/protobuf/proto:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/mesos/mesos-go/mesosproto:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
package mesosutil
const (
// MesosVersion indicates the supported mesos version.
MesosVersion = "0.24.0"
)
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package mesosutil
import (
"github.com/gogo/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto"
)
func NewValueRange(begin, end uint64) *mesos.Value_Range {
return &mesos.Value_Range{Begin: proto.Uint64(begin), End: proto.Uint64(end)}
}
func FilterResources(resources []*mesos.Resource, filter func(*mesos.Resource) bool) (result []*mesos.Resource) {
for _, resource := range resources {
if filter(resource) {
result = append(result, resource)
}
}
return result
}
func AddResourceReservation(resource *mesos.Resource, principal string, role string) *mesos.Resource {
resource.Reservation = &mesos.Resource_ReservationInfo{Principal: proto.String(principal)}
resource.Role = proto.String(role)
return resource
}
func NewScalarResourceWithReservation(name string, value float64, principal string, role string) *mesos.Resource {
return AddResourceReservation(NewScalarResource(name, value), principal, role)
}
func NewRangesResourceWithReservation(name string, ranges []*mesos.Value_Range, principal string, role string) *mesos.Resource {
return AddResourceReservation(NewRangesResource(name, ranges), principal, role)
}
func NewSetResourceWithReservation(name string, items []string, principal string, role string) *mesos.Resource {
return AddResourceReservation(NewSetResource(name, items), principal, role)
}
func NewVolumeResourceWithReservation(val float64, containerPath string, persistenceId string, mode *mesos.Volume_Mode, principal string, role string) *mesos.Resource {
return AddResourceReservation(NewVolumeResource(val, containerPath, persistenceId, mode), principal, role)
}
func NewScalarResource(name string, val float64) *mesos.Resource {
return &mesos.Resource{
Name: proto.String(name),
Type: mesos.Value_SCALAR.Enum(),
Scalar: &mesos.Value_Scalar{Value: proto.Float64(val)},
}
}
func NewRangesResource(name string, ranges []*mesos.Value_Range) *mesos.Resource {
return &mesos.Resource{
Name: proto.String(name),
Type: mesos.Value_RANGES.Enum(),
Ranges: &mesos.Value_Ranges{Range: ranges},
}
}
func NewSetResource(name string, items []string) *mesos.Resource {
return &mesos.Resource{
Name: proto.String(name),
Type: mesos.Value_SET.Enum(),
Set: &mesos.Value_Set{Item: items},
}
}
func NewVolumeResource(val float64, containerPath string, persistenceId string, mode *mesos.Volume_Mode) *mesos.Resource {
resource := NewScalarResource("disk", val)
resource.Disk = &mesos.Resource_DiskInfo{
Persistence: &mesos.Resource_DiskInfo_Persistence{Id: proto.String(persistenceId)},
Volume: &mesos.Volume{ContainerPath: proto.String(containerPath), Mode: mode},
}
return resource
}
func NewFrameworkID(id string) *mesos.FrameworkID {
return &mesos.FrameworkID{Value: proto.String(id)}
}
func NewFrameworkInfo(user, name string, frameworkId *mesos.FrameworkID) *mesos.FrameworkInfo {
return &mesos.FrameworkInfo{
User: proto.String(user),
Name: proto.String(name),
Id: frameworkId,
}
}
func NewMasterInfo(id string, ip, port uint32) *mesos.MasterInfo {
return &mesos.MasterInfo{
Id: proto.String(id),
Ip: proto.Uint32(ip),
Port: proto.Uint32(port),
}
}
func NewOfferID(id string) *mesos.OfferID {
return &mesos.OfferID{Value: proto.String(id)}
}
func NewOffer(offerId *mesos.OfferID, frameworkId *mesos.FrameworkID, slaveId *mesos.SlaveID, hostname string) *mesos.Offer {
return &mesos.Offer{
Id: offerId,
FrameworkId: frameworkId,
SlaveId: slaveId,
Hostname: proto.String(hostname),
}
}
func FilterOffersResources(offers []*mesos.Offer, filter func(*mesos.Resource) bool) (result []*mesos.Resource) {
for _, offer := range offers {
result = FilterResources(offer.Resources, filter)
}
return result
}
func NewSlaveID(id string) *mesos.SlaveID {
return &mesos.SlaveID{Value: proto.String(id)}
}
func NewTaskID(id string) *mesos.TaskID {
return &mesos.TaskID{Value: proto.String(id)}
}
func NewTaskInfo(
name string,
taskId *mesos.TaskID,
slaveId *mesos.SlaveID,
resources []*mesos.Resource,
) *mesos.TaskInfo {
return &mesos.TaskInfo{
Name: proto.String(name),
TaskId: taskId,
SlaveId: slaveId,
Resources: resources,
}
}
func NewTaskStatus(taskId *mesos.TaskID, state mesos.TaskState) *mesos.TaskStatus {
return &mesos.TaskStatus{
TaskId: taskId,
State: mesos.TaskState(state).Enum(),
}
}
func NewStatusUpdate(frameworkId *mesos.FrameworkID, taskStatus *mesos.TaskStatus, timestamp float64, uuid []byte) *mesos.StatusUpdate {
return &mesos.StatusUpdate{
FrameworkId: frameworkId,
Status: taskStatus,
Timestamp: proto.Float64(timestamp),
Uuid: uuid,
}
}
func NewCommandInfo(command string) *mesos.CommandInfo {
return &mesos.CommandInfo{Value: proto.String(command)}
}
func NewExecutorID(id string) *mesos.ExecutorID {
return &mesos.ExecutorID{Value: proto.String(id)}
}
func NewExecutorInfo(execId *mesos.ExecutorID, command *mesos.CommandInfo) *mesos.ExecutorInfo {
return &mesos.ExecutorInfo{
ExecutorId: execId,
Command: command,
}
}
func NewCreateOperation(volumes []*mesos.Resource) *mesos.Offer_Operation {
return &mesos.Offer_Operation{
Type: mesos.Offer_Operation_CREATE.Enum(),
Create: &mesos.Offer_Operation_Create{Volumes: volumes},
}
}
func NewDestroyOperation(volumes []*mesos.Resource) *mesos.Offer_Operation {
return &mesos.Offer_Operation{
Type: mesos.Offer_Operation_DESTROY.Enum(),
Destroy: &mesos.Offer_Operation_Destroy{Volumes: volumes},
}
}
func NewReserveOperation(resources []*mesos.Resource) *mesos.Offer_Operation {
return &mesos.Offer_Operation{
Type: mesos.Offer_Operation_RESERVE.Enum(),
Reserve: &mesos.Offer_Operation_Reserve{Resources: resources},
}
}
func NewUnreserveOperation(resources []*mesos.Resource) *mesos.Offer_Operation {
return &mesos.Offer_Operation{
Type: mesos.Offer_Operation_UNRESERVE.Enum(),
Unreserve: &mesos.Offer_Operation_Unreserve{Resources: resources},
}
}
func NewLaunchOperation(tasks []*mesos.TaskInfo) *mesos.Offer_Operation {
return &mesos.Offer_Operation{
Type: mesos.Offer_Operation_LAUNCH.Enum(),
Launch: &mesos.Offer_Operation_Launch{TaskInfos: tasks},
}
}
package mesosutil
import (
"os"
"os/exec"
"strings"
log "github.com/golang/glog"
)
//TODO(jdef) copied from kubernetes/pkg/util/node.go
func GetHostname(hostnameOverride string) string {
hostname := hostnameOverride
if hostname == "" {
// Note: We use exec here instead of os.Hostname() because we
// want the FQDN, and this is the easiest way to get it.
fqdn, err := exec.Command("hostname", "-f").Output()
if err != nil || len(fqdn) == 0 {
log.Errorf("Couldn't determine hostname fqdn, failing back to hostname: %v", err)
hostname, err = os.Hostname()
if err != nil {
log.Fatalf("Error getting hostname: %v", err)
}
} else {
hostname = string(fqdn)
}
}
return strings.TrimSpace(hostname)
}
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"upid.go",
],
tags = ["automanaged"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
/*
Package upid defines the UPID type and some utilities of the UPID.
*/
package upid
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package upid
import (
"fmt"
"net"
"strings"
)
// UPID is a equivalent of the UPID in libprocess.
type UPID struct {
ID string
Host string
Port string
}
// Parse parses the UPID from the input string.
func Parse(input string) (*UPID, error) {
upid := new(UPID)
splits := strings.Split(input, "@")
if len(splits) != 2 {
return nil, fmt.Errorf("Expect one `@' in the input")
}
upid.ID = splits[0]
if _, err := net.ResolveTCPAddr("tcp4", splits[1]); err != nil {
return nil, err
}
upid.Host, upid.Port, _ = net.SplitHostPort(splits[1])
return upid, nil
}
// String returns the string representation.
func (u UPID) String() string {
return fmt.Sprintf("%s@%s:%s", u.ID, u.Host, u.Port)
}
// Equal returns true if two upid is equal
func (u *UPID) Equal(upid *UPID) bool {
if u == nil {
return upid == nil
} else {
return upid != nil && u.ID == upid.ID && u.Host == upid.Host && u.Port == upid.Port
}
}
Copyright (c) 2013, Samuel Stauffer <samuel@descolada.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"conn.go",
"constants.go",
"flw.go",
"lock.go",
"server_help.go",
"server_java.go",
"structs.go",
"tracer.go",
"util.go",
],
tags = ["automanaged"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
package zk
import (
"errors"
)
const (
protocolVersion = 0
DefaultPort = 2181
)
const (
opNotify = 0
opCreate = 1
opDelete = 2
opExists = 3
opGetData = 4
opSetData = 5
opGetAcl = 6
opSetAcl = 7
opGetChildren = 8
opSync = 9
opPing = 11
opGetChildren2 = 12
opCheck = 13
opMulti = 14
opClose = -11
opSetAuth = 100
opSetWatches = 101
// Not in protocol, used internally
opWatcherEvent = -2
)
const (
EventNodeCreated = EventType(1)
EventNodeDeleted = EventType(2)
EventNodeDataChanged = EventType(3)
EventNodeChildrenChanged = EventType(4)
EventSession = EventType(-1)
EventNotWatching = EventType(-2)
)
var (
eventNames = map[EventType]string{
EventNodeCreated: "EventNodeCreated",
EventNodeDeleted: "EventNodeDeleted",
EventNodeDataChanged: "EventNodeDataChanged",
EventNodeChildrenChanged: "EventNodeChildrenChanged",
EventSession: "EventSession",
EventNotWatching: "EventNotWatching",
}
)
const (
StateUnknown = State(-1)
StateDisconnected = State(0)
StateConnecting = State(1)
StateAuthFailed = State(4)
StateConnectedReadOnly = State(5)
StateSaslAuthenticated = State(6)
StateExpired = State(-112)
// StateAuthFailed = State(-113)
StateConnected = State(100)
StateHasSession = State(101)
)
const (
FlagEphemeral = 1
FlagSequence = 2
)
var (
stateNames = map[State]string{
StateUnknown: "StateUnknown",
StateDisconnected: "StateDisconnected",
StateConnectedReadOnly: "StateConnectedReadOnly",
StateSaslAuthenticated: "StateSaslAuthenticated",
StateExpired: "StateExpired",
StateAuthFailed: "StateAuthFailed",
StateConnecting: "StateConnecting",
StateConnected: "StateConnected",
StateHasSession: "StateHasSession",
}
)
type State int32
func (s State) String() string {
if name := stateNames[s]; name != "" {
return name
}
return "Unknown"
}
type ErrCode int32
var (
ErrConnectionClosed = errors.New("zk: connection closed")
ErrUnknown = errors.New("zk: unknown error")
ErrAPIError = errors.New("zk: api error")
ErrNoNode = errors.New("zk: node does not exist")
ErrNoAuth = errors.New("zk: not authenticated")
ErrBadVersion = errors.New("zk: version conflict")
ErrNoChildrenForEphemerals = errors.New("zk: ephemeral nodes may not have children")
ErrNodeExists = errors.New("zk: node already exists")
ErrNotEmpty = errors.New("zk: node has children")
ErrSessionExpired = errors.New("zk: session has been expired by the server")
ErrInvalidACL = errors.New("zk: invalid ACL specified")
ErrAuthFailed = errors.New("zk: client authentication failed")
ErrClosing = errors.New("zk: zookeeper is closing")
ErrNothing = errors.New("zk: no server responsees to process")
ErrSessionMoved = errors.New("zk: session moved to another server, so operation is ignored")
// ErrInvalidCallback = errors.New("zk: invalid callback specified")
errCodeToError = map[ErrCode]error{
0: nil,
errAPIError: ErrAPIError,
errNoNode: ErrNoNode,
errNoAuth: ErrNoAuth,
errBadVersion: ErrBadVersion,
errNoChildrenForEphemerals: ErrNoChildrenForEphemerals,
errNodeExists: ErrNodeExists,
errNotEmpty: ErrNotEmpty,
errSessionExpired: ErrSessionExpired,
// errInvalidCallback: ErrInvalidCallback,
errInvalidAcl: ErrInvalidACL,
errAuthFailed: ErrAuthFailed,
errClosing: ErrClosing,
errNothing: ErrNothing,
errSessionMoved: ErrSessionMoved,
}
)
func (e ErrCode) toError() error {
if err, ok := errCodeToError[e]; ok {
return err
}
return ErrUnknown
}
const (
errOk = 0
// System and server-side errors
errSystemError = -1
errRuntimeInconsistency = -2
errDataInconsistency = -3
errConnectionLoss = -4
errMarshallingError = -5
errUnimplemented = -6
errOperationTimeout = -7
errBadArguments = -8
errInvalidState = -9
// API errors
errAPIError = ErrCode(-100)
errNoNode = ErrCode(-101) // *
errNoAuth = ErrCode(-102)
errBadVersion = ErrCode(-103) // *
errNoChildrenForEphemerals = ErrCode(-108)
errNodeExists = ErrCode(-110) // *
errNotEmpty = ErrCode(-111)
errSessionExpired = ErrCode(-112)
errInvalidCallback = ErrCode(-113)
errInvalidAcl = ErrCode(-114)
errAuthFailed = ErrCode(-115)
errClosing = ErrCode(-116)
errNothing = ErrCode(-117)
errSessionMoved = ErrCode(-118)
)
// Constants for ACL permissions
const (
PermRead = 1 << iota
PermWrite
PermCreate
PermDelete
PermAdmin
PermAll = 0x1f
)
var (
emptyPassword = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
opNames = map[int32]string{
opNotify: "notify",
opCreate: "create",
opDelete: "delete",
opExists: "exists",
opGetData: "getData",
opSetData: "setData",
opGetAcl: "getACL",
opSetAcl: "setACL",
opGetChildren: "getChildren",
opSync: "sync",
opPing: "ping",
opGetChildren2: "getChildren2",
opCheck: "check",
opMulti: "multi",
opClose: "close",
opSetAuth: "setAuth",
opSetWatches: "setWatches",
opWatcherEvent: "watcherEvent",
}
)
type EventType int32
func (t EventType) String() string {
if name := eventNames[t]; name != "" {
return name
}
return "Unknown"
}
// Mode is used to build custom server modes (leader|follower|standalone).
type Mode uint8
func (m Mode) String() string {
if name := modeNames[m]; name != "" {
return name
}
return "unknown"
}
const (
ModeUnknown Mode = iota
ModeLeader Mode = iota
ModeFollower Mode = iota
ModeStandalone Mode = iota
)
var (
modeNames = map[Mode]string{
ModeLeader: "leader",
ModeFollower: "follower",
ModeStandalone: "standalone",
}
)
package zk
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"math/big"
"net"
"regexp"
"strconv"
"time"
)
// FLWSrvr is a FourLetterWord helper function. In particular, this function pulls the srvr output
// from the zookeeper instances and parses the output. A slice of *ServerStats structs are returned
// as well as a boolean value to indicate whether this function processed successfully.
//
// If the boolean value is false there was a problem. If the *ServerStats slice is empty or nil,
// then the error happened before we started to obtain 'srvr' values. Otherwise, one of the
// servers had an issue and the "Error" value in the struct should be inspected to determine
// which server had the issue.
func FLWSrvr(servers []string, timeout time.Duration) ([]*ServerStats, bool) {
// different parts of the regular expression that are required to parse the srvr output
var (
zrVer = `^Zookeeper version: ([A-Za-z0-9\.\-]+), built on (\d\d/\d\d/\d\d\d\d \d\d:\d\d [A-Za-z0-9:\+\-]+)`
zrLat = `^Latency min/avg/max: (\d+)/(\d+)/(\d+)`
zrNet = `^Received: (\d+).*\n^Sent: (\d+).*\n^Connections: (\d+).*\n^Outstanding: (\d+)`
zrState = `^Zxid: (0x[A-Za-z0-9]+).*\n^Mode: (\w+).*\n^Node count: (\d+)`
)
// build the regex from the pieces above
re, err := regexp.Compile(fmt.Sprintf(`(?m:\A%v.*\n%v.*\n%v.*\n%v)`, zrVer, zrLat, zrNet, zrState))
if err != nil {
return nil, false
}
imOk := true
servers = FormatServers(servers)
ss := make([]*ServerStats, len(servers))
for i := range ss {
response, err := fourLetterWord(servers[i], "srvr", timeout)
if err != nil {
ss[i] = &ServerStats{Error: err}
imOk = false
continue
}
matches := re.FindAllStringSubmatch(string(response), -1)
if matches == nil {
err := fmt.Errorf("unable to parse fields from zookeeper response (no regex matches)")
ss[i] = &ServerStats{Error: err}
imOk = false
continue
}
match := matches[0][1:]
// determine current server
var srvrMode Mode
switch match[10] {
case "leader":
srvrMode = ModeLeader
case "follower":
srvrMode = ModeFollower
case "standalone":
srvrMode = ModeStandalone
default:
srvrMode = ModeUnknown
}
buildTime, err := time.Parse("01/02/2006 15:04 MST", match[1])
if err != nil {
ss[i] = &ServerStats{Error: err}
imOk = false
continue
}
parsedInt, err := strconv.ParseInt(match[9], 0, 64)
if err != nil {
ss[i] = &ServerStats{Error: err}
imOk = false
continue
}
// the ZxID value is an int64 with two int32s packed inside
// the high int32 is the epoch (i.e., number of leader elections)
// the low int32 is the counter
epoch := int32(parsedInt >> 32)
counter := int32(parsedInt & 0xFFFFFFFF)
// within the regex above, these values must be numerical
// so we can avoid useless checking of the error return value
minLatency, _ := strconv.ParseInt(match[2], 0, 64)
avgLatency, _ := strconv.ParseInt(match[3], 0, 64)
maxLatency, _ := strconv.ParseInt(match[4], 0, 64)
recv, _ := strconv.ParseInt(match[5], 0, 64)
sent, _ := strconv.ParseInt(match[6], 0, 64)
cons, _ := strconv.ParseInt(match[7], 0, 64)
outs, _ := strconv.ParseInt(match[8], 0, 64)
ncnt, _ := strconv.ParseInt(match[11], 0, 64)
ss[i] = &ServerStats{
Sent: sent,
Received: recv,
NodeCount: ncnt,
MinLatency: minLatency,
AvgLatency: avgLatency,
MaxLatency: maxLatency,
Connections: cons,
Outstanding: outs,
Epoch: epoch,
Counter: counter,
BuildTime: buildTime,
Mode: srvrMode,
Version: match[0],
}
}
return ss, imOk
}
// FLWRuok is a FourLetterWord helper function. In particular, this function
// pulls the ruok output from each server.
func FLWRuok(servers []string, timeout time.Duration) []bool {
servers = FormatServers(servers)
oks := make([]bool, len(servers))
for i := range oks {
response, err := fourLetterWord(servers[i], "ruok", timeout)
if err != nil {
continue
}
if bytes.Equal(response[:4], []byte("imok")) {
oks[i] = true
}
}
return oks
}
// FLWCons is a FourLetterWord helper function. In particular, this function
// pulls the ruok output from each server.
//
// As with FLWSrvr, the boolean value indicates whether one of the requests had
// an issue. The Clients struct has an Error value that can be checked.
func FLWCons(servers []string, timeout time.Duration) ([]*ServerClients, bool) {
var (
zrAddr = `^ /((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):(?:\d+))\[\d+\]`
zrPac = `\(queued=(\d+),recved=(\d+),sent=(\d+),sid=(0x[A-Za-z0-9]+),lop=(\w+),est=(\d+),to=(\d+),`
zrSesh = `lcxid=(0x[A-Za-z0-9]+),lzxid=(0x[A-Za-z0-9]+),lresp=(\d+),llat=(\d+),minlat=(\d+),avglat=(\d+),maxlat=(\d+)\)`
)
re, err := regexp.Compile(fmt.Sprintf("%v%v%v", zrAddr, zrPac, zrSesh))
if err != nil {
return nil, false
}
servers = FormatServers(servers)
sc := make([]*ServerClients, len(servers))
imOk := true
for i := range sc {
response, err := fourLetterWord(servers[i], "cons", timeout)
if err != nil {
sc[i] = &ServerClients{Error: err}
imOk = false
continue
}
scan := bufio.NewScanner(bytes.NewReader(response))
var clients []*ServerClient
for scan.Scan() {
line := scan.Bytes()
if len(line) == 0 {
continue
}
m := re.FindAllStringSubmatch(string(line), -1)
if m == nil {
err := fmt.Errorf("unable to parse fields from zookeeper response (no regex matches)")
sc[i] = &ServerClients{Error: err}
imOk = false
continue
}
match := m[0][1:]
queued, _ := strconv.ParseInt(match[1], 0, 64)
recvd, _ := strconv.ParseInt(match[2], 0, 64)
sent, _ := strconv.ParseInt(match[3], 0, 64)
sid, _ := strconv.ParseInt(match[4], 0, 64)
est, _ := strconv.ParseInt(match[6], 0, 64)
timeout, _ := strconv.ParseInt(match[7], 0, 32)
lresp, _ := strconv.ParseInt(match[10], 0, 64)
llat, _ := strconv.ParseInt(match[11], 0, 32)
minlat, _ := strconv.ParseInt(match[12], 0, 32)
avglat, _ := strconv.ParseInt(match[13], 0, 32)
maxlat, _ := strconv.ParseInt(match[14], 0, 32)
// zookeeper returns a value, '0xffffffffffffffff', as the
// Lzxid for PING requests in the 'cons' output.
// unfortunately, in Go that is an invalid int64 and is not represented
// as -1.
// However, converting the string value to a big.Int and then back to
// and int64 properly sets the value to -1
lzxid, ok := new(big.Int).SetString(match[9], 0)
var errVal error
if !ok {
errVal = fmt.Errorf("failed to convert lzxid value to big.Int")
imOk = false
}
lcxid, ok := new(big.Int).SetString(match[8], 0)
if !ok && errVal == nil {
errVal = fmt.Errorf("failed to convert lcxid value to big.Int")
imOk = false
}
clients = append(clients, &ServerClient{
Queued: queued,
Received: recvd,
Sent: sent,
SessionID: sid,
Lcxid: lcxid.Int64(),
Lzxid: lzxid.Int64(),
Timeout: int32(timeout),
LastLatency: int32(llat),
MinLatency: int32(minlat),
AvgLatency: int32(avglat),
MaxLatency: int32(maxlat),
Established: time.Unix(est, 0),
LastResponse: time.Unix(lresp, 0),
Addr: match[0],
LastOperation: match[5],
Error: errVal,
})
}
sc[i] = &ServerClients{Clients: clients}
}
return sc, imOk
}
func fourLetterWord(server, command string, timeout time.Duration) ([]byte, error) {
conn, err := net.DialTimeout("tcp", server, timeout)
if err != nil {
return nil, err
}
// the zookeeper server should automatically close this socket
// once the command has been processed, but better safe than sorry
defer conn.Close()
conn.SetWriteDeadline(time.Now().Add(timeout))
_, err = conn.Write([]byte(command))
if err != nil {
return nil, err
}
conn.SetReadDeadline(time.Now().Add(timeout))
resp, err := ioutil.ReadAll(conn)
if err != nil {
return nil, err
}
return resp, nil
}
package zk
import (
"errors"
"fmt"
"strconv"
"strings"
)
var (
// ErrDeadlock is returned by Lock when trying to lock twice without unlocking first
ErrDeadlock = errors.New("zk: trying to acquire a lock twice")
// ErrNotLocked is returned by Unlock when trying to release a lock that has not first be acquired.
ErrNotLocked = errors.New("zk: not locked")
)
// Lock is a mutual exclusion lock.
type Lock struct {
c *Conn
path string
acl []ACL
lockPath string
seq int
}
// NewLock creates a new lock instance using the provided connection, path, and acl.
// The path must be a node that is only used by this lock. A lock instances starts
// unlocked until Lock() is called.
func NewLock(c *Conn, path string, acl []ACL) *Lock {
return &Lock{
c: c,
path: path,
acl: acl,
}
}
func parseSeq(path string) (int, error) {
parts := strings.Split(path, "-")
return strconv.Atoi(parts[len(parts)-1])
}
// Lock attempts to acquire the lock. It will wait to return until the lock
// is acquired or an error occurs. If this instance already has the lock
// then ErrDeadlock is returned.
func (l *Lock) Lock() error {
if l.lockPath != "" {
return ErrDeadlock
}
prefix := fmt.Sprintf("%s/lock-", l.path)
path := ""
var err error
for i := 0; i < 3; i++ {
path, err = l.c.CreateProtectedEphemeralSequential(prefix, []byte{}, l.acl)
if err == ErrNoNode {
// Create parent node.
parts := strings.Split(l.path, "/")
pth := ""
for _, p := range parts[1:] {
pth += "/" + p
_, err := l.c.Create(pth, []byte{}, 0, l.acl)
if err != nil && err != ErrNodeExists {
return err
}
}
} else if err == nil {
break
} else {
return err
}
}
if err != nil {
return err
}
seq, err := parseSeq(path)
if err != nil {
return err
}
for {
children, _, err := l.c.Children(l.path)
if err != nil {
return err
}
lowestSeq := seq
prevSeq := 0
prevSeqPath := ""
for _, p := range children {
s, err := parseSeq(p)
if err != nil {
return err
}
if s < lowestSeq {
lowestSeq = s
}
if s < seq && s > prevSeq {
prevSeq = s
prevSeqPath = p
}
}
if seq == lowestSeq {
// Acquired the lock
break
}
// Wait on the node next in line for the lock
_, _, ch, err := l.c.GetW(l.path + "/" + prevSeqPath)
if err != nil && err != ErrNoNode {
return err
} else if err != nil && err == ErrNoNode {
// try again
continue
}
ev := <-ch
if ev.Err != nil {
return ev.Err
}
}
l.seq = seq
l.lockPath = path
return nil
}
// Unlock releases an acquired lock. If the lock is not currently acquired by
// this Lock instance than ErrNotLocked is returned.
func (l *Lock) Unlock() error {
if l.lockPath == "" {
return ErrNotLocked
}
if err := l.c.Delete(l.lockPath, -1); err != nil {
return err
}
l.lockPath = ""
l.seq = 0
return nil
}
package zk
import (
"fmt"
"io"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"time"
)
type TestServer struct {
Port int
Path string
Srv *Server
}
type TestCluster struct {
Path string
Servers []TestServer
}
func StartTestCluster(size int, stdout, stderr io.Writer) (*TestCluster, error) {
tmpPath, err := ioutil.TempDir("", "gozk")
if err != nil {
return nil, err
}
success := false
startPort := int(rand.Int31n(6000) + 10000)
cluster := &TestCluster{Path: tmpPath}
defer func() {
if !success {
cluster.Stop()
}
}()
for serverN := 0; serverN < size; serverN++ {
srvPath := filepath.Join(tmpPath, fmt.Sprintf("srv%d", serverN))
if err := os.Mkdir(srvPath, 0700); err != nil {
return nil, err
}
port := startPort + serverN*3
cfg := ServerConfig{
ClientPort: port,
DataDir: srvPath,
}
for i := 0; i < size; i++ {
cfg.Servers = append(cfg.Servers, ServerConfigServer{
ID: i + 1,
Host: "127.0.0.1",
PeerPort: startPort + i*3 + 1,
LeaderElectionPort: startPort + i*3 + 2,
})
}
cfgPath := filepath.Join(srvPath, "zoo.cfg")
fi, err := os.Create(cfgPath)
if err != nil {
return nil, err
}
err = cfg.Marshall(fi)
fi.Close()
if err != nil {
return nil, err
}
fi, err = os.Create(filepath.Join(srvPath, "myid"))
if err != nil {
return nil, err
}
_, err = fmt.Fprintf(fi, "%d\n", serverN+1)
fi.Close()
if err != nil {
return nil, err
}
srv := &Server{
ConfigPath: cfgPath,
Stdout: stdout,
Stderr: stderr,
}
if err := srv.Start(); err != nil {
return nil, err
}
cluster.Servers = append(cluster.Servers, TestServer{
Path: srvPath,
Port: cfg.ClientPort,
Srv: srv,
})
}
success = true
time.Sleep(time.Second) // Give the server time to become active. Should probably actually attempt to connect to verify.
return cluster, nil
}
func (ts *TestCluster) Connect(idx int) (*Conn, error) {
zk, _, err := Connect([]string{fmt.Sprintf("127.0.0.1:%d", ts.Servers[idx].Port)}, time.Second*15)
return zk, err
}
func (ts *TestCluster) ConnectAll() (*Conn, <-chan Event, error) {
return ts.ConnectAllTimeout(time.Second * 15)
}
func (ts *TestCluster) ConnectAllTimeout(sessionTimeout time.Duration) (*Conn, <-chan Event, error) {
hosts := make([]string, len(ts.Servers))
for i, srv := range ts.Servers {
hosts[i] = fmt.Sprintf("127.0.0.1:%d", srv.Port)
}
zk, ch, err := Connect(hosts, sessionTimeout)
return zk, ch, err
}
func (ts *TestCluster) Stop() error {
for _, srv := range ts.Servers {
srv.Srv.Stop()
}
defer os.RemoveAll(ts.Path)
return nil
}
package zk
import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
)
type ErrMissingServerConfigField string
func (e ErrMissingServerConfigField) Error() string {
return fmt.Sprintf("zk: missing server config field '%s'", string(e))
}
const (
DefaultServerTickTime = 2000
DefaultServerInitLimit = 10
DefaultServerSyncLimit = 5
DefaultServerAutoPurgeSnapRetainCount = 3
DefaultPeerPort = 2888
DefaultLeaderElectionPort = 3888
)
type ServerConfigServer struct {
ID int
Host string
PeerPort int
LeaderElectionPort int
}
type ServerConfig struct {
TickTime int // Number of milliseconds of each tick
InitLimit int // Number of ticks that the initial synchronization phase can take
SyncLimit int // Number of ticks that can pass between sending a request and getting an acknowledgement
DataDir string // Direcrory where the snapshot is stored
ClientPort int // Port at which clients will connect
AutoPurgeSnapRetainCount int // Number of snapshots to retain in dataDir
AutoPurgePurgeInterval int // Purge task internal in hours (0 to disable auto purge)
Servers []ServerConfigServer
}
func (sc ServerConfig) Marshall(w io.Writer) error {
if sc.DataDir == "" {
return ErrMissingServerConfigField("dataDir")
}
fmt.Fprintf(w, "dataDir=%s\n", sc.DataDir)
if sc.TickTime <= 0 {
sc.TickTime = DefaultServerTickTime
}
fmt.Fprintf(w, "tickTime=%d\n", sc.TickTime)
if sc.InitLimit <= 0 {
sc.InitLimit = DefaultServerInitLimit
}
fmt.Fprintf(w, "initLimit=%d\n", sc.InitLimit)
if sc.SyncLimit <= 0 {
sc.SyncLimit = DefaultServerSyncLimit
}
fmt.Fprintf(w, "syncLimit=%d\n", sc.SyncLimit)
if sc.ClientPort <= 0 {
sc.ClientPort = DefaultPort
}
fmt.Fprintf(w, "clientPort=%d\n", sc.ClientPort)
if sc.AutoPurgePurgeInterval > 0 {
if sc.AutoPurgeSnapRetainCount <= 0 {
sc.AutoPurgeSnapRetainCount = DefaultServerAutoPurgeSnapRetainCount
}
fmt.Fprintf(w, "autopurge.snapRetainCount=%d\n", sc.AutoPurgeSnapRetainCount)
fmt.Fprintf(w, "autopurge.purgeInterval=%d\n", sc.AutoPurgePurgeInterval)
}
if len(sc.Servers) > 0 {
for _, srv := range sc.Servers {
if srv.PeerPort <= 0 {
srv.PeerPort = DefaultPeerPort
}
if srv.LeaderElectionPort <= 0 {
srv.LeaderElectionPort = DefaultLeaderElectionPort
}
fmt.Fprintf(w, "server.%d=%s:%d:%d\n", srv.ID, srv.Host, srv.PeerPort, srv.LeaderElectionPort)
}
}
return nil
}
var jarSearchPaths = []string{
"zookeeper-*/contrib/fatjar/zookeeper-*-fatjar.jar",
"../zookeeper-*/contrib/fatjar/zookeeper-*-fatjar.jar",
"/usr/share/java/zookeeper-*.jar",
"/usr/local/zookeeper-*/contrib/fatjar/zookeeper-*-fatjar.jar",
"/usr/local/Cellar/zookeeper/*/libexec/contrib/fatjar/zookeeper-*-fatjar.jar",
}
func findZookeeperFatJar() string {
var paths []string
zkPath := os.Getenv("ZOOKEEPER_PATH")
if zkPath == "" {
paths = jarSearchPaths
} else {
paths = []string{filepath.Join(zkPath, "contrib/fatjar/zookeeper-*-fatjar.jar")}
}
for _, path := range paths {
matches, _ := filepath.Glob(path)
// TODO: could sort by version and pick latest
if len(matches) > 0 {
return matches[0]
}
}
return ""
}
type Server struct {
JarPath string
ConfigPath string
Stdout, Stderr io.Writer
cmd *exec.Cmd
}
func (srv *Server) Start() error {
if srv.JarPath == "" {
srv.JarPath = findZookeeperFatJar()
if srv.JarPath == "" {
return fmt.Errorf("zk: unable to find server jar")
}
}
srv.cmd = exec.Command("java", "-jar", srv.JarPath, "server", srv.ConfigPath)
srv.cmd.Stdout = srv.Stdout
srv.cmd.Stderr = srv.Stderr
return srv.cmd.Start()
}
func (srv *Server) Stop() error {
srv.cmd.Process.Signal(os.Kill)
return srv.cmd.Wait()
}
package zk
import (
"encoding/binary"
"fmt"
"io"
"net"
"sync"
)
var (
requests = make(map[int32]int32) // Map of Xid -> Opcode
requestsLock = &sync.Mutex{}
)
func trace(conn1, conn2 net.Conn, client bool) {
defer conn1.Close()
defer conn2.Close()
buf := make([]byte, 10*1024)
init := true
for {
_, err := io.ReadFull(conn1, buf[:4])
if err != nil {
fmt.Println("1>", client, err)
return
}
blen := int(binary.BigEndian.Uint32(buf[:4]))
_, err = io.ReadFull(conn1, buf[4:4+blen])
if err != nil {
fmt.Println("2>", client, err)
return
}
var cr interface{}
opcode := int32(-1)
readHeader := true
if client {
if init {
cr = &connectRequest{}
readHeader = false
} else {
xid := int32(binary.BigEndian.Uint32(buf[4:8]))
opcode = int32(binary.BigEndian.Uint32(buf[8:12]))
requestsLock.Lock()
requests[xid] = opcode
requestsLock.Unlock()
cr = requestStructForOp(opcode)
if cr == nil {
fmt.Printf("Unknown opcode %d\n", opcode)
}
}
} else {
if init {
cr = &connectResponse{}
readHeader = false
} else {
xid := int32(binary.BigEndian.Uint32(buf[4:8]))
zxid := int64(binary.BigEndian.Uint64(buf[8:16]))
errnum := int32(binary.BigEndian.Uint32(buf[16:20]))
if xid != -1 || zxid != -1 {
requestsLock.Lock()
found := false
opcode, found = requests[xid]
if !found {
opcode = 0
}
delete(requests, xid)
requestsLock.Unlock()
} else {
opcode = opWatcherEvent
}
cr = responseStructForOp(opcode)
if cr == nil {
fmt.Printf("Unknown opcode %d\n", opcode)
}
if errnum != 0 {
cr = &struct{}{}
}
}
}
opname := "."
if opcode != -1 {
opname = opNames[opcode]
}
if cr == nil {
fmt.Printf("%+v %s %+v\n", client, opname, buf[4:4+blen])
} else {
n := 4
hdrStr := ""
if readHeader {
var hdr interface{}
if client {
hdr = &requestHeader{}
} else {
hdr = &responseHeader{}
}
if n2, err := decodePacket(buf[n:n+blen], hdr); err != nil {
fmt.Println(err)
} else {
n += n2
}
hdrStr = fmt.Sprintf(" %+v", hdr)
}
if _, err := decodePacket(buf[n:n+blen], cr); err != nil {
fmt.Println(err)
}
fmt.Printf("%+v %s%s %+v\n", client, opname, hdrStr, cr)
}
init = false
written, err := conn2.Write(buf[:4+blen])
if err != nil {
fmt.Println("3>", client, err)
return
} else if written != 4+blen {
fmt.Printf("Written != read: %d != %d\n", written, blen)
return
}
}
}
func handleConnection(addr string, conn net.Conn) {
zkConn, err := net.Dial("tcp", addr)
if err != nil {
fmt.Println(err)
return
}
go trace(conn, zkConn, true)
trace(zkConn, conn, false)
}
func StartTracer(listenAddr, serverAddr string) {
ln, err := net.Listen("tcp", listenAddr)
if err != nil {
panic(err)
}
for {
conn, err := ln.Accept()
if err != nil {
fmt.Println(err)
continue
}
go handleConnection(serverAddr, conn)
}
}
package zk
import (
"crypto/sha1"
"encoding/base64"
"fmt"
"math/rand"
"strconv"
"strings"
)
// AuthACL produces an ACL list containing a single ACL which uses the
// provided permissions, with the scheme "auth", and ID "", which is used
// by ZooKeeper to represent any authenticated user.
func AuthACL(perms int32) []ACL {
return []ACL{{perms, "auth", ""}}
}
// WorldACL produces an ACL list containing a single ACL which uses the
// provided permissions, with the scheme "world", and ID "anyone", which
// is used by ZooKeeper to represent any user at all.
func WorldACL(perms int32) []ACL {
return []ACL{{perms, "world", "anyone"}}
}
func DigestACL(perms int32, user, password string) []ACL {
userPass := []byte(fmt.Sprintf("%s:%s", user, password))
h := sha1.New()
if n, err := h.Write(userPass); err != nil || n != len(userPass) {
panic("SHA1 failed")
}
digest := base64.StdEncoding.EncodeToString(h.Sum(nil))
return []ACL{{perms, "digest", fmt.Sprintf("%s:%s", user, digest)}}
}
// FormatServers takes a slice of addresses, and makes sure they are in a format
// that resembles <addr>:<port>. If the server has no port provided, the
// DefaultPort constant is added to the end.
func FormatServers(servers []string) []string {
for i := range servers {
if !strings.Contains(servers[i], ":") {
servers[i] = servers[i] + ":" + strconv.Itoa(DefaultPort)
}
}
return servers
}
// stringShuffle performs a Fisher-Yates shuffle on a slice of strings
func stringShuffle(s []string) {
for i := len(s) - 1; i > 0; i-- {
j := rand.Intn(i + 1)
s[i], s[j] = s[j], s[i]
}
}
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