From 04d8b5d48384d5f837949525d6c1f44b3e67c565 Mon Sep 17 00:00:00 2001 From: Trevin Teacutter Date: Tue, 3 Jul 2018 16:42:03 -0500 Subject: [PATCH] Adding compatibility for marathon 1.5 --- Gopkg.lock | 2 +- integration/integration_test.go | 1 + integration/marathon15_test.go | 134 ++++++++ integration/resources/compose/marathon15.yml | 55 ++++ provider/marathon/builder_test.go | 18 ++ provider/marathon/config.go | 11 +- provider/marathon/config_test.go | 85 ++++- provider/marathon/deprecated_config.go | 12 +- provider/marathon/deprecated_config_test.go | 85 ++++- provider/marathon/mocks/Marathon.go | 304 +++++++++++++++++- .../gambol99/go-marathon/application.go | 66 ++-- .../go-marathon/application_marshalling.go | 2 +- .../github.com/gambol99/go-marathon/client.go | 80 ++++- .../github.com/gambol99/go-marathon/const.go | 1 + .../gambol99/go-marathon/deployment.go | 12 +- .../github.com/gambol99/go-marathon/docker.go | 103 +++++- .../github.com/gambol99/go-marathon/error.go | 4 + .../github.com/gambol99/go-marathon/health.go | 29 ++ .../gambol99/go-marathon/network.go | 124 +++++++ vendor/github.com/gambol99/go-marathon/pod.go | 277 ++++++++++++++++ .../gambol99/go-marathon/pod_container.go | 193 +++++++++++ .../go-marathon/pod_container_image.go | 57 ++++ .../go-marathon/pod_container_marshalling.go | 94 ++++++ .../gambol99/go-marathon/pod_instance.go | 105 ++++++ .../go-marathon/pod_instance_status.go | 89 +++++ .../gambol99/go-marathon/pod_marshalling.go | 100 ++++++ .../gambol99/go-marathon/pod_scheduling.go | 75 +++++ .../gambol99/go-marathon/pod_status.go | 108 +++++++ .../github.com/gambol99/go-marathon/queue.go | 2 +- .../gambol99/go-marathon/residency.go | 2 +- .../gambol99/go-marathon/resources.go | 37 +++ .../gambol99/go-marathon/subscription.go | 2 +- .../github.com/gambol99/go-marathon/task.go | 5 +- .../go-marathon/unreachable_strategy.go | 1 + .../github.com/gambol99/go-marathon/volume.go | 45 +++ 35 files changed, 2257 insertions(+), 63 deletions(-) create mode 100644 integration/marathon15_test.go create mode 100644 integration/resources/compose/marathon15.yml create mode 100644 vendor/github.com/gambol99/go-marathon/network.go create mode 100644 vendor/github.com/gambol99/go-marathon/pod.go create mode 100644 vendor/github.com/gambol99/go-marathon/pod_container.go create mode 100644 vendor/github.com/gambol99/go-marathon/pod_container_image.go create mode 100644 vendor/github.com/gambol99/go-marathon/pod_container_marshalling.go create mode 100644 vendor/github.com/gambol99/go-marathon/pod_instance.go create mode 100644 vendor/github.com/gambol99/go-marathon/pod_instance_status.go create mode 100644 vendor/github.com/gambol99/go-marathon/pod_marshalling.go create mode 100644 vendor/github.com/gambol99/go-marathon/pod_scheduling.go create mode 100644 vendor/github.com/gambol99/go-marathon/pod_status.go create mode 100644 vendor/github.com/gambol99/go-marathon/resources.go create mode 100644 vendor/github.com/gambol99/go-marathon/volume.go diff --git a/Gopkg.lock b/Gopkg.lock index 5d2df5027..ef2fc31bd 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -540,7 +540,7 @@ [[projects]] name = "github.com/gambol99/go-marathon" packages = ["."] - revision = "03b46169666c53b9cc953b875ac5714e5103e064" + revision = "99a156b96fb2f9dbe6465affa51bbdccdd37174d" [[projects]] name = "github.com/ghodss/yaml" diff --git a/integration/integration_test.go b/integration/integration_test.go index 335474a24..c817b2276 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -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{}) diff --git a/integration/marathon15_test.go b/integration/marathon15_test.go new file mode 100644 index 000000000..4fee2ab8f --- /dev/null +++ b/integration/marathon15_test.go @@ -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) +} diff --git a/integration/resources/compose/marathon15.yml b/integration/resources/compose/marathon15.yml new file mode 100644 index 000000000..eaf13c5b0 --- /dev/null +++ b/integration/resources/compose/marathon15.yml @@ -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 diff --git a/provider/marathon/builder_test.go b/provider/marathon/builder_test.go index 4fb77d8fb..36a428dd3 100644 --- a/provider/marathon/builder_test.go +++ b/provider/marathon/builder_test.go @@ -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{ diff --git a/provider/marathon/config.go b/provider/marathon/config.go index aaa0725fc..cb3c265eb 100644 --- a/provider/marathon/config.go +++ b/provider/marathon/config.go @@ -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) } diff --git a/provider/marathon/config_test.go b/provider/marathon/config_test.go index 6634f3baf..7e33cf49d 100644 --- a/provider/marathon/config_test.go +++ b/provider/marathon/config_test.go @@ -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,20 +1335,66 @@ 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() + t.Run(test.desc, func(t *testing.T) { + t.Parallel() - actual := p.getServers(withAppData(test.application, test.segmentName)) + actual := p.getServers(withAppData(test.application, test.segmentName)) - assert.Equal(t, test.expected, actual) - }) - } + assert.Equal(t, test.expected, actual) + }) } } diff --git a/provider/marathon/deprecated_config.go b/provider/marathon/deprecated_config.go index 5f4b50bca..69fba3e6f 100644 --- a/provider/marathon/deprecated_config.go +++ b/provider/marathon/deprecated_config.go @@ -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) diff --git a/provider/marathon/deprecated_config_test.go b/provider/marathon/deprecated_config_test.go index 3c5df44f6..b4d07846f 100644 --- a/provider/marathon/deprecated_config_test.go +++ b/provider/marathon/deprecated_config_test.go @@ -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,20 +830,66 @@ 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() + t.Run(test.desc, func(t *testing.T) { + t.Parallel() - actual := p.getServersV1(test.application, test.segmentName) + actual := p.getServersV1(test.application, test.segmentName) - assert.Equal(t, test.expected, actual) - }) - } + assert.Equal(t, test.expected, actual) + }) } } diff --git a/provider/marathon/mocks/Marathon.go b/provider/marathon/mocks/Marathon.go index 29048d930..a4cc12178 100644 --- a/provider/marathon/mocks/Marathon.go +++ b/provider/marathon/mocks/Marathon.go @@ -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 +} diff --git a/vendor/github.com/gambol99/go-marathon/application.go b/vendor/github.com/gambol99/go-marathon/application.go index fbb6dc1e6..958781f49 100644 --- a/vendor/github.com/gambol99/go-marathon/application.go +++ b/vendor/github.com/gambol99/go-marathon/application.go @@ -56,14 +56,16 @@ type Port struct { // Application is the definition for an application in marathon type Application struct { - ID string `json:"id,omitempty"` - Cmd *string `json:"cmd,omitempty"` - Args *[]string `json:"args,omitempty"` - Constraints *[][]string `json:"constraints,omitempty"` - Container *Container `json:"container,omitempty"` - CPUs float64 `json:"cpus,omitempty"` - GPUs *float64 `json:"gpus,omitempty"` - Disk *float64 `json:"disk,omitempty"` + ID string `json:"id,omitempty"` + Cmd *string `json:"cmd,omitempty"` + Args *[]string `json:"args,omitempty"` + Constraints *[][]string `json:"constraints,omitempty"` + Container *Container `json:"container,omitempty"` + 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"` @@ -495,7 +497,10 @@ 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 { - return nil, err + portIndex, err = r.Container.ServicePortIndex(port) + if err != nil { + return nil, err + } } health := NewDefaultHealthCheck() health.IntervalSeconds = interval @@ -518,7 +523,10 @@ func (r *Application) CheckTCP(port, interval int) (*Application, error) { // step: get the port index portIndex, err := r.Container.Docker.ServicePortIndex(port) if err != nil { - return nil, err + portIndex, err = r.Container.ServicePortIndex(port) + if err != nil { + return nil, err + } } health := NewDefaultHealthCheck() health.Protocol = "TCP" @@ -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 +} diff --git a/vendor/github.com/gambol99/go-marathon/application_marshalling.go b/vendor/github.com/gambol99/go-marathon/application_marshalling.go index c92b9ca01..8bedd9e96 100644 --- a/vendor/github.com/gambol99/go-marathon/application_marshalling.go +++ b/vendor/github.com/gambol99/go-marathon/application_marshalling.go @@ -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) diff --git a/vendor/github.com/gambol99/go-marathon/client.go b/vendor/github.com/gambol99/go-marathon/client.go index cc75c3d3e..00736a4b2 100644 --- a/vendor/github.com/gambol99/go-marathon/client.go +++ b/vendor/github.com/gambol99/go-marathon/client.go @@ -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,11 +368,24 @@ 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 err := json.Unmarshal(respBody, result); err != nil { - return fmt.Errorf("failed to unmarshal response from Marathon: %s", err) + // 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) { diff --git a/vendor/github.com/gambol99/go-marathon/const.go b/vendor/github.com/gambol99/go-marathon/const.go index 8b70c5acb..ba8d9b865 100644 --- a/vendor/github.com/gambol99/go-marathon/const.go +++ b/vendor/github.com/gambol99/go-marathon/const.go @@ -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" diff --git a/vendor/github.com/gambol99/go-marathon/deployment.go b/vendor/github.com/gambol99/go-marathon/deployment.go index f83821903..fc8480de9 100644 --- a/vendor/github.com/gambol99/go-marathon/deployment.go +++ b/vendor/github.com/gambol99/go-marathon/deployment.go @@ -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 } diff --git a/vendor/github.com/gambol99/go-marathon/docker.go b/vendor/github.com/gambol99/go-marathon/docker.go index 217d3bbbe..20bc1b761 100644 --- a/vendor/github.com/gambol99/go-marathon/docker.go +++ b/vendor/github.com/gambol99/go-marathon/docker.go @@ -23,9 +23,10 @@ import ( // Container is the definition for a container type in marathon type Container struct { - Type string `json:"type,omitempty"` - Docker *Docker `json:"docker,omitempty"` - Volumes *[]Volume `json:"volumes,omitempty"` + 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 PersistentVolumeType = "root" - PersistentVolumeTypePath PersistentVolumeType = "path" + // 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 } diff --git a/vendor/github.com/gambol99/go-marathon/error.go b/vendor/github.com/gambol99/go-marathon/error.go index 09d7dae49..d52e60de1 100644 --- a/vendor/github.com/gambol99/go-marathon/error.go +++ b/vendor/github.com/gambol99/go-marathon/error.go @@ -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: diff --git a/vendor/github.com/gambol99/go-marathon/health.go b/vendor/github.com/gambol99/go-marathon/health.go index b46d94aad..4ebeb3170 100644 --- a/vendor/github.com/gambol99/go-marathon/health.go +++ b/vendor/github.com/gambol99/go-marathon/health.go @@ -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 diff --git a/vendor/github.com/gambol99/go-marathon/network.go b/vendor/github.com/gambol99/go-marathon/network.go new file mode 100644 index 000000000..6804996f4 --- /dev/null +++ b/vendor/github.com/gambol99/go-marathon/network.go @@ -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 +} diff --git a/vendor/github.com/gambol99/go-marathon/pod.go b/vendor/github.com/gambol99/go-marathon/pod.go new file mode 100644 index 000000000..adeb70a54 --- /dev/null +++ b/vendor/github.com/gambol99/go-marathon/pod.go @@ -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)) +} diff --git a/vendor/github.com/gambol99/go-marathon/pod_container.go b/vendor/github.com/gambol99/go-marathon/pod_container.go new file mode 100644 index 000000000..c5ef908ff --- /dev/null +++ b/vendor/github.com/gambol99/go-marathon/pod_container.go @@ -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 +} diff --git a/vendor/github.com/gambol99/go-marathon/pod_container_image.go b/vendor/github.com/gambol99/go-marathon/pod_container_image.go new file mode 100644 index 000000000..9cc7d41d3 --- /dev/null +++ b/vendor/github.com/gambol99/go-marathon/pod_container_image.go @@ -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) +} diff --git a/vendor/github.com/gambol99/go-marathon/pod_container_marshalling.go b/vendor/github.com/gambol99/go-marathon/pod_container_marshalling.go new file mode 100644 index 000000000..917aeac1c --- /dev/null +++ b/vendor/github.com/gambol99/go-marathon/pod_container_marshalling.go @@ -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) +} diff --git a/vendor/github.com/gambol99/go-marathon/pod_instance.go b/vendor/github.com/gambol99/go-marathon/pod_instance.go new file mode 100644 index 000000000..5df0afa16 --- /dev/null +++ b/vendor/github.com/gambol99/go-marathon/pod_instance.go @@ -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)) +} diff --git a/vendor/github.com/gambol99/go-marathon/pod_instance_status.go b/vendor/github.com/gambol99/go-marathon/pod_instance_status.go new file mode 100644 index 000000000..cc8915599 --- /dev/null +++ b/vendor/github.com/gambol99/go-marathon/pod_instance_status.go @@ -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"` +} diff --git a/vendor/github.com/gambol99/go-marathon/pod_marshalling.go b/vendor/github.com/gambol99/go-marathon/pod_marshalling.go new file mode 100644 index 000000000..de1cb5f4f --- /dev/null +++ b/vendor/github.com/gambol99/go-marathon/pod_marshalling.go @@ -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) +} diff --git a/vendor/github.com/gambol99/go-marathon/pod_scheduling.go b/vendor/github.com/gambol99/go-marathon/pod_scheduling.go new file mode 100644 index 000000000..a63ce42d4 --- /dev/null +++ b/vendor/github.com/gambol99/go-marathon/pod_scheduling.go @@ -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(), + } +} diff --git a/vendor/github.com/gambol99/go-marathon/pod_status.go b/vendor/github.com/gambol99/go-marathon/pod_status.go new file mode 100644 index 000000000..227868ef4 --- /dev/null +++ b/vendor/github.com/gambol99/go-marathon/pod_status.go @@ -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)) +} diff --git a/vendor/github.com/gambol99/go-marathon/queue.go b/vendor/github.com/gambol99/go-marathon/queue.go index 2eaede34f..b9cfa4827 100644 --- a/vendor/github.com/gambol99/go-marathon/queue.go +++ b/vendor/github.com/gambol99/go-marathon/queue.go @@ -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"` diff --git a/vendor/github.com/gambol99/go-marathon/residency.go b/vendor/github.com/gambol99/go-marathon/residency.go index ea9d72d6c..9fc94cea4 100644 --- a/vendor/github.com/gambol99/go-marathon/residency.go +++ b/vendor/github.com/gambol99/go-marathon/residency.go @@ -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" ) diff --git a/vendor/github.com/gambol99/go-marathon/resources.go b/vendor/github.com/gambol99/go-marathon/resources.go new file mode 100644 index 000000000..a2aaeed06 --- /dev/null +++ b/vendor/github.com/gambol99/go-marathon/resources.go @@ -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{} +} diff --git a/vendor/github.com/gambol99/go-marathon/subscription.go b/vendor/github.com/gambol99/go-marathon/subscription.go index a9f75c664..561026f28 100644 --- a/vendor/github.com/gambol99/go-marathon/subscription.go +++ b/vendor/github.com/gambol99/go-marathon/subscription.go @@ -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. diff --git a/vendor/github.com/gambol99/go-marathon/task.go b/vendor/github.com/gambol99/go-marathon/task.go index d923692d7..e17cf24ed 100644 --- a/vendor/github.com/gambol99/go-marathon/task.go +++ b/vendor/github.com/gambol99/go-marathon/task.go @@ -186,7 +186,10 @@ 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 { - return nil, err + portIndex, err = application.Container.ServicePortIndex(port) + if err != nil { + return nil, err + } } // step: do we have any tasks? diff --git a/vendor/github.com/gambol99/go-marathon/unreachable_strategy.go b/vendor/github.com/gambol99/go-marathon/unreachable_strategy.go index 9ed02df9f..6563239ab 100644 --- a/vendor/github.com/gambol99/go-marathon/unreachable_strategy.go +++ b/vendor/github.com/gambol99/go-marathon/unreachable_strategy.go @@ -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. diff --git a/vendor/github.com/gambol99/go-marathon/volume.go b/vendor/github.com/gambol99/go-marathon/volume.go new file mode 100644 index 000000000..89a2d97a4 --- /dev/null +++ b/vendor/github.com/gambol99/go-marathon/volume.go @@ -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, + } +}