Adding compatibility for marathon 1.5

This commit is contained in:
Trevin Teacutter 2018-07-03 16:42:03 -05:00 committed by Traefiker Bot
parent 461ebf6d88
commit 04d8b5d483
35 changed files with 2257 additions and 63 deletions

2
Gopkg.lock generated
View file

@ -540,7 +540,7 @@
[[projects]]
name = "github.com/gambol99/go-marathon"
packages = ["."]
revision = "03b46169666c53b9cc953b875ac5714e5103e064"
revision = "99a156b96fb2f9dbe6465affa51bbdccdd37174d"
[[projects]]
name = "github.com/ghodss/yaml"

View file

@ -55,6 +55,7 @@ func init() {
check.Suite(&HTTPSSuite{})
check.Suite(&LogRotationSuite{})
check.Suite(&MarathonSuite{})
check.Suite(&MarathonSuite15{})
check.Suite(&MesosSuite{})
check.Suite(&RateLimitSuite{})
check.Suite(&RetrySuite{})

View file

@ -0,0 +1,134 @@
package integration
import (
"fmt"
"net/http"
"os"
"time"
"github.com/containous/traefik/integration/try"
"github.com/containous/traefik/provider/label"
"github.com/gambol99/go-marathon"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
)
// Marathon test suites (using libcompose)
type MarathonSuite15 struct {
BaseSuite
marathonURL string
}
func (s *MarathonSuite15) SetUpSuite(c *check.C) {
s.createComposeProject(c, "marathon15")
s.composeProject.Start(c)
marathonIPAddr := s.composeProject.Container(c, containerNameMarathon).NetworkSettings.IPAddress
c.Assert(marathonIPAddr, checker.Not(checker.HasLen), 0)
s.marathonURL = "http://" + marathonIPAddr + ":8080"
// Wait for Marathon readiness prior to creating the client so that we
// don't run into the "all cluster members down" state right from the
// start.
err := try.GetRequest(s.marathonURL+"/v2/leader", 1*time.Minute, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
// Add entry for Mesos slave container IP address in the hosts file so
// that Traefik can properly forward traffic.
// This is necessary as long as we are still using the docker-compose v1
// spec. Once we switch to v2 or higher, we can have both the test/builder
// container and the Mesos slave container join the same custom network and
// enjoy DNS-discoverable container host names.
mesosSlaveIPAddr := s.composeProject.Container(c, containerNameMesosSlave).NetworkSettings.IPAddress
c.Assert(mesosSlaveIPAddr, checker.Not(checker.HasLen), 0)
err = s.extendDockerHostsFile(containerNameMesosSlave, mesosSlaveIPAddr)
c.Assert(err, checker.IsNil)
}
// extendDockerHostsFile extends the hosts file (/etc/hosts) by the given
// host/IP address mapping if we are running inside a container.
func (s *MarathonSuite15) extendDockerHostsFile(host, ipAddr string) error {
const hostsFile = "/etc/hosts"
// Determine if the run inside a container. The most reliable way to
// do this is to inject an indicator, which we do in terms of an
// environment variable.
// (See also https://groups.google.com/d/topic/docker-user/JOGE7AnJ3Gw/discussion.)
if os.Getenv("CONTAINER") == "DOCKER" {
// We are running inside a container -- extend the hosts file.
file, err := os.OpenFile(hostsFile, os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
return err
}
defer file.Close()
if _, err = file.WriteString(fmt.Sprintf("%s\t%s\n", ipAddr, host)); err != nil {
return err
}
}
return nil
}
func (s *MarathonSuite15) TestConfigurationUpdate(c *check.C) {
// Start Traefik.
file := s.adaptFile(c, "fixtures/marathon/simple.toml", struct {
MarathonURL string
}{s.marathonURL})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// Wait for Traefik to turn ready.
err = try.GetRequest("http://127.0.0.1:8000/", 2*time.Second, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil)
// Prepare Marathon client.
config := marathon.NewDefaultConfig()
config.URL = s.marathonURL
client, err := marathon.NewClient(config)
c.Assert(err, checker.IsNil)
// Create test application to be deployed.
app := marathon.NewDockerApplication().
Name("/whoami").
CPU(0.1).
Memory(32).
EmptyNetworks().
AddLabel(label.TraefikFrontendRule, "PathPrefix:/service")
app.Container.
Expose(80).
Docker.
Container("emilevauge/whoami")
*app.Networks = append(*app.Networks, *marathon.NewBridgePodNetwork())
// Deploy the test application.
deployApplication(c, client, app)
// Query application via Traefik.
err = try.GetRequest("http://127.0.0.1:8000/service", 30*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
// Create test application with services to be deployed.
app = marathon.NewDockerApplication().
Name("/whoami").
CPU(0.1).
Memory(32).
EmptyNetworks().
AddLabel(label.GetServiceLabel(label.TraefikFrontendRule, "app"), "PathPrefix:/app")
app.Container.
Expose(80).
Docker.
Container("emilevauge/whoami")
*app.Networks = append(*app.Networks, *marathon.NewBridgePodNetwork())
// Deploy the test application.
deployApplication(c, client, app)
// Query application via Traefik.
err = try.GetRequest("http://127.0.0.1:8000/app", 30*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
}

View file

@ -0,0 +1,55 @@
zookeeper:
image: zookeeper:3.4.10
mesos-master:
links:
- zookeeper
image: mesosphere/mesos-master:1.4.1
# Uncomment published ports for interactive debugging.
# ports:
# - "5050:5050"
environment:
- MESOS_HOSTNAME=mesos-master
- MESOS_CLUSTER=local
- MESOS_REGISTRY=in_memory
- MESOS_LOG_DIR=/var/log
- MESOS_WORK_DIR=/var/lib/mesos
- MESOS_ZK=zk://zookeeper:2181/mesos
mesos-slave:
links:
- zookeeper
- mesos-master
image: mesosphere/mesos-slave-dind:0.4.0_mesos-1.4.1_docker-17.05.0_ubuntu-16.04.3
privileged: true
# Uncomment published ports for interactive debugging.
# ports:
# - "5051:5051"
environment:
- MESOS_HOSTNAME=mesos-slave
- MESOS_CONTAINERIZERS=docker,mesos
- MESOS_ISOLATOR=cgroups/cpu,cgroups/mem
- MESOS_LOG_DIR=/var/log
- MESOS_MASTER=zk://zookeeper:2181/mesos
- MESOS_PORT=5051
- MESOS_WORK_DIR=/var/lib/mesos
- MESOS_EXECUTOR_REGISTRATION_TIMEOUT=5mins
- MESOS_EXECUTOR_SHUTDOWN_GRACE_PERIOD=90secs
- MESOS_DOCKER_STOP_TIMEOUT=60secs
- MESOS_RESOURCES=cpus:2;mem:2048;disk:20480;ports(*):[12000-12999]
- MESOS_SYSTEMD_ENABLE_SUPPORT=false
marathon:
links:
- zookeeper
- mesos-master
- mesos-slave
image: mesosphere/marathon:v1.5.9
# Uncomment published ports for interactive debugging.
# ports:
# - "8080:8080"
extra_hosts:
- "mesos-slave:172.17.0.1"
environment:
- MARATHON_ZK=zk://zookeeper:2181/marathon
- MARATHON_MASTER=zk://zookeeper:2181/mesos

View file

@ -83,6 +83,24 @@ func portDefinition(port int) func(*marathon.Application) {
}
}
func bridgeNetwork() func(*marathon.Application) {
return func(app *marathon.Application) {
app.SetNetwork("bridge", marathon.BridgeNetworkMode)
}
}
func containerNetwork() func(*marathon.Application) {
return func(app *marathon.Application) {
app.SetNetwork("cni", marathon.ContainerNetworkMode)
}
}
func hostNetwork() func(*marathon.Application) {
return func(app *marathon.Application) {
app.SetNetwork("host", marathon.HostNetworkMode)
}
}
func ipAddrPerTask(port int) func(*marathon.Application) {
return func(app *marathon.Application) {
p := marathon.Port{

View file

@ -347,7 +347,16 @@ func (p *Provider) getServer(app appData, task marathon.Task) (string, *types.Se
}
func (p *Provider) getServerHost(task marathon.Task, app appData) (string, error) {
if app.IPAddressPerTask == nil || p.ForceTaskHostname {
networks := app.Networks
var hostFlag bool
if networks == nil {
hostFlag = app.IPAddressPerTask == nil
} else {
hostFlag = (*networks)[0].Mode != marathon.ContainerNetworkMode
}
if hostFlag || p.ForceTaskHostname {
if len(task.Host) == 0 {
return "", fmt.Errorf("host is undefined for task %q app %q", task.ID, app.ID)
}

View file

@ -1289,7 +1289,30 @@ func TestGetServers(t *testing.T) {
expected: nil,
},
{
desc: "with 3 tasks",
desc: "with 3 tasks and hosts set",
application: application(
withTasks(
task(ipAddresses("1.1.1.1"), host("2.2.2.2"), withTaskID("A"), taskPorts(80)),
task(ipAddresses("1.1.1.2"), host("2.2.2.2"), withTaskID("B"), taskPorts(81)),
task(ipAddresses("1.1.1.3"), host("2.2.2.2"), withTaskID("C"), taskPorts(82))),
),
expected: map[string]types.Server{
"server-A": {
URL: "http://2.2.2.2:80",
Weight: label.DefaultWeight,
},
"server-B": {
URL: "http://2.2.2.2:81",
Weight: label.DefaultWeight,
},
"server-C": {
URL: "http://2.2.2.2:82",
Weight: label.DefaultWeight,
},
},
},
{
desc: "with 3 tasks and ipAddrPerTask set",
application: application(
ipAddrPerTask(80),
withTasks(
@ -1312,13 +1335,60 @@ func TestGetServers(t *testing.T) {
},
},
},
{
desc: "with 3 tasks and bridge network",
application: application(
bridgeNetwork(),
withTasks(
task(ipAddresses("1.1.1.1"), host("2.2.2.2"), withTaskID("A"), taskPorts(80)),
task(ipAddresses("1.1.1.2"), host("2.2.2.2"), withTaskID("B"), taskPorts(81)),
task(ipAddresses("1.1.1.3"), host("2.2.2.2"), withTaskID("C"), taskPorts(82))),
),
expected: map[string]types.Server{
"server-A": {
URL: "http://2.2.2.2:80",
Weight: label.DefaultWeight,
},
"server-B": {
URL: "http://2.2.2.2:81",
Weight: label.DefaultWeight,
},
"server-C": {
URL: "http://2.2.2.2:82",
Weight: label.DefaultWeight,
},
},
},
{
desc: "with 3 tasks and cni set",
application: application(
containerNetwork(),
withTasks(
task(ipAddresses("1.1.1.1"), withTaskID("A"), taskPorts(80)),
task(ipAddresses("1.1.1.2"), withTaskID("B"), taskPorts(80)),
task(ipAddresses("1.1.1.3"), withTaskID("C"), taskPorts(80))),
),
expected: map[string]types.Server{
"server-A": {
URL: "http://1.1.1.1:80",
Weight: label.DefaultWeight,
},
"server-B": {
URL: "http://1.1.1.2:80",
Weight: label.DefaultWeight,
},
"server-C": {
URL: "http://1.1.1.3:80",
Weight: label.DefaultWeight,
},
},
},
}
p := &Provider{}
for _, test := range testCases {
test := test
if test.desc == "should return nil when all hosts are empty" {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -1328,4 +1398,3 @@ func TestGetServers(t *testing.T) {
})
}
}
}

View file

@ -147,11 +147,21 @@ func (p *Provider) getFrontendRuleV1(application marathon.Application, serviceNa
// Deprecated
func (p *Provider) getBackendServerV1(task marathon.Task, application marathon.Application) string {
if application.IPAddressPerTask == nil || p.ForceTaskHostname {
networks := application.Networks
var hostFlag bool
if networks == nil {
hostFlag = application.IPAddressPerTask == nil
} else {
hostFlag = (*networks)[0].Mode != marathon.ContainerNetworkMode
}
if hostFlag || p.ForceTaskHostname {
return task.Host
}
numTaskIPAddresses := len(task.IPAddresses)
switch numTaskIPAddresses {
case 0:
log.Errorf("Missing IP address for Marathon application %s on task %s", application.ID, task.ID)

View file

@ -784,7 +784,30 @@ func TestGetServersV1(t *testing.T) {
expected: nil,
},
{
desc: "with 3 tasks",
desc: "with 3 tasks and hosts set",
application: application(
withTasks(
task(ipAddresses("1.1.1.1"), host("2.2.2.2"), withTaskID("A"), taskPorts(80)),
task(ipAddresses("1.1.1.2"), host("2.2.2.2"), withTaskID("B"), taskPorts(81)),
task(ipAddresses("1.1.1.3"), host("2.2.2.2"), withTaskID("C"), taskPorts(82))),
),
expected: map[string]types.Server{
"server-A": {
URL: "http://2.2.2.2:80",
Weight: label.DefaultWeight,
},
"server-B": {
URL: "http://2.2.2.2:81",
Weight: label.DefaultWeight,
},
"server-C": {
URL: "http://2.2.2.2:82",
Weight: label.DefaultWeight,
},
},
},
{
desc: "with 3 tasks and ipAddrPerTask set",
application: application(
ipAddrPerTask(80),
withTasks(
@ -807,13 +830,60 @@ func TestGetServersV1(t *testing.T) {
},
},
},
{
desc: "with 3 tasks and bridge network",
application: application(
bridgeNetwork(),
withTasks(
task(ipAddresses("1.1.1.1"), host("2.2.2.2"), withTaskID("A"), taskPorts(80)),
task(ipAddresses("1.1.1.2"), host("2.2.2.2"), withTaskID("B"), taskPorts(81)),
task(ipAddresses("1.1.1.3"), host("2.2.2.2"), withTaskID("C"), taskPorts(82))),
),
expected: map[string]types.Server{
"server-A": {
URL: "http://2.2.2.2:80",
Weight: label.DefaultWeight,
},
"server-B": {
URL: "http://2.2.2.2:81",
Weight: label.DefaultWeight,
},
"server-C": {
URL: "http://2.2.2.2:82",
Weight: label.DefaultWeight,
},
},
},
{
desc: "with 3 tasks and cni set",
application: application(
containerNetwork(),
withTasks(
task(ipAddresses("1.1.1.1"), withTaskID("A"), taskPorts(80)),
task(ipAddresses("1.1.1.2"), withTaskID("B"), taskPorts(80)),
task(ipAddresses("1.1.1.3"), withTaskID("C"), taskPorts(80))),
),
expected: map[string]types.Server{
"server-A": {
URL: "http://1.1.1.1:80",
Weight: label.DefaultWeight,
},
"server-B": {
URL: "http://1.1.1.2:80",
Weight: label.DefaultWeight,
},
"server-C": {
URL: "http://1.1.1.3:80",
Weight: label.DefaultWeight,
},
},
},
}
p := &Provider{}
for _, test := range testCases {
test := test
if test.desc == "should return nil when all hosts are empty" {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -823,4 +893,3 @@ func TestGetServersV1(t *testing.T) {
})
}
}
}

View file

@ -1,4 +1,4 @@
// Package mocks Code generated by mockery v1.0.0
// Package mocks Code generated by mockery v1.0.0. DO NOT EDIT.
// mockery -recursive -dir=vendor/github.com/gambol99/ -name=Marathon -output=provider/marathon/mocks
package mocks
@ -278,6 +278,29 @@ func (_m *Marathon) CreateGroup(group *marathon.Group) error {
return r0
}
// CreatePod provides a mock function with given fields: pod
func (_m *Marathon) CreatePod(pod *marathon.Pod) (*marathon.Pod, error) {
ret := _m.Called(pod)
var r0 *marathon.Pod
if rf, ok := ret.Get(0).(func(*marathon.Pod) *marathon.Pod); ok {
r0 = rf(pod)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Pod)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*marathon.Pod) error); ok {
r1 = rf(pod)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DeleteApplication provides a mock function with given fields: name, force
func (_m *Marathon) DeleteApplication(name string, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(name, force)
@ -347,6 +370,75 @@ func (_m *Marathon) DeleteGroup(name string, force bool) (*marathon.DeploymentID
return r0, r1
}
// DeletePod provides a mock function with given fields: name, force
func (_m *Marathon) DeletePod(name string, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(name, force)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string, bool) *marathon.DeploymentID); ok {
r0 = rf(name, force)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, bool) error); ok {
r1 = rf(name, force)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DeletePodInstance provides a mock function with given fields: name, instance
func (_m *Marathon) DeletePodInstance(name string, instance string) (*marathon.PodInstance, error) {
ret := _m.Called(name, instance)
var r0 *marathon.PodInstance
if rf, ok := ret.Get(0).(func(string, string) *marathon.PodInstance); ok {
r0 = rf(name, instance)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.PodInstance)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(name, instance)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DeletePodInstances provides a mock function with given fields: name, instances
func (_m *Marathon) DeletePodInstances(name string, instances []string) ([]*marathon.PodInstance, error) {
ret := _m.Called(name, instances)
var r0 []*marathon.PodInstance
if rf, ok := ret.Get(0).(func(string, []string) []*marathon.PodInstance); ok {
r0 = rf(name, instances)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*marathon.PodInstance)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, []string) error); ok {
r1 = rf(name, instances)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DeleteQueueDelay provides a mock function with given fields: appID
func (_m *Marathon) DeleteQueueDelay(appID string) error {
ret := _m.Called(appID)
@ -701,6 +793,158 @@ func (_m *Marathon) Ping() (bool, error) {
return r0, r1
}
// Pod provides a mock function with given fields: name
func (_m *Marathon) Pod(name string) (*marathon.Pod, error) {
ret := _m.Called(name)
var r0 *marathon.Pod
if rf, ok := ret.Get(0).(func(string) *marathon.Pod); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Pod)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// PodByVersion provides a mock function with given fields: name, version
func (_m *Marathon) PodByVersion(name string, version string) (*marathon.Pod, error) {
ret := _m.Called(name, version)
var r0 *marathon.Pod
if rf, ok := ret.Get(0).(func(string, string) *marathon.Pod); ok {
r0 = rf(name, version)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Pod)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(name, version)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// PodIsRunning provides a mock function with given fields: name
func (_m *Marathon) PodIsRunning(name string) bool {
ret := _m.Called(name)
var r0 bool
if rf, ok := ret.Get(0).(func(string) bool); ok {
r0 = rf(name)
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// PodStatus provides a mock function with given fields: name
func (_m *Marathon) PodStatus(name string) (*marathon.PodStatus, error) {
ret := _m.Called(name)
var r0 *marathon.PodStatus
if rf, ok := ret.Get(0).(func(string) *marathon.PodStatus); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.PodStatus)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// PodStatuses provides a mock function with given fields:
func (_m *Marathon) PodStatuses() ([]*marathon.PodStatus, error) {
ret := _m.Called()
var r0 []*marathon.PodStatus
if rf, ok := ret.Get(0).(func() []*marathon.PodStatus); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*marathon.PodStatus)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// PodVersions provides a mock function with given fields: name
func (_m *Marathon) PodVersions(name string) ([]string, error) {
ret := _m.Called(name)
var r0 []string
if rf, ok := ret.Get(0).(func(string) []string); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Pods provides a mock function with given fields:
func (_m *Marathon) Pods() ([]marathon.Pod, error) {
ret := _m.Called()
var r0 []marathon.Pod
if rf, ok := ret.Get(0).(func() []marathon.Pod); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]marathon.Pod)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Queue provides a mock function with given fields:
func (_m *Marathon) Queue() (*marathon.Queue, error) {
ret := _m.Called()
@ -835,6 +1079,27 @@ func (_m *Marathon) Subscriptions() (*marathon.Subscriptions, error) {
return r0, r1
}
// SupportsPods provides a mock function with given fields:
func (_m *Marathon) SupportsPods() (bool, error) {
ret := _m.Called()
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// TaskEndpoints provides a mock function with given fields: name, port, healthCheck
func (_m *Marathon) TaskEndpoints(name string, port int, healthCheck bool) ([]string, error) {
ret := _m.Called(name, port, healthCheck)
@ -941,6 +1206,29 @@ func (_m *Marathon) UpdateGroup(id string, group *marathon.Group, force bool) (*
return r0, r1
}
// UpdatePod provides a mock function with given fields: pod, force
func (_m *Marathon) UpdatePod(pod *marathon.Pod, force bool) (*marathon.Pod, error) {
ret := _m.Called(pod, force)
var r0 *marathon.Pod
if rf, ok := ret.Get(0).(func(*marathon.Pod, bool) *marathon.Pod); ok {
r0 = rf(pod, force)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Pod)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*marathon.Pod, bool) error); ok {
r1 = rf(pod, force)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// WaitOnApplication provides a mock function with given fields: name, timeout
func (_m *Marathon) WaitOnApplication(name string, timeout time.Duration) error {
ret := _m.Called(name, timeout)
@ -982,3 +1270,17 @@ func (_m *Marathon) WaitOnGroup(name string, timeout time.Duration) error {
return r0
}
// WaitOnPod provides a mock function with given fields: name, timeout
func (_m *Marathon) WaitOnPod(name string, timeout time.Duration) error {
ret := _m.Called(name, timeout)
var r0 error
if rf, ok := ret.Get(0).(func(string, time.Duration) error); ok {
r0 = rf(name, timeout)
} else {
r0 = ret.Error(0)
}
return r0
}

View file

@ -64,6 +64,8 @@ type Application struct {
CPUs float64 `json:"cpus,omitempty"`
GPUs *float64 `json:"gpus,omitempty"`
Disk *float64 `json:"disk,omitempty"`
Networks *[]PodNetwork `json:"networks,omitempty"`
// Contains non-secret environment variables. Secrets environment variables are part of the Secrets map.
Env *map[string]string `json:"-"`
Executor *string `json:"executor,omitempty"`
@ -494,9 +496,12 @@ func (r *Application) CheckHTTP(path string, port, interval int) (*Application,
}
// step: get the port index
portIndex, err := r.Container.Docker.ServicePortIndex(port)
if err != nil {
portIndex, err = r.Container.ServicePortIndex(port)
if err != nil {
return nil, err
}
}
health := NewDefaultHealthCheck()
health.IntervalSeconds = interval
*health.Path = path
@ -517,9 +522,12 @@ func (r *Application) CheckTCP(port, interval int) (*Application, error) {
}
// step: get the port index
portIndex, err := r.Container.Docker.ServicePortIndex(port)
if err != nil {
portIndex, err = r.Container.ServicePortIndex(port)
if err != nil {
return nil, err
}
}
health := NewDefaultHealthCheck()
health.Protocol = "TCP"
health.IntervalSeconds = interval
@ -810,24 +818,7 @@ func (r *marathonClient) CreateApplication(application *Application) (*Applicati
// name: the id of the application
// timeout: a duration of time to wait for an application to deploy
func (r *marathonClient) WaitOnApplication(name string, timeout time.Duration) error {
if r.appExistAndRunning(name) {
return nil
}
timeoutTimer := time.After(timeout)
ticker := time.NewTicker(r.config.PollingWaitTime)
defer ticker.Stop()
for {
select {
case <-timeoutTimer:
return ErrTimeoutError
case <-ticker.C:
if r.appExistAndRunning(name) {
return nil
}
}
}
return r.wait(name, timeout, r.appExistAndRunning)
}
func (r *marathonClient) appExistAndRunning(name string) bool {
@ -973,3 +964,22 @@ func (d *Discovery) AddPort(port Port) *Discovery {
d.Ports = &ports
return d
}
// EmptyNetworks explicitly empties networks
func (r *Application) EmptyNetworks() *Application {
r.Networks = &[]PodNetwork{}
return r
}
// SetNetwork sets the networking mode
func (r *Application) SetNetwork(name string, mode PodNetworkMode) *Application {
if r.Networks == nil {
r.EmptyNetworks()
}
network := PodNetwork{Name: name, Mode: mode}
networks := *r.Networks
networks = append(networks, network)
r.Networks = &networks
return r
}

View file

@ -61,7 +61,7 @@ func (app *Application) UnmarshalJSON(b []byte) error {
(*secrets)[secStore] = Secret{EnvVar: envName}
break
}
return fmt.Errorf("unexpected secret field %v or value type %T", secret, envValOrSecret[secret])
return fmt.Errorf("unexpected secret field %v of value type %T", secret, envValOrSecret[secret])
}
default:
return fmt.Errorf("unexpected environment variable type %T", envValOrSecret)

View file

@ -70,6 +70,40 @@ type Marathon interface {
// wait of application
WaitOnApplication(name string, timeout time.Duration) error
// -- PODS ---
// whether this version of Marathon supports pods
SupportsPods() (bool, error)
// get pod status
PodStatus(name string) (*PodStatus, error)
// get all pod statuses
PodStatuses() ([]*PodStatus, error)
// get pod
Pod(name string) (*Pod, error)
// get all pods
Pods() ([]Pod, error)
// create pod
CreatePod(pod *Pod) (*Pod, error)
// update pod
UpdatePod(pod *Pod, force bool) (*Pod, error)
// delete pod
DeletePod(name string, force bool) (*DeploymentID, error)
// wait on pod to be deployed
WaitOnPod(name string, timeout time.Duration) error
// check if a pod is running
PodIsRunning(name string) bool
// get versions of a pod
PodVersions(name string) ([]string, error)
// get pod by version
PodByVersion(name, version string) (*Pod, error)
// delete instances of a pod
DeletePodInstances(name string, instances []string) ([]*PodInstance, error)
// delete pod instance
DeletePodInstance(name, instance string) (*PodInstance, error)
// -- TASKS ---
// get a list of tasks for a specific application
@ -273,6 +307,10 @@ func (r *marathonClient) Ping() (bool, error) {
return true, nil
}
func (r *marathonClient) apiHead(path string, result interface{}) error {
return r.apiCall("HEAD", path, nil, result)
}
func (r *marathonClient) apiGet(path string, post, result interface{}) error {
return r.apiCall("GET", path, post, result)
}
@ -290,6 +328,8 @@ func (r *marathonClient) apiDelete(path string, post, result interface{}) error
}
func (r *marathonClient) apiCall(method, path string, body, result interface{}) error {
const deploymentHeader = "Marathon-Deployment-Id"
for {
// step: marshall the request to json
var requestBody []byte
@ -328,13 +368,26 @@ func (r *marathonClient) apiCall(method, path string, body, result interface{})
r.debugLog("apiCall(): %v %v returned %v %s", request.Method, request.URL.String(), response.Status, oneLogLine(respBody))
}
// step: check for a successfull response
// step: check for a successful response
if response.StatusCode >= 200 && response.StatusCode <= 299 {
if result != nil {
// If we have a deployment ID header and no response body, give them that
// This specifically handles the use case of a DELETE on an app/pod
// We need a way to retrieve the deployment ID
deploymentID := response.Header.Get(deploymentHeader)
if len(respBody) == 0 && deploymentID != "" {
d := DeploymentID{
DeploymentID: deploymentID,
}
if deployID, ok := result.(*DeploymentID); ok {
*deployID = d
}
} else {
if err := json.Unmarshal(respBody, result); err != nil {
return fmt.Errorf("failed to unmarshal response from Marathon: %s", err)
}
}
}
return nil
}
@ -350,6 +403,27 @@ func (r *marathonClient) apiCall(method, path string, body, result interface{})
}
}
// wait waits until the provided function returns true (or times out)
func (r *marathonClient) wait(name string, timeout time.Duration, fn func(string) bool) error {
timer := time.NewTimer(timeout)
defer timer.Stop()
ticker := time.NewTicker(r.config.PollingWaitTime)
defer ticker.Stop()
for {
if fn(name) {
return nil
}
select {
case <-timer.C:
return ErrTimeoutError
case <-ticker.C:
continue
}
}
}
// buildAPIRequest creates a default API request.
// It fails when there is no available member in the cluster anymore or when the request can not be built.
func (r *marathonClient) buildAPIRequest(method, path string, reader io.Reader) (request *http.Request, member string, err error) {

View file

@ -24,6 +24,7 @@ const (
marathonAPIEventStream = marathonAPIVersion + "/events"
marathonAPISubscription = marathonAPIVersion + "/eventSubscriptions"
marathonAPIApps = marathonAPIVersion + "/apps"
marathonAPIPods = marathonAPIVersion + "/pods"
marathonAPITasks = marathonAPIVersion + "/tasks"
marathonAPIDeployments = marathonAPIVersion + "/deployments"
marathonAPIGroups = marathonAPIVersion + "/groups"

View file

@ -29,6 +29,7 @@ type Deployment struct {
CurrentStep int `json:"currentStep"`
TotalSteps int `json:"totalSteps"`
AffectedApps []string `json:"affectedApps"`
AffectedPods []string `json:"affectedPods"`
Steps [][]*DeploymentStep `json:"-"`
XXStepsRaw json.RawMessage `json:"steps"` // Holds raw steps JSON to unmarshal later
CurrentActions []*DeploymentStep `json:"currentActions"`
@ -107,8 +108,17 @@ func (r *marathonClient) Deployments() ([]*Deployment, error) {
// id: the deployment id you wish to delete
// force: whether or not to force the deletion
func (r *marathonClient) DeleteDeployment(id string, force bool) (*DeploymentID, error) {
path := fmt.Sprintf("%s/%s", marathonAPIDeployments, id)
// if force=true, no body is returned
if force {
path += "?force=true"
return nil, r.apiDelete(path, nil, nil)
}
deployment := new(DeploymentID)
err := r.apiDelete(fmt.Sprintf("%s/%s", marathonAPIDeployments, id), nil, deployment)
err := r.apiDelete(path, nil, deployment)
if err != nil {
return nil, err
}

View file

@ -26,6 +26,7 @@ type Container struct {
Type string `json:"type,omitempty"`
Docker *Docker `json:"docker,omitempty"`
Volumes *[]Volume `json:"volumes,omitempty"`
PortMappings *[]PortMapping `json:"portMappings,omitempty"`
}
// PortMapping is the portmapping structure between container and mesos
@ -36,6 +37,7 @@ type PortMapping struct {
Name string `json:"name,omitempty"`
ServicePort int `json:"servicePort,omitempty"`
Protocol string `json:"protocol,omitempty"`
NetworkNames *[]string `json:"networkNames,omitempty"`
}
// Parameters is the parameters to pass to the docker client when creating the container
@ -53,11 +55,15 @@ type Volume struct {
Persistent *PersistentVolume `json:"persistent,omitempty"`
}
// PersistentVolumeType is the a persistent docker volume to be mounted
type PersistentVolumeType string
const (
// PersistentVolumeTypeRoot is the root path of the persistent volume
PersistentVolumeTypeRoot PersistentVolumeType = "root"
// PersistentVolumeTypePath is the mount path of the persistent volume
PersistentVolumeTypePath PersistentVolumeType = "path"
// PersistentVolumeTypeMount is the mount type of the persistent volume
PersistentVolumeTypeMount PersistentVolumeType = "mount"
)
@ -255,6 +261,19 @@ func (docker *Docker) Host() *Docker {
return docker
}
// Expose sets the container to expose the following TCP ports
// ports: the TCP ports the container is exposing
func (container *Container) Expose(ports ...int) *Container {
for _, port := range ports {
container.ExposePort(PortMapping{
ContainerPort: port,
HostPort: 0,
ServicePort: 0,
Protocol: "tcp"})
}
return container
}
// Expose sets the container to expose the following TCP ports
// ports: the TCP ports the container is exposing
func (docker *Docker) Expose(ports ...int) *Docker {
@ -268,6 +287,19 @@ func (docker *Docker) Expose(ports ...int) *Docker {
return docker
}
// ExposeUDP sets the container to expose the following UDP ports
// ports: the UDP ports the container is exposing
func (container *Container) ExposeUDP(ports ...int) *Container {
for _, port := range ports {
container.ExposePort(PortMapping{
ContainerPort: port,
HostPort: 0,
ServicePort: 0,
Protocol: "udp"})
}
return container
}
// ExposeUDP sets the container to expose the following UDP ports
// ports: the UDP ports the container is exposing
func (docker *Docker) ExposeUDP(ports ...int) *Docker {
@ -281,6 +313,19 @@ func (docker *Docker) ExposeUDP(ports ...int) *Docker {
return docker
}
// ExposePort exposes an port in the container
func (container *Container) ExposePort(portMapping PortMapping) *Container {
if container.PortMappings == nil {
container.EmptyPortMappings()
}
portMappings := *container.PortMappings
portMappings = append(portMappings, portMapping)
container.PortMappings = &portMappings
return container
}
// ExposePort exposes an port in the container
func (docker *Docker) ExposePort(portMapping PortMapping) *Docker {
if docker.PortMappings == nil {
@ -294,6 +339,14 @@ func (docker *Docker) ExposePort(portMapping PortMapping) *Docker {
return docker
}
// EmptyPortMappings explicitly empties the port mappings -- use this if you need to empty
// port mappings of an application that already has port mappings set (setting port mappings to nil will
// keep the current value)
func (container *Container) EmptyPortMappings() *Container {
container.PortMappings = &[]PortMapping{}
return container
}
// EmptyPortMappings explicitly empties the port mappings -- use this if you need to empty
// port mappings of an application that already has port mappings set (setting port mappings to nil will
// keep the current value)
@ -349,6 +402,24 @@ func (docker *Docker) EmptyParameters() *Docker {
return docker
}
// ServicePortIndex finds the service port index of the exposed port
// port: the port you are looking for
func (container *Container) ServicePortIndex(port int) (int, error) {
if container.PortMappings == nil || len(*container.PortMappings) == 0 {
return 0, errors.New("The container does not contain any port mappings to search")
}
// step: iterate and find the port
for index, containerPort := range *container.PortMappings {
if containerPort.ContainerPort == port {
return index, nil
}
}
// step: we didn't find the port in the mappings
return 0, fmt.Errorf("The container port %d was not found in the container port mappings", port)
}
// ServicePortIndex finds the service port index of the exposed port
// port: the port you are looking for
func (docker *Docker) ServicePortIndex(port int) (int, error) {
@ -364,5 +435,25 @@ func (docker *Docker) ServicePortIndex(port int) (int, error) {
}
// step: we didn't find the port in the mappings
return 0, fmt.Errorf("The container port required was not found in the container port mappings")
return 0, fmt.Errorf("The docker port %d was not found in the container port mappings", port)
}
// AddNetwork adds a network name to a PortMapping
// name: the name of the network
func (p *PortMapping) AddNetwork(name string) *PortMapping {
if p.NetworkNames == nil {
p.EmptyNetworkNames()
}
networks := *p.NetworkNames
networks = append(networks, name)
p.NetworkNames = &networks
return p
}
// EmptyNetworkNames explicitly empties the network names -- use this if you need to empty
// the network names of a port mapping that already has network names set
func (p *PortMapping) EmptyNetworkNames() *PortMapping {
p.NetworkNames = &[]string{}
return p
}

View file

@ -42,6 +42,8 @@ const (
ErrCodeServer
// ErrCodeUnknown specifies an unknown error.
ErrCodeUnknown
// ErrCodeMethodNotAllowed specifies a 405 Method Not Allowed.
ErrCodeMethodNotAllowed
)
// InvalidEndpointError indicates a endpoint error in the marathon urls
@ -82,6 +84,8 @@ func NewAPIError(code int, content []byte) error {
errDef = &simpleErrDef{code: ErrCodeForbidden}
case code == http.StatusNotFound:
errDef = &simpleErrDef{code: ErrCodeNotFound}
case code == http.StatusMethodNotAllowed:
errDef = &simpleErrDef{code: ErrCodeMethodNotAllowed}
case code == http.StatusConflict:
errDef = &conflictDef{}
case code == 422:

View file

@ -30,6 +30,35 @@ type HealthCheck struct {
IgnoreHTTP1xx *bool `json:"ignoreHttp1xx,omitempty"`
}
// HTTPHealthCheck describes an HTTP based health check
type HTTPHealthCheck struct {
Endpoint string `json:"endpoint,omitempty"`
Path string `json:"path,omitempty"`
Scheme string `json:"scheme,omitempty"`
}
// TCPHealthCheck describes a TCP based health check
type TCPHealthCheck struct {
Endpoint string `json:"endpoint,omitempty"`
}
// CommandHealthCheck describes a shell-based health check
type CommandHealthCheck struct {
Command PodCommand `json:"command,omitempty"`
}
// PodHealthCheck describes how to determine a pod's health
type PodHealthCheck struct {
HTTP *HTTPHealthCheck `json:"http,omitempty"`
TCP *TCPHealthCheck `json:"tcp,omitempty"`
Exec *CommandHealthCheck `json:"exec,omitempty"`
GracePeriodSeconds *int `json:"gracePeriodSeconds,omitempty"`
IntervalSeconds *int `json:"intervalSeconds,omitempty"`
MaxConsecutiveFailures *int `json:"maxConsecutiveFailures,omitempty"`
TimeoutSeconds *int `json:"timeoutSeconds,omitempty"`
DelaySeconds *int `json:"delaySeconds,omitempty"`
}
// SetCommand sets the given command on the health check.
func (h *HealthCheck) SetCommand(c Command) *HealthCheck {
h.Command = &c

124
vendor/github.com/gambol99/go-marathon/network.go generated vendored Normal file
View file

@ -0,0 +1,124 @@
/*
Copyright 2017 The go-marathon Authors All rights reserved.
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 marathon
// PodNetworkMode is the mode of a network descriptor
type PodNetworkMode string
const (
ContainerNetworkMode PodNetworkMode = "container"
BridgeNetworkMode PodNetworkMode = "container/bridge"
HostNetworkMode PodNetworkMode = "host"
)
// PodNetwork contains network descriptors for a pod
type PodNetwork struct {
Name string `json:"name,omitempty"`
Mode PodNetworkMode `json:"mode,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
}
// PodEndpoint describes an endpoint for a pod's container
type PodEndpoint struct {
Name string `json:"name,omitempty"`
ContainerPort int `json:"containerPort,omitempty"`
HostPort int `json:"hostPort,omitempty"`
Protocol []string `json:"protocol,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
}
// NewPodNetwork creates an empty PodNetwork
func NewPodNetwork(name string) *PodNetwork {
return &PodNetwork{
Name: name,
Labels: map[string]string{},
}
}
// NewPodEndpoint creates an empty PodEndpoint
func NewPodEndpoint() *PodEndpoint {
return &PodEndpoint{
Protocol: []string{},
Labels: map[string]string{},
}
}
// NewBridgePodNetwork creates a PodNetwork for a container in bridge mode
func NewBridgePodNetwork() *PodNetwork {
pn := NewPodNetwork("")
return pn.SetMode(BridgeNetworkMode)
}
// NewContainerPodNetwork creates a PodNetwork for a container
func NewContainerPodNetwork(name string) *PodNetwork {
pn := NewPodNetwork(name)
return pn.SetMode(ContainerNetworkMode)
}
// NewHostPodNetwork creates a PodNetwork for a container in host mode
func NewHostPodNetwork() *PodNetwork {
pn := NewPodNetwork("")
return pn.SetMode(HostNetworkMode)
}
// SetName sets the name of a PodNetwork
func (n *PodNetwork) SetName(name string) *PodNetwork {
n.Name = name
return n
}
// SetMode sets the mode of a PodNetwork
func (n *PodNetwork) SetMode(mode PodNetworkMode) *PodNetwork {
n.Mode = mode
return n
}
// Label sets a label of a PodNetwork
func (n *PodNetwork) Label(key, value string) *PodNetwork {
n.Labels[key] = value
return n
}
// SetName sets the name for a PodEndpoint
func (e *PodEndpoint) SetName(name string) *PodEndpoint {
e.Name = name
return e
}
// SetContainerPort sets the container port for a PodEndpoint
func (e *PodEndpoint) SetContainerPort(port int) *PodEndpoint {
e.ContainerPort = port
return e
}
// SetHostPort sets the host port for a PodEndpoint
func (e *PodEndpoint) SetHostPort(port int) *PodEndpoint {
e.HostPort = port
return e
}
// AddProtocol appends a protocol for a PodEndpoint
func (e *PodEndpoint) AddProtocol(protocol string) *PodEndpoint {
e.Protocol = append(e.Protocol, protocol)
return e
}
// Label sets a label for a PodEndpoint
func (e *PodEndpoint) Label(key, value string) *PodEndpoint {
e.Labels[key] = value
return e
}

277
vendor/github.com/gambol99/go-marathon/pod.go generated vendored Normal file
View file

@ -0,0 +1,277 @@
/*
Copyright 2017 The go-marathon Authors All rights reserved.
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 marathon
import (
"fmt"
)
// Pod is the definition for an pod in marathon
type Pod struct {
ID string `json:"id,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Version string `json:"version,omitempty"`
User string `json:"user,omitempty"`
// Non-secret environment variables. Actual secrets are stored in Secrets
// Magic happens at marshaling/unmarshaling to get them into the correct schema
Env map[string]string `json:"-"`
Secrets map[string]Secret `json:"-"`
Containers []*PodContainer `json:"containers,omitempty"`
Volumes []*PodVolume `json:"volumes,omitempty"`
Networks []*PodNetwork `json:"networks,omitempty"`
Scaling *PodScalingPolicy `json:"scaling,omitempty"`
Scheduling *PodSchedulingPolicy `json:"scheduling,omitempty"`
ExecutorResources *ExecutorResources `json:"executorResources,omitempty"`
}
// PodScalingPolicy is the scaling policy of the pod
type PodScalingPolicy struct {
Kind string `json:"kind"`
Instances int `json:"instances"`
MaxInstances int `json:"maxInstances,omitempty"`
}
// NewPod create an empty pod
func NewPod() *Pod {
return &Pod{
Labels: map[string]string{},
Env: map[string]string{},
Containers: []*PodContainer{},
Secrets: map[string]Secret{},
Volumes: []*PodVolume{},
Networks: []*PodNetwork{},
}
}
// Name sets the name / ID of the pod i.e. the identifier for this pod
func (p *Pod) Name(id string) *Pod {
p.ID = validateID(id)
return p
}
// SetUser sets the user to run the pod as
func (p *Pod) SetUser(user string) *Pod {
p.User = user
return p
}
// EmptyLabels empties the labels in a pod
func (p *Pod) EmptyLabels() *Pod {
p.Labels = make(map[string]string)
return p
}
// AddLabel adds a label to a pod
func (p *Pod) AddLabel(key, value string) *Pod {
p.Labels[key] = value
return p
}
// SetLabels sets the labels for a pod
func (p *Pod) SetLabels(labels map[string]string) *Pod {
p.Labels = labels
return p
}
// EmptyEnvs empties the environment variables for a pod
func (p *Pod) EmptyEnvs() *Pod {
p.Env = make(map[string]string)
return p
}
// AddEnv adds an environment variable to a pod
func (p *Pod) AddEnv(name, value string) *Pod {
if p.Env == nil {
p = p.EmptyEnvs()
}
p.Env[name] = value
return p
}
// ExtendEnv extends the environment with the new environment variables
func (p *Pod) ExtendEnv(env map[string]string) *Pod {
if p.Env == nil {
p = p.EmptyEnvs()
}
for k, v := range env {
p.AddEnv(k, v)
}
return p
}
// AddContainer adds a container to a pod
func (p *Pod) AddContainer(container *PodContainer) *Pod {
p.Containers = append(p.Containers, container)
return p
}
// EmptySecrets empties the secret sources in a pod
func (p *Pod) EmptySecrets() *Pod {
p.Secrets = make(map[string]Secret)
return p
}
// GetSecretSource gets the source of the named secret
func (p *Pod) GetSecretSource(name string) (string, error) {
if val, ok := p.Secrets[name]; ok {
return val.Source, nil
}
return "", fmt.Errorf("secret does not exist")
}
// AddSecret adds the secret to the pod
func (p *Pod) AddSecret(envVar, secretName, sourceName string) *Pod {
if p.Secrets == nil {
p = p.EmptySecrets()
}
p.Secrets[secretName] = Secret{EnvVar: envVar, Source: sourceName}
return p
}
// AddVolume adds a volume to a pod
func (p *Pod) AddVolume(vol *PodVolume) *Pod {
p.Volumes = append(p.Volumes, vol)
return p
}
// AddNetwork adds a PodNetwork to a pod
func (p *Pod) AddNetwork(net *PodNetwork) *Pod {
p.Networks = append(p.Networks, net)
return p
}
// Count sets the count of the pod
func (p *Pod) Count(count int) *Pod {
p.Scaling = &PodScalingPolicy{
Kind: "fixed",
Instances: count,
}
return p
}
// SetPodSchedulingPolicy sets the PodSchedulingPolicy of a pod
func (p *Pod) SetPodSchedulingPolicy(policy *PodSchedulingPolicy) *Pod {
p.Scheduling = policy
return p
}
// SetExecutorResources sets the resources for the pod executor
func (p *Pod) SetExecutorResources(resources *ExecutorResources) *Pod {
p.ExecutorResources = resources
return p
}
// SupportsPods determines if this version of marathon supports pods
// If HEAD returns 200 it does
func (r *marathonClient) SupportsPods() (bool, error) {
if err := r.apiHead(marathonAPIPods, nil); err != nil {
// If we get a 404 we can return a strict false, otherwise it could be
// a valid error
if apiErr, ok := err.(*APIError); ok && apiErr.ErrCode == ErrCodeNotFound {
return false, nil
}
return false, err
}
return true, nil
}
// Pod gets a pod object from marathon by name
func (r *marathonClient) Pod(name string) (*Pod, error) {
uri := buildPodURI(name)
result := new(Pod)
if err := r.apiGet(uri, nil, result); err != nil {
return nil, err
}
return result, nil
}
// Pods gets all pods from marathon
func (r *marathonClient) Pods() ([]Pod, error) {
var result []Pod
if err := r.apiGet(marathonAPIPods, nil, &result); err != nil {
return nil, err
}
return result, nil
}
// CreatePod creates a new pod in Marathon
func (r *marathonClient) CreatePod(pod *Pod) (*Pod, error) {
result := new(Pod)
if err := r.apiPost(marathonAPIPods, &pod, result); err != nil {
return nil, err
}
return result, nil
}
// DeletePod deletes a pod from marathon
func (r *marathonClient) DeletePod(name string, force bool) (*DeploymentID, error) {
uri := fmt.Sprintf("%s?force=%v", buildPodURI(name), force)
deployID := new(DeploymentID)
if err := r.apiDelete(uri, nil, deployID); err != nil {
return nil, err
}
return deployID, nil
}
// UpdatePod creates a new pod in Marathon
func (r *marathonClient) UpdatePod(pod *Pod, force bool) (*Pod, error) {
uri := fmt.Sprintf("%s?force=%v", buildPodURI(pod.ID), force)
result := new(Pod)
if err := r.apiPut(uri, pod, result); err != nil {
return nil, err
}
return result, nil
}
// PodVersions gets all the deployed versions of a pod
func (r *marathonClient) PodVersions(name string) ([]string, error) {
uri := buildPodVersionURI(name)
var result []string
if err := r.apiGet(uri, nil, &result); err != nil {
return nil, err
}
return result, nil
}
// PodByVersion gets a pod by a version identifier
func (r *marathonClient) PodByVersion(name, version string) (*Pod, error) {
uri := fmt.Sprintf("%s/%s", buildPodVersionURI(name), version)
result := new(Pod)
if err := r.apiGet(uri, nil, result); err != nil {
return nil, err
}
return result, nil
}
func buildPodVersionURI(name string) string {
return fmt.Sprintf("%s/%s::versions", marathonAPIPods, trimRootPath(name))
}
func buildPodURI(path string) string {
return fmt.Sprintf("%s/%s", marathonAPIPods, trimRootPath(path))
}

193
vendor/github.com/gambol99/go-marathon/pod_container.go generated vendored Normal file
View file

@ -0,0 +1,193 @@
/*
Copyright 2017 The go-marathon Authors All rights reserved.
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 marathon
// PodContainer describes a container in a pod
type PodContainer struct {
Name string `json:"name,omitempty"`
Exec *PodExec `json:"exec,omitempty"`
Resources *Resources `json:"resources,omitempty"`
Endpoints []*PodEndpoint `json:"endpoints,omitempty"`
Image *PodContainerImage `json:"image,omitempty"`
Env map[string]string `json:"-"`
Secrets map[string]Secret `json:"-"`
User string `json:"user,omitempty"`
HealthCheck *PodHealthCheck `json:"healthCheck,omitempty"`
VolumeMounts []*PodVolumeMount `json:"volumeMounts,omitempty"`
Artifacts []*PodArtifact `json:"artifacts,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Lifecycle PodLifecycle `json:"lifecycle,omitempty"`
}
// PodLifecycle describes the lifecycle of a pod
type PodLifecycle struct {
KillGracePeriodSeconds *float64 `json:"killGracePeriodSeconds,omitempty"`
}
// PodCommand is the command to run as the entrypoint of the container
type PodCommand struct {
Shell string `json:"shell,omitempty"`
}
// PodExec contains the PodCommand
type PodExec struct {
Command PodCommand `json:"command,omitempty"`
}
// PodArtifact describes how to obtain a generic artifact for a pod
type PodArtifact struct {
URI string `json:"uri,omitempty"`
Extract bool `json:"extract,omitempty"`
Executable bool `json:"executable,omitempty"`
Cache bool `json:"cache,omitempty"`
DestPath string `json:"destPath,omitempty"`
}
// NewPodContainer creates an empty PodContainer
func NewPodContainer() *PodContainer {
return &PodContainer{
Endpoints: []*PodEndpoint{},
Env: map[string]string{},
VolumeMounts: []*PodVolumeMount{},
Artifacts: []*PodArtifact{},
Labels: map[string]string{},
Resources: NewResources(),
}
}
// SetName sets the name of a pod container
func (p *PodContainer) SetName(name string) *PodContainer {
p.Name = name
return p
}
// SetCommand sets the shell command of a pod container
func (p *PodContainer) SetCommand(name string) *PodContainer {
p.Exec = &PodExec{
Command: PodCommand{
Shell: name,
},
}
return p
}
// CPUs sets the CPUs of a pod container
func (p *PodContainer) CPUs(cpu float64) *PodContainer {
p.Resources.Cpus = cpu
return p
}
// Memory sets the memory of a pod container
func (p *PodContainer) Memory(memory float64) *PodContainer {
p.Resources.Mem = memory
return p
}
// Storage sets the storage capacity of a pod container
func (p *PodContainer) Storage(disk float64) *PodContainer {
p.Resources.Disk = disk
return p
}
// GPUs sets the GPU requirements of a pod container
func (p *PodContainer) GPUs(gpu int32) *PodContainer {
p.Resources.Gpus = gpu
return p
}
// AddEndpoint appends an endpoint for a pod container
func (p *PodContainer) AddEndpoint(endpoint *PodEndpoint) *PodContainer {
p.Endpoints = append(p.Endpoints, endpoint)
return p
}
// SetImage sets the image of a pod container
func (p *PodContainer) SetImage(image *PodContainerImage) *PodContainer {
p.Image = image
return p
}
// EmptyEnvironment initialized env to empty
func (p *PodContainer) EmptyEnvs() *PodContainer {
p.Env = make(map[string]string)
return p
}
// AddEnvironment adds an environment variable for a pod container
func (p *PodContainer) AddEnv(name, value string) *PodContainer {
if p.Env == nil {
p = p.EmptyEnvs()
}
p.Env[name] = value
return p
}
// ExtendEnvironment extends the environment for a pod container
func (p *PodContainer) ExtendEnv(env map[string]string) *PodContainer {
if p.Env == nil {
p = p.EmptyEnvs()
}
for k, v := range env {
p.AddEnv(k, v)
}
return p
}
// AddSecret adds a secret to the environment for a pod container
func (p *PodContainer) AddSecret(name, secretName string) *PodContainer {
if p.Env == nil {
p = p.EmptyEnvs()
}
p.Env[name] = secretName
return p
}
// SetUser sets the user to run the pod as
func (p *PodContainer) SetUser(user string) *PodContainer {
p.User = user
return p
}
// SetHealthCheck sets the health check of a pod container
func (p *PodContainer) SetHealthCheck(healthcheck *PodHealthCheck) *PodContainer {
p.HealthCheck = healthcheck
return p
}
// AddVolumeMount appends a volume mount to a pod container
func (p *PodContainer) AddVolumeMount(mount *PodVolumeMount) *PodContainer {
p.VolumeMounts = append(p.VolumeMounts, mount)
return p
}
// AddArtifact appends an artifact to a pod container
func (p *PodContainer) AddArtifact(artifact *PodArtifact) *PodContainer {
p.Artifacts = append(p.Artifacts, artifact)
return p
}
// AddLabel adds a label to a pod container
func (p *PodContainer) AddLabel(key, value string) *PodContainer {
p.Labels[key] = value
return p
}
// SetLifecycle sets the lifecycle of a pod container
func (p *PodContainer) SetLifecycle(lifecycle PodLifecycle) *PodContainer {
p.Lifecycle = lifecycle
return p
}

View file

@ -0,0 +1,57 @@
/*
Copyright 2017 The go-marathon Authors All rights reserved.
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 marathon
// ImageType represents the image format type
type ImageType string
const (
// ImageTypeDocker is the docker format
ImageTypeDocker ImageType = "DOCKER"
// ImageTypeAppC is the appc format
ImageTypeAppC ImageType = "APPC"
)
// PodContainerImage describes how to retrieve the container image
type PodContainerImage struct {
Kind ImageType `json:"kind,omitempty"`
ID string `json:"id,omitempty"`
ForcePull bool `json:"forcePull,omitempty"`
}
// NewPodContainerImage creates an empty PodContainerImage
func NewPodContainerImage() *PodContainerImage {
return &PodContainerImage{}
}
// SetKind sets the Kind of the image
func (i *PodContainerImage) SetKind(typ ImageType) *PodContainerImage {
i.Kind = typ
return i
}
// SetID sets the ID of the image
func (i *PodContainerImage) SetID(id string) *PodContainerImage {
i.ID = id
return i
}
// NewDockerPodContainerImage creates a docker PodContainerImage
func NewDockerPodContainerImage() *PodContainerImage {
return NewPodContainerImage().SetKind(ImageTypeDocker)
}

View file

@ -0,0 +1,94 @@
/*
Copyright 2017 The go-marathon Authors All rights reserved.
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 marathon
import (
"encoding/json"
"fmt"
)
// PodContainerAlias aliases the PodContainer struct so that it will be marshaled/unmarshaled automatically
type PodContainerAlias PodContainer
// UnmarshalJSON unmarshals the given PodContainer JSON as expected except for environment variables and secrets.
// Environment variables are stored in the Env field. Secrets, including the environment variable part,
// are stored in the Secrets field.
func (p *PodContainer) UnmarshalJSON(b []byte) error {
aux := &struct {
*PodContainerAlias
Env map[string]interface{} `json:"environment"`
}{
PodContainerAlias: (*PodContainerAlias)(p),
}
if err := json.Unmarshal(b, aux); err != nil {
return fmt.Errorf("malformed pod container definition %v", err)
}
env := map[string]string{}
secrets := map[string]Secret{}
for envName, genericEnvValue := range aux.Env {
switch envValOrSecret := genericEnvValue.(type) {
case string:
env[envName] = envValOrSecret
case map[string]interface{}:
for secret, secretStore := range envValOrSecret {
if secStore, ok := secretStore.(string); ok && secret == "secret" {
secrets[secStore] = Secret{EnvVar: envName}
break
}
return fmt.Errorf("unexpected secret field %v of value type %T", secret, envValOrSecret[secret])
}
default:
return fmt.Errorf("unexpected environment variable type %T", envValOrSecret)
}
}
p.Env = env
for k, v := range aux.Secrets {
tmp := secrets[k]
tmp.Source = v.Source
secrets[k] = tmp
}
p.Secrets = secrets
return nil
}
// MarshalJSON marshals the given PodContainer as expected except for environment variables and secrets,
// which are marshaled from specialized structs. The environment variable piece of the secrets and other
// normal environment variables are combined and marshaled to the env field. The secrets and the related
// source are marshaled into the secrets field.
func (p *PodContainer) MarshalJSON() ([]byte, error) {
env := make(map[string]interface{})
secrets := make(map[string]TmpSecret)
if p.Env != nil {
for k, v := range p.Env {
env[string(k)] = string(v)
}
}
if p.Secrets != nil {
for k, v := range p.Secrets {
env[v.EnvVar] = TmpEnvSecret{Secret: k}
secrets[k] = TmpSecret{v.Source}
}
}
aux := &struct {
*PodContainerAlias
Env map[string]interface{} `json:"environment,omitempty"`
}{PodContainerAlias: (*PodContainerAlias)(p), Env: env}
return json.Marshal(aux)
}

105
vendor/github.com/gambol99/go-marathon/pod_instance.go generated vendored Normal file
View file

@ -0,0 +1,105 @@
/*
Copyright 2017 The go-marathon Authors All rights reserved.
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 marathon
import (
"fmt"
"time"
)
// PodInstance is the representation of an instance as returned by deleting an instance
type PodInstance struct {
InstanceID PodInstanceID `json:"instanceId"`
AgentInfo PodAgentInfo `json:"agentInfo"`
TasksMap map[string]PodTask `json:"tasksMap"`
RunSpecVersion time.Time `json:"runSpecVersion"`
State PodInstanceStateHistory `json:"state"`
UnreachableStrategy EnabledUnreachableStrategy `json:"unreachableStrategy"`
}
// PodInstanceStateHistory is the pod instance's state
type PodInstanceStateHistory struct {
Condition PodTaskCondition `json:"condition"`
Since time.Time `json:"since"`
ActiveSince time.Time `json:"activeSince"`
}
// PodInstanceID contains the instance ID
type PodInstanceID struct {
ID string `json:"idString"`
}
// PodAgentInfo contains info about the agent the instance is running on
type PodAgentInfo struct {
Host string `json:"host"`
AgentID string `json:"agentId"`
Attributes []string `json:"attributes"`
}
// PodTask contains the info about the specific task within the instance
type PodTask struct {
TaskID string `json:"taskId"`
RunSpecVersion time.Time `json:"runSpecVersion"`
Status PodTaskStatus `json:"status"`
}
// PodTaskStatus is the current status of the task
type PodTaskStatus struct {
StagedAt time.Time `json:"stagedAt"`
StartedAt time.Time `json:"startedAt"`
MesosStatus string `json:"mesosStatus"`
Condition PodTaskCondition `json:"condition"`
NetworkInfo PodNetworkInfo `json:"networkInfo"`
}
// PodTaskCondition contains a string representation of the condition
type PodTaskCondition struct {
Str string `json:"str"`
}
// PodNetworkInfo contains the network info for a task
type PodNetworkInfo struct {
HostName string `json:"hostName"`
HostPorts []int `json:"hostPorts"`
IPAddresses []IPAddress `json:"ipAddresses"`
}
// DeletePodInstances deletes all instances of the named pod
func (r *marathonClient) DeletePodInstances(name string, instances []string) ([]*PodInstance, error) {
uri := buildPodInstancesURI(name)
var result []*PodInstance
if err := r.apiDelete(uri, instances, &result); err != nil {
return nil, err
}
return result, nil
}
// DeletePodInstance deletes a specific instance of a pod
func (r *marathonClient) DeletePodInstance(name, instance string) (*PodInstance, error) {
uri := fmt.Sprintf("%s/%s", buildPodInstancesURI(name), instance)
result := new(PodInstance)
if err := r.apiDelete(uri, nil, result); err != nil {
return nil, err
}
return result, nil
}
func buildPodInstancesURI(path string) string {
return fmt.Sprintf("%s/%s::instances", marathonAPIPods, trimRootPath(path))
}

View file

@ -0,0 +1,89 @@
/*
Copyright 2017 The go-marathon Authors All rights reserved.
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 marathon
// PodInstanceState is the state of a specific pod instance
type PodInstanceState string
const (
// PodInstanceStatePending is when an instance is pending scheduling
PodInstanceStatePending PodInstanceState = "PENDING"
// PodInstanceStateStaging is when an instance is staged to be scheduled
PodInstanceStateStaging PodInstanceState = "STAGING"
// PodInstanceStateStable is when an instance is stably running
PodInstanceStateStable PodInstanceState = "STABLE"
// PodInstanceStateDegraded is when an instance is degraded status
PodInstanceStateDegraded PodInstanceState = "DEGRADED"
// PodInstanceStateTerminal is when an instance is terminal
PodInstanceStateTerminal PodInstanceState = "TERMINAL"
)
// PodInstanceStatus is the status of a pod instance
type PodInstanceStatus struct {
AgentHostname string `json:"agentHostname,omitempty"`
Conditions []*StatusCondition `json:"conditions,omitempty"`
Containers []*ContainerStatus `json:"containers,omitempty"`
ID string `json:"id,omitempty"`
LastChanged string `json:"lastChanged,omitempty"`
LastUpdated string `json:"lastUpdated,omitempty"`
Message string `json:"message,omitempty"`
Networks []*PodNetworkStatus `json:"networks,omitempty"`
Resources *Resources `json:"resources,omitempty"`
SpecReference string `json:"specReference,omitempty"`
Status PodInstanceState `json:"status,omitempty"`
StatusSince string `json:"statusSince,omitempty"`
}
// PodNetworkStatus is the networks attached to a pod instance
type PodNetworkStatus struct {
Addresses []string `json:"addresses,omitempty"`
Name string `json:"name,omitempty"`
}
// StatusCondition describes info about a status change
type StatusCondition struct {
Name string `json:"name,omitempty"`
Value string `json:"value,omitempty"`
Reason string `json:"reason,omitempty"`
LastChanged string `json:"lastChanged,omitempty"`
LastUpdated string `json:"lastUpdated,omitempty"`
}
// ContainerStatus contains all status information for a container instance
type ContainerStatus struct {
Conditions []*StatusCondition `json:"conditions,omitempty"`
ContainerID string `json:"containerId,omitempty"`
Endpoints []*PodEndpoint `json:"endpoints,omitempty"`
LastChanged string `json:"lastChanged,omitempty"`
LastUpdated string `json:"lastUpdated,omitempty"`
Message string `json:"message,omitempty"`
Name string `json:"name,omitempty"`
Resources *Resources `json:"resources,omitempty"`
Status string `json:"status,omitempty"`
StatusSince string `json:"statusSince,omitempty"`
Termination *ContainerTerminationState `json:"termination,omitempty"`
}
// ContainerTerminationState describes why a container terminated
type ContainerTerminationState struct {
ExitCode int `json:"exitCode,omitempty"`
Message string `json:"message,omitempty"`
}

View file

@ -0,0 +1,100 @@
/*
Copyright 2017 The go-marathon Authors All rights reserved.
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 marathon
import (
"encoding/json"
"fmt"
)
// PodAlias aliases the Pod struct so that it will be marshaled/unmarshaled automatically
type PodAlias Pod
// UnmarshalJSON unmarshals the given Pod JSON as expected except for environment variables and secrets.
// Environment variables are stored in the Env field. Secrets, including the environment variable part,
// are stored in the Secrets field.
func (p *Pod) UnmarshalJSON(b []byte) error {
aux := &struct {
*PodAlias
Env map[string]interface{} `json:"environment"`
Secrets map[string]TmpSecret `json:"secrets"`
}{
PodAlias: (*PodAlias)(p),
}
if err := json.Unmarshal(b, aux); err != nil {
return fmt.Errorf("malformed pod definition %v", err)
}
env := map[string]string{}
secrets := map[string]Secret{}
for envName, genericEnvValue := range aux.Env {
switch envValOrSecret := genericEnvValue.(type) {
case string:
env[envName] = envValOrSecret
case map[string]interface{}:
for secret, secretStore := range envValOrSecret {
if secStore, ok := secretStore.(string); ok && secret == "secret" {
secrets[secStore] = Secret{EnvVar: envName}
break
}
return fmt.Errorf("unexpected secret field %v of value type %T", secret, envValOrSecret[secret])
}
default:
return fmt.Errorf("unexpected environment variable type %T", envValOrSecret)
}
}
p.Env = env
for k, v := range aux.Secrets {
tmp := secrets[k]
tmp.Source = v.Source
secrets[k] = tmp
}
p.Secrets = secrets
return nil
}
// MarshalJSON marshals the given Pod as expected except for environment variables and secrets,
// which are marshaled from specialized structs. The environment variable piece of the secrets and other
// normal environment variables are combined and marshaled to the env field. The secrets and the related
// source are marshaled into the secrets field.
func (p *Pod) MarshalJSON() ([]byte, error) {
env := make(map[string]interface{})
secrets := make(map[string]TmpSecret)
if p.Env != nil {
for k, v := range p.Env {
env[string(k)] = string(v)
}
}
if p.Secrets != nil {
for k, v := range p.Secrets {
// Only add it to the root level pod environment if it's used
// Otherwise it's likely in one of the container environments
if v.EnvVar != "" {
env[v.EnvVar] = TmpEnvSecret{Secret: k}
}
secrets[k] = TmpSecret{v.Source}
}
}
aux := &struct {
*PodAlias
Env map[string]interface{} `json:"environment,omitempty"`
Secrets map[string]TmpSecret `json:"secrets,omitempty"`
}{PodAlias: (*PodAlias)(p), Env: env, Secrets: secrets}
return json.Marshal(aux)
}

View file

@ -0,0 +1,75 @@
/*
Copyright 2017 The go-marathon Authors All rights reserved.
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 marathon
// PodBackoff describes the backoff for re-run attempts of a pod
type PodBackoff struct {
Backoff *int `json:"backoff,omitempty"`
BackoffFactor *float64 `json:"backoffFactor,omitempty"`
MaxLaunchDelay *int `json:"maxLaunchDelay,omitempty"`
}
// PodUpgrade describes the policy for upgrading a pod in-place
type PodUpgrade struct {
MinimumHealthCapacity *float64 `json:"minimumHealthCapacity,omitempty"`
MaximumOverCapacity *float64 `json:"maximumOverCapacity,omitempty"`
}
// PodPlacement supports constraining which hosts a pod is placed on
type PodPlacement struct {
Constraints *[]Constraint `json:"constraints"`
AcceptedResourceRoles []string `json:"acceptedResourceRoles,omitempty"`
}
// PodSchedulingPolicy is the overarching pod scheduling policy
type PodSchedulingPolicy struct {
Backoff *PodBackoff `json:"backoff,omitempty"`
Upgrade *PodUpgrade `json:"upgrade,omitempty"`
Placement *PodPlacement `json:"placement,omitempty"`
}
// Constraint describes the constraint for pod placement
type Constraint struct {
FieldName string `json:"fieldName"`
Operator string `json:"operator"`
Value string `json:"value,omitempty"`
}
// NewPodPlacement creates an empty PodPlacement
func NewPodPlacement() *PodPlacement {
return &PodPlacement{
Constraints: &[]Constraint{},
AcceptedResourceRoles: []string{},
}
}
// AddConstraint adds a new constraint
// constraints: the constraint definition, one constraint per array element
func (r *PodPlacement) AddConstraint(constraint Constraint) *PodPlacement {
c := *r.Constraints
c = append(c, constraint)
r.Constraints = &c
return r
}
// NewPodSchedulingPolicy creates an empty PodSchedulingPolicy
func NewPodSchedulingPolicy() *PodSchedulingPolicy {
return &PodSchedulingPolicy{
Placement: NewPodPlacement(),
}
}

108
vendor/github.com/gambol99/go-marathon/pod_status.go generated vendored Normal file
View file

@ -0,0 +1,108 @@
/*
Copyright 2017 The go-marathon Authors All rights reserved.
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 marathon
import (
"fmt"
"time"
)
// PodState defines the state of a pod
type PodState string
const (
// PodStateDegraded is a degraded pod
PodStateDegraded PodState = "DEGRADED"
// PodStateStable is a stable pod
PodStateStable PodState = "STABLE"
// PodStateTerminal is a terminal pod
PodStateTerminal PodState = "TERMINAL"
)
// PodStatus describes the pod status
type PodStatus struct {
ID string `json:"id,omitempty"`
Spec *Pod `json:"spec,omitempty"`
Status PodState `json:"status,omitempty"`
StatusSince string `json:"statusSince,omitempty"`
Message string `json:"message,omitempty"`
Instances []*PodInstanceStatus `json:"instances,omitempty"`
TerminationHistory []*PodTerminationHistory `json:"terminationHistory,omitempty"`
LastUpdated string `json:"lastUpdated,omitempty"`
LastChanged string `json:"lastChanged,omitempty"`
}
// PodTerminationHistory is the termination history of the pod
type PodTerminationHistory struct {
InstanceID string `json:"instanceId,omitempty"`
StartedAt string `json:"startedAt,omitempty"`
TerminatedAt string `json:"terminatedAt,omitempty"`
Message string `json:"message,omitempty"`
Containers []*ContainerTerminationHistory `json:"containers,omitempty"`
}
// ContainerTerminationHistory is the termination history of a container in a pod
type ContainerTerminationHistory struct {
ContainerID string `json:"containerId,omitempty"`
LastKnownState string `json:"lastKnownState,omitempty"`
Termination *ContainerTerminationState `json:"termination,omitempty"`
}
// PodStatus retrieves the pod configuration from marathon
func (r *marathonClient) PodStatus(name string) (*PodStatus, error) {
var podStatus PodStatus
if err := r.apiGet(buildPodStatusURI(name), nil, &podStatus); err != nil {
return nil, err
}
return &podStatus, nil
}
// PodStatuses retrieves all pod configuration from marathon
func (r *marathonClient) PodStatuses() ([]*PodStatus, error) {
var podStatuses []*PodStatus
if err := r.apiGet(buildPodStatusURI(""), nil, &podStatuses); err != nil {
return nil, err
}
return podStatuses, nil
}
// WaitOnPod blocks until a pod to be deployed
func (r *marathonClient) WaitOnPod(name string, timeout time.Duration) error {
return r.wait(name, timeout, r.PodIsRunning)
}
// PodIsRunning returns whether the pod is stably running
func (r *marathonClient) PodIsRunning(name string) bool {
podStatus, err := r.PodStatus(name)
if apiErr, ok := err.(*APIError); ok && apiErr.ErrCode == ErrCodeNotFound {
return false
}
if err == nil && podStatus.Status == PodStateStable {
return true
}
return false
}
func buildPodStatusURI(path string) string {
return fmt.Sprintf("%s/%s::status", marathonAPIPods, trimRootPath(path))
}

View file

@ -32,7 +32,7 @@ type Item struct {
Application Application `json:"app"`
}
// Delay cotains the application postpone infomation
// Delay cotains the application postpone information
type Delay struct {
Overdue bool `json:"overdue"`
TimeLeftSeconds int `json:"timeLeftSeconds"`

View file

@ -24,7 +24,7 @@ type TaskLostBehaviorType string
const (
// TaskLostBehaviorTypeWaitForever indicates to not take any action when the resident task is lost
TaskLostBehaviorTypeWaitForever TaskLostBehaviorType = "WAIT_FOREVER"
// TaskLostBehaviorTypeWaitForever indicates to try relaunching the lost resident task on
// TaskLostBehaviorTypeRelaunchAfterTimeout indicates to try relaunching the lost resident task on
// another node after the relaunch escalation timeout has elapsed
TaskLostBehaviorTypeRelaunchAfterTimeout TaskLostBehaviorType = "RELAUNCH_AFTER_TIMEOUT"
)

37
vendor/github.com/gambol99/go-marathon/resources.go generated vendored Normal file
View file

@ -0,0 +1,37 @@
/*
Copyright 2017 The go-marathon Authors All rights reserved.
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 marathon
// ExecutorResources are the resources supported by an executor (a task running a pod)
type ExecutorResources struct {
Cpus float64 `json:"cpus,omitempty"`
Mem float64 `json:"mem,omitempty"`
Disk float64 `json:"disk,omitempty"`
}
// Resources are the full set of resources for a task
type Resources struct {
Cpus float64 `json:"cpus"`
Mem float64 `json:"mem"`
Disk float64 `json:"disk,omitempty"`
Gpus int32 `json:"gpus,omitempty"`
}
// NewResources creates an empty Resources
func NewResources() *Resources {
return &Resources{}
}

View file

@ -162,7 +162,7 @@ func (r *marathonClient) registerCallbackSubscription() error {
return nil
}
// registerSSESubscription starts a go routine that continously tries to
// registerSSESubscription starts a go routine that continuously tries to
// connect to the SSE stream and to process the received events. To establish
// the connection it tries the active cluster members until no more member is
// active. When this happens it will retry to get a connection every 5 seconds.

View file

@ -185,9 +185,12 @@ func (r *marathonClient) TaskEndpoints(name string, port int, healthCheck bool)
// step: we need to get the port index of the service we are interested in
portIndex, err := application.Container.Docker.ServicePortIndex(port)
if err != nil {
portIndex, err = application.Container.ServicePortIndex(port)
if err != nil {
return nil, err
}
}
// step: do we have any tasks?
if application.Tasks == nil || len(application.Tasks) == 0 {

View file

@ -21,6 +21,7 @@ import (
"fmt"
)
// UnreachableStrategyAbsenceReasonDisabled signifies the reason of disabled unreachable strategy
const UnreachableStrategyAbsenceReasonDisabled = "disabled"
// UnreachableStrategy is the unreachable strategy applied to an application.

45
vendor/github.com/gambol99/go-marathon/volume.go generated vendored Normal file
View file

@ -0,0 +1,45 @@
/*
Copyright 2017 The go-marathon Authors All rights reserved.
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 marathon
// PodVolume describes a volume on the host
type PodVolume struct {
Name string `json:"name,omitempty"`
Host string `json:"host,omitempty"`
}
// PodVolumeMount describes how to mount a volume into a task
type PodVolumeMount struct {
Name string `json:"name,omitempty"`
MountPath string `json:"mountPath,omitempty"`
}
// NewPodVolume creates a new PodVolume
func NewPodVolume(name, path string) *PodVolume {
return &PodVolume{
Name: name,
Host: path,
}
}
// NewPodVolumeMount creates a new PodVolumeMount
func NewPodVolumeMount(name, mount string) *PodVolumeMount {
return &PodVolumeMount{
Name: name,
MountPath: mount,
}
}