Upgrade go-marathon to 15ea23e.

Our vendored copy contains a bug that causes unavailable Marathon nodes
to never be marked as available again due to a misconstruction in the
URL to the Marathon health check / ping endpoint used by go-marathon
internally.

A fix[1] has been published.

[1]https://github.com/gambol99/go-marathon/pull/283
This commit is contained in:
Timo Reimann 2017-05-19 14:24:28 +02:00
parent 2e762e76f3
commit 219a6372b0
10 changed files with 126 additions and 72 deletions

4
glide.lock generated
View file

@ -1,5 +1,5 @@
hash: 1aa32496b865dda72d76c7cba3458f1c2c467acf0b99aab4609323f109aa64f6
updated: 2017-05-02T11:46:23.91434995-04:00
updated: 2017-05-19T23:30:19.890844996+02:00
imports:
- name: cloud.google.com/go
version: 2e6a95edb1071d750f6d7db777bf66cd2997af6c
@ -201,7 +201,7 @@ imports:
- name: github.com/fatih/color
version: 9131ab34cf20d2f6d83fdc67168a5430d1c7dc23
- name: github.com/gambol99/go-marathon
version: d672c6fbb499596869d95146a26e7d0746c06c54
version: 15ea23e360abb8b25071e677aed344f31838e403
- name: github.com/ghodss/yaml
version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee
- name: github.com/go-ini/ini

View file

