Merge branch 'v1.3' into merge-back-1_3_0-rc3

This commit is contained in:
Fernandez Ludovic 2017-05-24 20:39:38 +02:00
commit 2833d68f15
17 changed files with 357 additions and 195 deletions

View file

@ -1,5 +1,24 @@
# Change Log
## [v1.3.0-rc3](https://github.com/containous/traefik/tree/v1.3.0-rc3) (2017-05-24)
[All Commits](https://github.com/containous/traefik/compare/v1.3.0-rc2...v1.3.0-rc3)
**Enhancements:**
- [#1658](https://api.github.com/repos/containous/traefik/issues/1658) Get testify/require dependency. ([timoreimann](https://api.github.com/users/timoreimann))
**Bug fixes:**
- [#1507](https://api.github.com/repos/containous/traefik/issues/1507) Create log folder if not present ([tanyadegurechaff](https://api.github.com/users/tanyadegurechaff))
- [#1604](https://api.github.com/repos/containous/traefik/issues/1604) [k8s] Ignore Ingresses with empty Endpoint subsets. ([timoreimann](https://api.github.com/users/timoreimann))
- [#1630](https://api.github.com/repos/containous/traefik/issues/1630) [k8s] Remove rule type path list. ([timoreimann](https://api.github.com/users/timoreimann))
- [#1635](https://api.github.com/repos/containous/traefik/issues/1635) Upgrade go-marathon to 15ea23e. ([timoreimann](https://api.github.com/users/timoreimann))
- [#1638](https://api.github.com/repos/containous/traefik/issues/1638) Fix behavior for PathPrefixStrip ([seryl](https://api.github.com/users/seryl))
- [#1654](https://api.github.com/repos/containous/traefik/issues/1654) fix: Empty Rancher Service Labels. ([ldez](https://api.github.com/users/ldez))
**Documentation:**
- [#1578](https://api.github.com/repos/containous/traefik/issues/1578) Add Marathon guide. ([Stibbons](https://api.github.com/users/Stibbons))
- [#1602](https://api.github.com/repos/containous/traefik/issues/1602) Re Orginise k8s docs to make 1.6 usage easier ([errm](https://api.github.com/users/errm))
- [#1642](https://api.github.com/repos/containous/traefik/issues/1642) Update changelog ([ldez](https://api.github.com/users/ldez))
## [v1.3.0-rc2](https://github.com/containous/traefik/tree/v1.3.0-rc2) (2017-05-16)
[All Commits](https://github.com/containous/traefik/compare/v1.3.0-rc1...v1.3.0-rc2)

4
glide.lock generated
View file

@ -1,5 +1,5 @@
hash: e59e8244152a823cd3633fb09cdd583c4e5be78d7b50fb7047ba6b6a9ed5e8ec
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

@ -17,17 +17,29 @@ type StripPrefix struct {
func (s *StripPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for _, prefix := range s.Prefixes {
if p := strings.TrimPrefix(r.URL.Path, strings.TrimSpace(prefix)); len(p) < len(r.URL.Path) {
r.URL.Path = p
r.Header[forwardedPrefixHeader] = []string{prefix}
r.RequestURI = r.URL.RequestURI()
s.Handler.ServeHTTP(w, r)
origPrefix := strings.TrimSpace(prefix)
if origPrefix == r.URL.Path {
r.URL.Path = "/"
s.serveRequest(w, r, origPrefix)
return
}
prefix = strings.TrimSuffix(origPrefix, "/") + "/"
if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {
r.URL.Path = "/" + strings.TrimPrefix(p, "/")
s.serveRequest(w, r, origPrefix)
return
}
}
http.NotFound(w, r)
}
func (s *StripPrefix) serveRequest(w http.ResponseWriter, r *http.Request, prefix string) {
r.Header[forwardedPrefixHeader] = []string{prefix}
r.RequestURI = r.URL.RequestURI()
s.Handler.ServeHTTP(w, r)
}
// SetHandler sets handler
func (s *StripPrefix) SetHandler(Handler http.Handler) {
s.Handler = Handler

View file

@ -0,0 +1,103 @@
package middlewares
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestStripPrefix(t *testing.T) {
tests := []struct {
desc string
prefixes []string
path string
expectedStatusCode int
expectedPath string
}{
{
desc: "no prefixes configured",
prefixes: []string{},
path: "/noprefixes",
expectedStatusCode: http.StatusNotFound,
},
{
desc: "wildcard (.*) requests",
prefixes: []string{"/"},
path: "/",
expectedStatusCode: http.StatusOK,
expectedPath: "/",
},
{
desc: "prefix and path matching",
prefixes: []string{"/stat"},
path: "/stat",
expectedStatusCode: http.StatusOK,
expectedPath: "/",
},
{
desc: "path prefix on exactly matching path",
prefixes: []string{"/stat/"},
path: "/stat/",
expectedStatusCode: http.StatusOK,
expectedPath: "/",
},
{
desc: "path prefix on matching longer path",
prefixes: []string{"/stat/"},
path: "/stat/us",
expectedStatusCode: http.StatusOK,
expectedPath: "/us",
},
{
desc: "path prefix on mismatching path",
prefixes: []string{"/stat/"},
path: "/status",
expectedStatusCode: http.StatusNotFound,
},
{
desc: "general prefix on matching path",
prefixes: []string{"/stat"},
path: "/stat/",
expectedStatusCode: http.StatusOK,
expectedPath: "/",
},
{
desc: "earlier prefix matching",
prefixes: []string{"/stat", "/stat/us"},
path: "/stat/us",
expectedStatusCode: http.StatusOK,
expectedPath: "/us",
},
{
desc: "later prefix matching",
prefixes: []string{"/mismatch", "/stat"},
path: "/stat",
expectedStatusCode: http.StatusOK,
expectedPath: "/",
},
}
for _, test := range tests {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var gotPath string
server := httptest.NewServer(&StripPrefix{
Prefixes: test.prefixes,
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotPath = r.URL.Path
}),
})
defer server.Close()
resp, err := http.Get(server.URL + test.path)
require.NoError(t, err, "Failed to send GET request")
assert.Equal(t, test.expectedStatusCode, resp.StatusCode, "Unexpected status code")
assert.Equal(t, test.expectedPath, gotPath, "Unexpected path")
})
}
}

View file

@ -27,9 +27,6 @@ var _ provider.Provider = (*Provider)(nil)
const (
annotationFrontendRuleType = "traefik.frontend.rule.type"
ruleTypePathPrefixStrip = "PathPrefixStrip"
ruleTypePathStrip = "PathStrip"
ruleTypePath = "Path"
ruleTypePathPrefix = "PathPrefix"
annotationKubernetesWhitelistSourceRange = "ingress.kubernetes.io/whitelist-source-range"
@ -205,12 +202,8 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
}
if len(pa.Path) > 0 {
ruleType, unknown := getRuleTypeFromAnnotation(i.Annotations)
switch {
case unknown:
log.Warnf("Unknown RuleType '%s' for Ingress %s/%s, falling back to PathPrefix", ruleType, i.ObjectMeta.Namespace, i.ObjectMeta.Name)
fallthrough
case ruleType == "":
ruleType := i.Annotations[annotationFrontendRuleType]
if ruleType == "" {
ruleType = ruleTypePathPrefix
}
@ -265,17 +258,15 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
}
if !exists {
log.Errorf("Endpoints not found for %s/%s", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
continue
log.Warnf("Endpoints not found for %s/%s", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
break
}
if len(endpoints.Subsets) == 0 {
log.Warnf("Service endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(int(port.Port)),
Weight: 1,
log.Warnf("Endpoints not available for %s/%s", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
break
}
} else {
for _, subset := range endpoints.Subsets {
for _, address := range subset.Addresses {
url := protocol + "://" + address.IP + ":" + strconv.Itoa(endpointPortNumber(port, subset.Ports))
@ -290,7 +281,6 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
}
}
}
}
break
}
}
@ -398,24 +388,3 @@ func (p *Provider) loadConfig(templateObjects types.Configuration) *types.Config
}
return configuration
}
func getRuleTypeFromAnnotation(annotations map[string]string) (ruleType string, unknown bool) {
ruleType = annotations[annotationFrontendRuleType]
for _, knownRuleType := range []string{
ruleTypePathPrefixStrip,
ruleTypePathStrip,
ruleTypePath,
ruleTypePathPrefix,
} {
if strings.ToLower(ruleType) == strings.ToLower(knownRuleType) {
return knownRuleType, false
}
}
if ruleType != "" {
// Annotation is set but does not match anything we know.
unknown = true
}
return ruleType, unknown
}

View file

@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"reflect"
"strings"
"testing"
"github.com/containous/traefik/types"
@ -333,19 +332,24 @@ func TestRuleType(t *testing.T) {
frontendRuleType string
}{
{
desc: "implicit default",
desc: "rule type annotation missing",
ingressRuleType: "",
frontendRuleType: ruleTypePathPrefix,
},
{
desc: "unknown ingress / explicit default",
ingressRuleType: "unknown",
frontendRuleType: ruleTypePathPrefix,
desc: "Path rule type annotation set",
ingressRuleType: "Path",
frontendRuleType: "Path",
},
{
desc: "explicit ingress",
ingressRuleType: ruleTypePath,
frontendRuleType: ruleTypePath,
desc: "PathStrip rule type annotation set",
ingressRuleType: "PathStrip",
frontendRuleType: "PathStrip",
},
{
desc: "PathStripPrefix rule type annotation set",
ingressRuleType: "PathStripPrefix",
frontendRuleType: "PathStripPrefix",
},
}
@ -1832,65 +1836,6 @@ func TestInvalidPassHostHeaderValue(t *testing.T) {
}
}
func TestGetRuleTypeFromAnnotation(t *testing.T) {
tests := []struct {
in string
wantedUnknown bool
}{
{
in: ruleTypePathPrefixStrip,
wantedUnknown: false,
},
{
in: ruleTypePathStrip,
wantedUnknown: false,
},
{
in: ruleTypePath,
wantedUnknown: false,
},
{
in: ruleTypePathPrefix,
wantedUnknown: false,
},
{
wantedUnknown: false,
},
{
in: "Unknown",
wantedUnknown: true,
},
}
for _, test := range tests {
test := test
inputs := []string{test.in, strings.ToLower(test.in)}
if inputs[0] == inputs[1] {
// Lower-casing makes no difference -- truncating to single case.
inputs = inputs[:1]
}
for _, input := range inputs {
t.Run(fmt.Sprintf("in='%s'", input), func(t *testing.T) {
t.Parallel()
annotations := map[string]string{}
if test.in != "" {
annotations[annotationFrontendRuleType] = test.in
}
gotRuleType, gotUnknown := getRuleTypeFromAnnotation(annotations)
if gotUnknown != test.wantedUnknown {
t.Errorf("got unknown '%t', wanted '%t'", gotUnknown, test.wantedUnknown)
}
if gotRuleType != test.in {
t.Errorf("got rule type '%s', wanted '%s'", gotRuleType, test.in)
}
})
}
}
}
func TestKubeAPIErrors(t *testing.T) {
ingresses := []*v1beta1.Ingress{{
ObjectMeta: v1.ObjectMeta{
@ -2027,6 +1972,21 @@ func TestMissingResources(t *testing.T) {
},
},
},
{
Host: "missing_endpoint_subsets",
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Backend: v1beta1.IngressBackend{
ServiceName: "missing_endpoint_subsets_service",
ServicePort: intstr.FromInt(80),
},
},
},
},
},
},
},
},
}}
@ -2061,6 +2021,21 @@ func TestMissingResources(t *testing.T) {
},
},
},
{
ObjectMeta: v1.ObjectMeta{
Name: "missing_endpoint_subsets_service",
UID: "4",
Namespace: "testing",
},
Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.4",
Ports: []v1.ServicePort{
{
Port: 80,
},
},
},
},
}
endpoints := []*v1.Endpoints{
{
@ -2084,6 +2059,14 @@ func TestMissingResources(t *testing.T) {
},
},
},
{
ObjectMeta: v1.ObjectMeta{
Name: "missing_endpoint_subsets_service",
UID: "4",
Namespace: "testing",
},
Subsets: []v1.EndpointSubset{},
},
}
watchChan := make(chan interface{})
@ -2130,6 +2113,14 @@ func TestMissingResources(t *testing.T) {
Sticky: false,
},
},
"missing_endpoint_subsets": {
Servers: map[string]types.Server{},
CircuitBreaker: nil,
LoadBalancer: &types.LoadBalancer{
Method: "wrr",
Sticky: false,
},
},
},
Frontends: map[string]*types.Frontend{
"fully_working": {
@ -2150,6 +2141,15 @@ func TestMissingResources(t *testing.T) {
},
},
},
"missing_endpoint_subsets": {
Backend: "missing_endpoint_subsets",
PassHostHeader: true,
Routes: map[string]types.Route{
"missing_endpoint_subsets": {
Rule: "Host:missing_endpoint_subsets",
},
},
},
},
}

View file

@ -388,9 +388,13 @@ func parseRancherData(environments []*rancher.Environment, services []*rancher.S
Containers: []string{},
}
if service.LaunchConfig == nil || service.LaunchConfig.Labels == nil {
log.Warnf("Rancher Service Labels are missing. Environment: %s, service: %s", environment.Name, service.Name)
} else {
for key, value := range service.LaunchConfig.Labels {
rancherData.Labels[key] = value.(string)
}
}
for _, container := range containers {
if container.Labels["io.rancher.stack_service.name"] == rancherData.Name && containerFilter(container) {

View file

@ -1,4 +1,5 @@
[backends]{{range $backendName, $backend := .Backends}}
[backends."{{$backendName}}"]
{{if $backend.CircuitBreaker}}
[backends."{{$backendName}}".circuitbreaker]
expression = "{{$backend.CircuitBreaker.Expression}}"

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
}