@ -89,6 +89,8 @@ type Application struct {
TaskStats map[string]TaskStats `json:"taskStats,omitempty"`
User string `json:"user,omitempty"`
UpgradeStrategy *UpgradeStrategy `json:"upgradeStrategy,omitempty"`
UnreachableStrategy *UnreachableStrategy `json:"unreachableStrategy,omitempty"`
KillSelection string `json:"killSelection,omitempty"`
Uris *[]string `json:"uris,omitempty"`
Version string `json:"version,omitempty"`
VersionInfo *VersionInfo `json:"versionInfo,omitempty"`
@ -453,7 +455,7 @@ func (r *Application) DeploymentIDs() []*DeploymentID {
// CheckHTTP adds a HTTP check to an application
// port: the port the check should be checking
// interval: the interval in seconds the check should be performed
func (r *Application) CheckHTTP(uri string, port, interval int) (*Application, error) {
func (r *Application) CheckHTTP(path string, port, interval int) (*Application, error) {
if r.Container == nil || r.Container.Docker == nil {
return nil, ErrNoApplicationContainer
}
@ -464,7 +466,7 @@ func (r *Application) CheckHTTP(uri string, port, interval int) (*Application, e
}
health := NewDefaultHealthCheck()
health.IntervalSeconds = interval
*health.Path = uri
*health.Path = path
*health.PortIndex = portIndex
// step: add to the checks
r.AddHealthCheck(*health)
@ -555,6 +557,20 @@ func (r *Application) EmptyUpgradeStrategy() *Application {
return r
}
// SetUnreachableStrategy sets the unreachable strategy.
func (r *Application) SetUnreachableStrategy(us UnreachableStrategy) *Application {
r.UnreachableStrategy = &us
return r
}
// EmptyUnreachableStrategy explicitly empties the unreachable strategy -- use this if
// you need to empty the unreachable strategy of an application that already has
// the unreachable strategy set (setting it to nil will keep the current value).
func (r *Application) EmptyUnreachableStrategy() *Application {
r.UnreachableStrategy = &UnreachableStrategy{}
return r
}
// String returns the json representation of this application
func (r *Application) String() string {
s, err := json.MarshalIndent(r, "", " ")
@ -611,9 +627,9 @@ func (r *marathonClient) HasApplicationVersion(name, version string) (bool, erro
// ApplicationVersions is a list of versions which has been deployed with marathon for a specific application
// name: the id used to identify the application
func (r *marathonClient) ApplicationVersions(name string) (*ApplicationVersions, error) {
uri := fmt.Sprintf("%s/versions", buildURI(name))
path := fmt.Sprintf("%s/versions", buildPath(name))
versions := new(ApplicationVersions)
if err := r.apiGet(uri, nil, versions); err != nil {
if err := r.apiGet(path, nil, versions); err != nil {
return nil, err
}
return versions, nil
@ -623,9 +639,9 @@ func (r *marathonClient) ApplicationVersions(name string) (*ApplicationVersions,
// name: the id used to identify the application
// version: the version (normally a timestamp) you wish to change to
func (r *marathonClient) SetApplicationVersion(name string, version *ApplicationVersion) (*DeploymentID, error) {
uri := fmt.Sprintf(buildURI(name))
path := fmt.Sprintf(buildPath(name))
deploymentID := new(DeploymentID)
if err := r.apiPut(uri, version, deploymentID); err != nil {
if err := r.apiPut(path, version, deploymentID); err != nil {
return nil, err
}
@ -639,7 +655,7 @@ func (r *marathonClient) Application(name string) (*Application, error) {
Application *Application `json:"app"`
}
if err := r.apiGet(buildURI(name), nil, &wrapper); err != nil {
if err := r.apiGet(buildPath(name), nil, &wrapper); err != nil {
return nil, err
}
@ -650,7 +666,7 @@ func (r *marathonClient) Application(name string) (*Application, error) {
// name: the id used to identify the application
// opts: GetAppOpts request payload
func (r *marathonClient) ApplicationBy(name string, opts *GetAppOpts) (*Application, error) {
u, err := addOptions(buildURI(name), opts)
path, err := addOptions(buildPath(name), opts)
if err != nil {
return nil, err
}
@ -658,7 +674,7 @@ func (r *marathonClient) ApplicationBy(name string, opts *GetAppOpts) (*Applicat
Application *Application `json:"app"`
}
if err := r.apiGet(u, nil, &wrapper); err != nil {
if err := r.apiGet(path, nil, &wrapper); err != nil {
return nil, err
}
@ -671,8 +687,8 @@ func (r *marathonClient) ApplicationBy(name string, opts *GetAppOpts) (*Applicat
func (r *marathonClient) ApplicationByVersion(name, version string) (*Application, error) {
app := new(Application)
uri := fmt.Sprintf("%s/versions/%s", buildURI(name), version)
if err := r.apiGet(uri, nil, app); err != nil {
path := fmt.Sprintf("%s/versions/%s", buildPath(name), version)
if err := r.apiGet(path, nil, app); err != nil {
return nil, err
}
@ -779,10 +795,10 @@ func (r *marathonClient) appExistAndRunning(name string) bool {
// name: the id used to identify the application
// force: used to force the delete operation in case of blocked deployment
func (r *marathonClient) DeleteApplication(name string, force bool) (*DeploymentID, error) {
uri := buildURIWithForceParam(name, force)
path := buildPathWithForceParam(name, force)
// step: check of the application already exists
deployID := new(DeploymentID)
if err := r.apiDelete(uri, nil, deployID); err != nil {
if err := r.apiDelete(path, nil, deployID); err != nil {
return nil, err
}
@ -794,8 +810,8 @@ func (r *marathonClient) DeleteApplication(name string, force bool) (*Deployment
func (r *marathonClient) RestartApplication(name string, force bool) (*DeploymentID, error) {
deployment := new(DeploymentID)
var options struct{}
uri := buildURIWithForceParam(fmt.Sprintf("%s/restart", name), force)
if err := r.apiPost(uri, &options, deployment); err != nil {
path := buildPathWithForceParam(fmt.Sprintf("%s/restart", name), force)
if err := r.apiPost(path, &options, deployment); err != nil {
return nil, err
}
@ -810,9 +826,9 @@ func (r *marathonClient) ScaleApplicationInstances(name string, instances int, f
changes := new(Application)
changes.ID = validateID(name)
changes.Instances = &instances
uri := buildURIWithForceParam(name, force)
path := buildPathWithForceParam(name, force)
deployID := new(DeploymentID)
if err := r.apiPut(uri, changes, deployID); err != nil {
if err := r.apiPut(path, changes, deployID); err != nil {
return nil, err
}
@ -823,22 +839,22 @@ func (r *marathonClient) ScaleApplicationInstances(name string, instances int, f
// application: the structure holding the application configuration
func (r *marathonClient) UpdateApplication(application *Application, force bool) (*DeploymentID, error) {
result := new(DeploymentID)
uri := buildURIWithForceParam(application.ID, force)
if err := r.apiPut(uri, application, result); err != nil {
path := buildPathWithForceParam(application.ID, force)
if err := r.apiPut(path, application, result); err != nil {
return nil, err
}
return result, nil
}
func buildURIWithForceParam(path string, force bool) string {
uri := buildURI(path)
func buildPathWithForceParam(rootPath string, force bool) string {
path := buildPath(rootPath)
if force {
uri += "?force=true"
path += "?force=true"
}
return uri
return path
}
func buildURI(path string) string {
func buildPath(path string) string {
return fmt.Sprintf("%s/%s", marathonAPIApps, trimRootPath(path))
}

View file

@ -27,6 +27,7 @@ import (
"net/http"
"net/url"
"regexp"
"strings"
"sync"
"time"
)
@ -238,23 +239,23 @@ func (r *marathonClient) Ping() (bool, error) {
return true, nil
}
func (r *marathonClient) apiGet(uri string, post, result interface{}) error {
return r.apiCall("GET", uri, post, result)
func (r *marathonClient) apiGet(path string, post, result interface{}) error {
return r.apiCall("GET", path, post, result)
}
func (r *marathonClient) apiPut(uri string, post, result interface{}) error {
return r.apiCall("PUT", uri, post, result)
func (r *marathonClient) apiPut(path string, post, result interface{}) error {
return r.apiCall("PUT", path, post, result)
}
func (r *marathonClient) apiPost(uri string, post, result interface{}) error {
return r.apiCall("POST", uri, post, result)
func (r *marathonClient) apiPost(path string, post, result interface{}) error {
return r.apiCall("POST", path, post, result)
}
func (r *marathonClient) apiDelete(uri string, post, result interface{}) error {
return r.apiCall("DELETE", uri, post, result)
func (r *marathonClient) apiDelete(path string, post, result interface{}) error {
return r.apiCall("DELETE", path, post, result)
}
func (r *marathonClient) apiCall(method, url string, body, result interface{}) error {
func (r *marathonClient) apiCall(method, path string, body, result interface{}) error {
for {
// step: marshall the request to json
var requestBody []byte
@ -266,7 +267,7 @@ func (r *marathonClient) apiCall(method, url string, body, result interface{}) e
}
// step: create the API request
request, member, err := r.buildAPIRequest(method, url, bytes.NewReader(requestBody))
request, member, err := r.buildAPIRequest(method, path, bytes.NewReader(requestBody))
if err != nil {
return err
}
@ -317,7 +318,7 @@ func (r *marathonClient) apiCall(method, url string, body, result interface{}) e
}
// buildAPIRequest creates a default API request
func (r *marathonClient) buildAPIRequest(method, uri string, reader io.Reader) (request *http.Request, member string, err error) {
func (r *marathonClient) buildAPIRequest(method, path string, reader io.Reader) (request *http.Request, member string, err error) {
// Grab a member from the cluster
member, err = r.hosts.getMember()
if err != nil {
@ -325,16 +326,22 @@ func (r *marathonClient) buildAPIRequest(method, uri string, reader io.Reader) (
}
// Build the HTTP request to Marathon
request, err = r.client.buildMarathonRequest(method, member, uri, reader)
request, err = r.client.buildMarathonRequest(method, member, path, reader)
if err != nil {
return nil, member, err
}
return request, member, nil
}
func (rc *httpClient) buildMarathonRequest(method string, member string, uri string, reader io.Reader) (request *http.Request, err error) {
// buildMarathonRequest creates a new HTTP request and configures it according to the *httpClient configuration.
// The path must not contain a leading "/", otherwise buildMarathonRequest will panic.
func (rc *httpClient) buildMarathonRequest(method string, member string, path string, reader io.Reader) (request *http.Request, err error) {
if strings.HasPrefix(path, "/") {
panic(fmt.Sprintf("Path '%s' must not start with a leading slash", path))
}
// Create the endpoint URL
url := fmt.Sprintf("%s/%s", member, uri)
url := fmt.Sprintf("%s/%s", member, path)
// Instantiate an HTTP request
request, err = http.NewRequest(method, url, reader)

View file

@ -131,7 +131,7 @@ func (c *cluster) markDown(endpoint string) {
func (c *cluster) healthCheckNode(node *member) {
// step: wait for the node to become active ... we are assuming a /ping is enough here
for {
req, err := c.client.buildMarathonRequest("GET", node.endpoint, "/ping", nil)
req, err := c.client.buildMarathonRequest("GET", node.endpoint, "ping", nil)
if err == nil {
res, err := c.client.Do(req)
if err == nil && res.StatusCode == 200 {

View file

@ -106,12 +106,12 @@ func (r *marathonClient) Group(name string) (*Group, error) {
// GroupsBy retrieves a list of all the groups from marathon by embed options
// opts: GetGroupOpts request payload
func (r *marathonClient) GroupsBy(opts *GetGroupOpts) (*Groups, error) {
u, err := addOptions(marathonAPIGroups, opts)
path, err := addOptions(marathonAPIGroups, opts)
if err != nil {
return nil, err
}
groups := new(Groups)
if err := r.apiGet(u, "", groups); err != nil {
if err := r.apiGet(path, "", groups); err != nil {
return nil, err
}
return groups, nil
@ -121,12 +121,12 @@ func (r *marathonClient) GroupsBy(opts *GetGroupOpts) (*Groups, error) {
// name: the identifier for the group
// opts: GetGroupOpts request payload
func (r *marathonClient) GroupBy(name string, opts *GetGroupOpts) (*Group, error) {
u, err := addOptions(fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)), opts)
path, err := addOptions(fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)), opts)
if err != nil {
return nil, err
}
group := new(Group)
if err := r.apiGet(u, nil, group); err != nil {
if err := r.apiGet(path, nil, group); err != nil {
return nil, err
}
return group, nil
@ -135,8 +135,8 @@ func (r *marathonClient) GroupBy(name string, opts *GetGroupOpts) (*Group, error
// HasGroup checks if the group exists in marathon
// name: the identifier for the group
func (r *marathonClient) HasGroup(name string) (bool, error) {
uri := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name))
err := r.apiCall("GET", uri, "", nil)
path := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name))
err := r.apiCall("GET", path, "", nil)
if err != nil {
if apiErr, ok := err.(*APIError); ok && apiErr.ErrCode == ErrCodeNotFound {
return false, nil
@ -208,11 +208,9 @@ func (r *marathonClient) WaitOnGroup(name string, timeout time.Duration) error {
// force: used to force the delete operation in case of blocked deployment
func (r *marathonClient) DeleteGroup(name string, force bool) (*DeploymentID, error) {
version := new(DeploymentID)
uri := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name))
if force {
uri = uri + "?force=true"
}
if err := r.apiDelete(uri, nil, version); err != nil {
path := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name))
path = buildPathWithForceParam(path, force)
if err := r.apiDelete(path, nil, version); err != nil {
return nil, err
}
@ -225,11 +223,9 @@ func (r *marathonClient) DeleteGroup(name string, force bool) (*DeploymentID, er
// force: used to force the update operation in case of blocked deployment
func (r *marathonClient) UpdateGroup(name string, group *Group, force bool) (*DeploymentID, error) {
deploymentID := new(DeploymentID)
uri := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name))
if force {
uri = uri + "?force=true"
}
if err := r.apiPut(uri, group, deploymentID); err != nil {
path := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name))
path = buildPathWithForceParam(path, force)
if err := r.apiPut(path, group, deploymentID); err != nil {
return nil, err
}

View file

@ -51,8 +51,8 @@ func (r *marathonClient) Queue() (*Queue, error) {
// DeleteQueueDelay resets task launch delay of the specific application
// appID: the ID of the application
func (r *marathonClient) DeleteQueueDelay(appID string) error {
uri := fmt.Sprintf("%s/%s/delay", marathonAPIQueue, trimRootPath(appID))
err := r.apiDelete(uri, nil, nil)
path := fmt.Sprintf("%s/%s/delay", marathonAPIQueue, trimRootPath(appID))
err := r.apiDelete(path, nil, nil)
if err != nil {
return err
}

View file

@ -201,8 +201,8 @@ func (r *marathonClient) registerSSESubscription() error {
// Subscribe adds a URL to Marathon's callback facility
// callback : the URL you wish to subscribe
func (r *marathonClient) Subscribe(callback string) error {
uri := fmt.Sprintf("%s?callbackUrl=%s", marathonAPISubscription, callback)
return r.apiPost(uri, "", nil)
path := fmt.Sprintf("%s?callbackUrl=%s", marathonAPISubscription, callback)
return r.apiPost(path, "", nil)
}

View file

@ -79,13 +79,13 @@ func (r *Task) HasHealthCheckResults() bool {
// AllTasks lists tasks of all applications.
// opts: AllTasksOpts request payload
func (r *marathonClient) AllTasks(opts *AllTasksOpts) (*Tasks, error) {
u, err := addOptions(marathonAPITasks, opts)
path, err := addOptions(marathonAPITasks, opts)
if err != nil {
return nil, err
}
tasks := new(Tasks)
if err := r.apiGet(u, nil, tasks); err != nil {
if err := r.apiGet(path, nil, tasks); err != nil {
return nil, err
}
@ -107,14 +107,14 @@ func (r *marathonClient) Tasks(id string) (*Tasks, error) {
// id: the id of the application
// opts: KillApplicationTasksOpts request payload
func (r *marathonClient) KillApplicationTasks(id string, opts *KillApplicationTasksOpts) (*Tasks, error) {
u := fmt.Sprintf("%s/%s/tasks", marathonAPIApps, trimRootPath(id))
u, err := addOptions(u, opts)
path := fmt.Sprintf("%s/%s/tasks", marathonAPIApps, trimRootPath(id))
path, err := addOptions(path, opts)
if err != nil {
return nil, err
}
tasks := new(Tasks)
if err := r.apiDelete(u, nil, tasks); err != nil {
if err := r.apiDelete(path, nil, tasks); err != nil {
return nil, err
}
@ -129,8 +129,8 @@ func (r *marathonClient) KillTask(taskID string, opts *KillTaskOpts) (*Task, err
appName = strings.Replace(appName, "_", "/", -1)
taskID = strings.Replace(taskID, "/", "_", -1)
u := fmt.Sprintf("%s/%s/tasks/%s", marathonAPIApps, appName, taskID)
u, err := addOptions(u, opts)
path := fmt.Sprintf("%s/%s/tasks/%s", marathonAPIApps, appName, taskID)
path, err := addOptions(path, opts)
if err != nil {
return nil, err
}
@ -139,7 +139,7 @@ func (r *marathonClient) KillTask(taskID string, opts *KillTaskOpts) (*Task, err
Task Task `json:"task"`
})
if err := r.apiDelete(u, nil, wrappedTask); err != nil {
if err := r.apiDelete(path, nil, wrappedTask); err != nil {
return nil, err
}
@ -150,8 +150,8 @@ func (r *marathonClient) KillTask(taskID string, opts *KillTaskOpts) (*Task, err
// tasks: the array of task ids
// opts: KillTaskOpts request payload
func (r *marathonClient) KillTasks(tasks []string, opts *KillTaskOpts) error {
u := fmt.Sprintf("%s/delete", marathonAPITasks)
u, err := addOptions(u, opts)
path := fmt.Sprintf("%s/delete", marathonAPITasks)
path, err := addOptions(path, opts)
if err != nil {
return nil
}
@ -161,7 +161,7 @@ func (r *marathonClient) KillTasks(tasks []string, opts *KillTaskOpts) error {
}
post.IDs = tasks
return r.apiPost(u, &post, nil)
return r.apiPost(path, &post, nil)
}
// TaskEndpoints gets the endpoints i.e. HOST_IP:DYNAMIC_PORT for a specific application service

View file

@ -0,0 +1,35 @@
/*
Copyright 2017 Rohith 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
// UnreachableStrategy is the unreachable strategy applied to an application.
type UnreachableStrategy struct {
InactiveAfterSeconds *float64 `json:"inactiveAfterSeconds,omitempty"`
ExpungeAfterSeconds *float64 `json:"expungeAfterSeconds,omitempty"`
}
// SetInactiveAfterSeconds sets the period after which instance will be marked as inactive.
func (us UnreachableStrategy) SetInactiveAfterSeconds(cap float64) UnreachableStrategy {
us.InactiveAfterSeconds = &cap
return us
}
// SetExpungeAfterSeconds sets the period after which instance will be expunged.
func (us UnreachableStrategy) SetExpungeAfterSeconds(cap float64) UnreachableStrategy {
us.ExpungeAfterSeconds = &cap
return us
}