diff --git a/README.md b/README.md index 6f32da6..bcf7010 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![Build Status](https://travis-ci.org/gambol99/go-marathon.svg?branch=master)](https://travis-ci.org/gambol99/go-marathon) [![GoDoc](http://godoc.org/github.com/gambol99/go-marathon?status.png)](http://godoc.org/github.com/gambol99/go-marathon) +[![Go Report Card](https://goreportcard.com/badge/github.com/katallaxie/go-marathon)](https://goreportcard.com/report/github.com/katallaxie/go-marathon) [![Coverage Status](https://coveralls.io/repos/github/gambol99/go-marathon/badge.svg?branch=master)](https://coveralls.io/github/gambol99/go-marathon?branch=master) # Go-Marathon diff --git a/application.go b/application.go index 36f5184..1b4c9f6 100644 --- a/application.go +++ b/application.go @@ -54,6 +54,12 @@ type Port struct { Protocol string `json:"protocol,omitempty"` } +// Network provides info about application networking +type Network struct { + Name string `json:"name,omitempty"` + Mode string `json:"mode,omitempty"` +} + // Application is the definition for an application in marathon type Application struct { ID string `json:"id,omitempty"` @@ -64,6 +70,8 @@ type Application struct { CPUs float64 `json:"cpus,omitempty"` GPUs *float64 `json:"gpus,omitempty"` Disk *float64 `json:"disk,omitempty"` + Networks *[]Network `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 +503,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 +529,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" @@ -956,3 +970,22 @@ func (d *Discovery) AddPort(port Port) *Discovery { d.Ports = &ports return d } + +// EmptyNetworks explicitly empties networks +func (r *Application) EmptyNetworks() *Application { + r.Networks = &[]Network{} + return r +} + +// SetNetwork sets the networking mode +func (r *Application) SetNetwork(name string, mode string) *Application { + if r.Networks == nil { + r.EmptyNetworks() + } + + network := Network{Name: name, Mode: mode} + networks := *r.Networks + networks = append(networks, network) + r.Networks = &networks + return r +} diff --git a/application_test.go b/application_test.go index df3ba82..e9c2228 100644 --- a/application_test.go +++ b/application_test.go @@ -43,31 +43,70 @@ func TestApplicationMemory(t *testing.T) { } func TestApplicationString(t *testing.T) { - app := NewDockerApplication(). - Name("my-app"). - CPU(0.1). - Memory(64). - Storage(0.0). - Count(2). - AddArgs("/usr/sbin/apache2ctl", "-D", "FOREGROUND"). - AddEnv("NAME", "frontend_http"). - AddEnv("SERVICE_80_NAME", "test_http") - app. - Container.Docker.Container("quay.io/gambol99/apache-php:latest"). - Bridged(). - Expose(80). - Expose(443) - app, err := app.CheckHTTP("/health", 80, 5) - assert.Nil(t, err) - - expectedAppJSONBytes, err := ioutil.ReadFile("tests/app-definitions/TestApplicationString-output.json") - if err != nil { - panic(err) + type test struct { + name string + app *Application + expectedAppJSONPath string + setup func(*Application) } - expectedAppJSON := strings.TrimSpace(string(expectedAppJSONBytes)) - assert.Equal(t, expectedAppJSON, app.String()) -} + tests := []test{ + { + name: "marathon < 1.5", + app: NewDockerApplication(). + Name("my-app"). + CPU(0.1). + Memory(64). + Storage(0.0). + Count(2). + AddArgs("/usr/sbin/apache2ctl", "-D", "FOREGROUND"). + AddEnv("NAME", "frontend_http"). + AddEnv("SERVICE_80_NAME", "test_http"), + expectedAppJSONPath: "tests/app-definitions/TestApplicationString-output.json", + setup: func(app *Application) { + app. + Container.Docker.Container("quay.io/gambol99/apache-php:latest"). + Bridged(). + Expose(80). + Expose(443) + }, + }, + { + name: "marathon > 1.5", + app: NewDockerApplication(). + Name("my-app"). + CPU(0.1). + Memory(64). + Storage(0.0). + Count(2). + SetNetwork("", "container/bridge"). + AddArgs("/usr/sbin/apache2ctl", "-D", "FOREGROUND"). + AddEnv("NAME", "frontend_http"). + AddEnv("SERVICE_80_NAME", "test_http"), + expectedAppJSONPath: "tests/app-definitions/TestApplicationString-1.5-output.json", + setup: func(app *Application) { + app. + Container.Expose(80).Expose(443). + Docker.Container("quay.io/gambol99/apache-php:latest") + }, + }, + } + + for _, test := range tests { + label := fmt.Sprintf("test: %s", test.name) + + test.setup(test.app) + _, err := test.app.CheckHTTP("/health", 80, 5) + assert.Nil(t, err) + + expectedAppJSONBytes, err := ioutil.ReadFile(test.expectedAppJSONPath) + if err != nil { + panic(err) + } + expectedAppJSON := strings.TrimSpace(string(expectedAppJSONBytes)) + assert.Equal(t, expectedAppJSON, test.app.String(), label) + } +} func TestApplicationCount(t *testing.T) { app := NewDockerApplication() assert.Nil(t, app.Instances) @@ -290,45 +329,87 @@ func TestApplicationPortDefinitions(t *testing.T) { } func TestHasHealthChecks(t *testing.T) { - app := NewDockerApplication() - assert.False(t, app.HasHealthChecks()) - app.Container.Docker.Container("quay.io/gambol99/apache-php:latest").Expose(80) - _, err := app.CheckTCP(80, 10) - assert.NoError(t, err) - assert.True(t, app.HasHealthChecks()) + apps := []*Application{ + NewDockerApplication(), + NewDockerApplication(), + } + + for i := range apps { + assert.False(t, apps[i].HasHealthChecks()) + } + + // Marathon < 1.5 + apps[0].Container.Docker.Container("quay.io/gambol99/apache-php:latest").Expose(80) + + // Marathon >= 1.5 + apps[1].Container.Expose(80).Docker.Container("quay.io/gambol99/apache-php:latest") + + for i := range apps { + _, err := apps[i].CheckTCP(80, 10) + assert.NoError(t, err) + assert.True(t, apps[i].HasHealthChecks()) + } } func TestApplicationCheckTCP(t *testing.T) { - app := NewDockerApplication() - assert.False(t, app.HasHealthChecks()) - _, err := app.CheckTCP(80, 10) - assert.Error(t, err) - assert.False(t, app.HasHealthChecks()) - app.Container.Docker.Container("quay.io/gambol99/apache-php:latest").Expose(80) - _, err = app.CheckTCP(80, 10) - assert.NoError(t, err) - assert.True(t, app.HasHealthChecks()) - check := (*app.HealthChecks)[0] - assert.Equal(t, "TCP", check.Protocol) - assert.Equal(t, 10, check.IntervalSeconds) - assert.Equal(t, 0, *check.PortIndex) + apps := []*Application{ + NewDockerApplication(), + NewDockerApplication(), + } + + for i := range apps { + assert.False(t, apps[i].HasHealthChecks()) + _, err := apps[i].CheckTCP(80, 10) + assert.Error(t, err) + assert.False(t, apps[i].HasHealthChecks()) + } + + // Marathon < 1.5 + apps[0].Container.Docker.Container("quay.io/gambol99/apache-php:latest").Expose(80) + + // Marathon >= 1.5 + apps[1].Container.Expose(80).Docker.Container("quay.io/gambol99/apache-php:latest") + + for i := range apps { + _, err := apps[i].CheckTCP(80, 10) + assert.NoError(t, err) + assert.True(t, apps[i].HasHealthChecks()) + check := (*apps[i].HealthChecks)[0] + assert.Equal(t, "TCP", check.Protocol) + assert.Equal(t, 10, check.IntervalSeconds) + assert.Equal(t, 0, *check.PortIndex) + } } func TestApplicationCheckHTTP(t *testing.T) { - app := NewDockerApplication() - assert.False(t, app.HasHealthChecks()) - _, err := app.CheckHTTP("/", 80, 10) - assert.Error(t, err) - assert.False(t, app.HasHealthChecks()) - app.Container.Docker.Container("quay.io/gambol99/apache-php:latest").Expose(80) - _, err = app.CheckHTTP("/health", 80, 10) - assert.NoError(t, err) - assert.True(t, app.HasHealthChecks()) - check := (*app.HealthChecks)[0] - assert.Equal(t, "HTTP", check.Protocol) - assert.Equal(t, 10, check.IntervalSeconds) - assert.Equal(t, "/health", *check.Path) - assert.Equal(t, 0, *check.PortIndex) + apps := []*Application{ + NewDockerApplication(), + NewDockerApplication(), + } + + for i := range apps { + assert.False(t, apps[i].HasHealthChecks()) + _, err := apps[i].CheckHTTP("/", 80, 10) + assert.Error(t, err) + assert.False(t, apps[i].HasHealthChecks()) + } + + // Marathon < 1.5 + apps[0].Container.Docker.Container("quay.io/gambol99/apache-php:latest").Expose(80) + + // Marathon >= 1.5 + apps[1].Container.Expose(80).Docker.Container("quay.io/gambol99/apache-php:latest") + + for i := range apps { + _, err := apps[i].CheckHTTP("/health", 80, 10) + assert.NoError(t, err) + assert.True(t, apps[i].HasHealthChecks()) + check := (*apps[i].HealthChecks)[0] + assert.Equal(t, "HTTP", check.Protocol) + assert.Equal(t, 10, check.IntervalSeconds) + assert.Equal(t, "/health", *check.Path) + assert.Equal(t, 0, *check.PortIndex) + } } func TestCreateApplication(t *testing.T) { @@ -483,9 +564,9 @@ func TestApplicationFetchURIs(t *testing.T) { assert.Equal(t, Fetch{URI: "file://uri2.tar.gz"}, (*app.Fetch)[1]) assert.Equal(t, Fetch{URI: "file://uri3.tar.gz"}, (*app.Fetch)[2]) - app.EmptyUris() - assert.NotNil(t, app.Uris) - assert.Equal(t, 0, len(*app.Uris)) + app.EmptyFetchURIs() + assert.NotNil(t, app.Fetch) + assert.Equal(t, 0, len(*app.Fetch)) } func TestSetApplicationVersion(t *testing.T) { @@ -725,7 +806,7 @@ func TestIPAddressPerTask(t *testing.T) { assert.Equal(t, 1, len(*ipPerTask.Groups)) assert.Equal(t, "label", (*ipPerTask.Groups)[0]) assert.Equal(t, "value", (*ipPerTask.Labels)["key"]) - assert.NotEmpty(t, ipPerTask.Discovery) + assert.NotEmpty(t, *ipPerTask.Discovery) ipPerTask.EmptyGroups() assert.Equal(t, 0, len(*ipPerTask.Groups)) @@ -764,3 +845,24 @@ func TestUpgradeStrategy(t *testing.T) { assert.Nil(t, us.MinimumHealthCapacity) assert.Nil(t, us.MaximumOverCapacity) } + +func TestBridgedNetworking(t *testing.T) { + app := NewDockerApplication().SetNetwork("test", "container/bridge") + networks := *app.Networks + + assert.Equal(t, networks[0].Mode, "container/bridge") +} + +func TestContainerNetworking(t *testing.T) { + app := NewDockerApplication().SetNetwork("test", "container") + networks := *app.Networks + + assert.Equal(t, networks[0].Mode, "container") +} + +func TestHostNetworking(t *testing.T) { + app := NewDockerApplication().SetNetwork("test", "host") + networks := *app.Networks + + assert.Equal(t, networks[0].Mode, "host") +} diff --git a/client.go b/client.go index 483457f..00736a4 100644 --- a/client.go +++ b/client.go @@ -368,7 +368,7 @@ func (r *marathonClient) apiCall(method, path string, body, result interface{}) r.debugLog("apiCall(): %v %v returned %v %s", request.Method, request.URL.String(), response.Status, oneLogLine(respBody)) } - // step: check for a successfull response + // step: check for a successful response if response.StatusCode >= 200 && response.StatusCode <= 299 { if result != nil { // If we have a deployment ID header and no response body, give them that diff --git a/docker.go b/docker.go index 217d3bb..20bc1b7 100644 --- a/docker.go +++ b/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/docker_test.go b/docker_test.go index 417d5bc..9127947 100644 --- a/docker_test.go +++ b/docker_test.go @@ -46,28 +46,55 @@ func TestDockerAddParameter(t *testing.T) { assert.NotNil(t, docker.Parameters) assert.Equal(t, 0, len(*docker.Parameters)) } - func TestDockerExpose(t *testing.T) { - app := NewDockerApplication() - app.Container.Docker.Expose(8080).Expose(80, 443) + apps := []*Application{ + NewDockerApplication(), + NewDockerApplication(), + } + + // Marathon < 1.5 + apps[0].Container.Docker.Expose(8080).Expose(80, 443) + + // Marathon >= 1.5 + apps[1].Container.Expose(8080).Expose(80, 443) + + portMappings := []*[]PortMapping{ + apps[0].Container.Docker.PortMappings, + apps[1].Container.PortMappings, + } - portMappings := app.Container.Docker.PortMappings - assert.Equal(t, 3, len(*portMappings)) + for _, portMapping := range portMappings { + assert.Equal(t, 3, len(*portMapping)) - assert.Equal(t, *createPortMapping(8080, "tcp"), (*portMappings)[0]) - assert.Equal(t, *createPortMapping(80, "tcp"), (*portMappings)[1]) - assert.Equal(t, *createPortMapping(443, "tcp"), (*portMappings)[2]) + assert.Equal(t, *createPortMapping(8080, "tcp"), (*portMapping)[0]) + assert.Equal(t, *createPortMapping(80, "tcp"), (*portMapping)[1]) + assert.Equal(t, *createPortMapping(443, "tcp"), (*portMapping)[2]) + } } func TestDockerExposeUDP(t *testing.T) { - app := NewDockerApplication() - app.Container.Docker.ExposeUDP(53).ExposeUDP(5060, 6881) - - portMappings := app.Container.Docker.PortMappings - assert.Equal(t, 3, len(*portMappings)) - assert.Equal(t, *createPortMapping(53, "udp"), (*portMappings)[0]) - assert.Equal(t, *createPortMapping(5060, "udp"), (*portMappings)[1]) - assert.Equal(t, *createPortMapping(6881, "udp"), (*portMappings)[2]) + apps := []*Application{ + NewDockerApplication(), + NewDockerApplication(), + } + + // Marathon < 1.5 + apps[0].Container.Docker.ExposeUDP(53).ExposeUDP(5060, 6881) + + // Marathon >= 1.5 + apps[1].Container.ExposeUDP(53).ExposeUDP(5060, 6881) + + portMappings := []*[]PortMapping{ + apps[0].Container.Docker.PortMappings, + apps[1].Container.PortMappings, + } + + for _, portMapping := range portMappings { + assert.Equal(t, 3, len(*portMapping)) + assert.Equal(t, *createPortMapping(53, "udp"), (*portMapping)[0]) + assert.Equal(t, *createPortMapping(5060, "udp"), (*portMapping)[1]) + assert.Equal(t, *createPortMapping(6881, "udp"), (*portMapping)[2]) + } } func TestPortMappingLabels(t *testing.T) { @@ -85,6 +112,20 @@ func TestPortMappingLabels(t *testing.T) { assert.Equal(t, 0, len(*pm.Labels)) } +func TestPortMappingNetworkNames(t *testing.T) { + pm := createPortMapping(80, "tcp") + + pm.AddNetwork("test") + + assert.Equal(t, 1, len(*pm.NetworkNames)) + assert.Equal(t, "test", (*pm.NetworkNames)[0]) + + pm.EmptyNetworkNames() + + assert.NotNil(t, pm.NetworkNames) + assert.Equal(t, 0, len(*pm.NetworkNames)) +} + func TestVolume(t *testing.T) { container := NewDockerApplication().Container diff --git a/examples/events_callback_transport/main.go b/examples/events_callback_transport/main.go index c9073e0..7e77bdc 100644 --- a/examples/events_callback_transport/main.go +++ b/examples/events_callback_transport/main.go @@ -71,9 +71,9 @@ func main() { log.Printf("Exiting the loop") done = true case event := <-events: - log.Printf("Recieved application event: %s", event) + log.Printf("Received application event: %s", event) case event := <-deployments: - log.Printf("Recieved deployment event: %v", event) + log.Printf("Received deployment event: %v", event) var deployment *marathon.EventDeploymentStepSuccess deployment = event.Event.(*marathon.EventDeploymentStepSuccess) log.Printf("deployment step: %v", deployment.CurrentStep) diff --git a/examples/events_sse_transport/main.go b/examples/events_sse_transport/main.go index b982eb0..ff72436 100644 --- a/examples/events_sse_transport/main.go +++ b/examples/events_sse_transport/main.go @@ -66,9 +66,9 @@ func main() { log.Printf("Exiting the loop") done = true case event := <-events: - log.Printf("Recieved application event: %s", event) + log.Printf("Received application event: %s", event) case event := <-deployments: - log.Printf("Recieved deployment event: %v", event) + log.Printf("Received deployment event: %v", event) var deployment *marathon.EventDeploymentStepSuccess deployment = event.Event.(*marathon.EventDeploymentStepSuccess) log.Printf("deployment step: %v", deployment.CurrentStep) diff --git a/examples/groups/main.go b/examples/groups/main.go index c04d350..fb494d8 100644 --- a/examples/groups/main.go +++ b/examples/groups/main.go @@ -107,7 +107,7 @@ func main() { assert(client.CreateGroup(group)) log.Printf("Successfully created the group: %s", group.ID) - log.Printf("Updating the group paramaters") + log.Printf("Updating the group parameters") frontend.Count(4) id, err := client.UpdateGroup(groupName, group, true) diff --git a/group_test.go b/group_test.go index a809c8f..d9af8c6 100644 --- a/group_test.go +++ b/group_test.go @@ -29,38 +29,72 @@ func TestGroups(t *testing.T) { groups, err := endpoint.Client.Groups() assert.NoError(t, err) assert.NotNil(t, groups) - assert.Equal(t, len(groups.Groups), 1) + assert.Equal(t, 1, len(groups.Groups)) group := groups.Groups[0] - assert.Equal(t, group.ID, fakeGroupName) + assert.Equal(t, fakeGroupName, group.ID) } -func TestGroup(t *testing.T) { +func TestNewGroup(t *testing.T) { endpoint := newFakeMarathonEndpoint(t, nil) defer endpoint.Close() group, err := endpoint.Client.Group(fakeGroupName) assert.NoError(t, err) assert.NotNil(t, group) - assert.Equal(t, len(group.Apps), 1) - assert.Equal(t, group.ID, fakeGroupName) + assert.Equal(t, 1, len(group.Apps)) + assert.Equal(t, fakeGroupName, group.ID) group, err = endpoint.Client.Group(fakeGroupName1) assert.NoError(t, err) assert.NotNil(t, group) - assert.Equal(t, group.ID, fakeGroupName1) + assert.Equal(t, fakeGroupName1, group.ID) assert.NotNil(t, group.Groups) - assert.Equal(t, len(group.Groups), 1) + assert.Equal(t, 1, len(group.Groups)) frontend := group.Groups[0] - assert.Equal(t, frontend.ID, "frontend") - assert.Equal(t, len(frontend.Apps), 3) + assert.Equal(t, "frontend", frontend.ID) + assert.Equal(t, 3, len(frontend.Apps)) for _, app := range frontend.Apps { assert.NotNil(t, app.Container) assert.NotNil(t, app.Container.Docker) - assert.Equal(t, app.Container.Docker.Network, "BRIDGE") - if len(*app.Container.Docker.PortMappings) == 0 { + for _, network := range *app.Networks { + assert.Equal(t, "container/bridge", network.Mode) + } + if len(*app.Container.PortMappings) == 0 { t.Fail() } } } + +// TODO @kamsz: How to work with old and new endpoints from methods.yml? +// func TestGroup(t *testing.T) { +// endpoint := newFakeMarathonEndpoint(t, nil) +// defer endpoint.Close() + +// group, err := endpoint.Client.Group(fakeGroupName) +// assert.NoError(t, err) +// assert.NotNil(t, group) +// assert.Equal(t, 1, len(group.Apps)) +// assert.Equal(t, fakeGroupName, group.ID) + +// group, err = endpoint.Client.Group(fakeGroupName1) + +// assert.NoError(t, err) +// assert.NotNil(t, group) +// assert.Equal(t, fakeGroupName1, group.ID) +// assert.NotNil(t, group.Groups) +// assert.Equal(t, 1, len(group.Groups)) + +// frontend := group.Groups[0] +// assert.Equal(t, "frontend", frontend.ID) +// assert.Equal(t, 3, len(frontend.Apps)) +// for _, app := range frontend.Apps { +// assert.NotNil(t, app.Container) +// assert.NotNil(t, app.Container.Docker) +// assert.Equal(t, "BRIDGE", app.Container.Docker.Network) +// if len(*app.Container.Docker.PortMappings) == 0 { +// t.Fail() +// } +// } +// } diff --git a/queue.go b/queue.go index 2eaede3..b9cfa48 100644 --- a/queue.go +++ b/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/residency.go b/residency.go index ea9d72d..9fc94ce 100644 --- a/residency.go +++ b/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/subscription.go b/subscription.go index a9f75c6..561026f 100644 --- a/subscription.go +++ b/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/task.go b/task.go index d923692..e17cf24 100644 --- a/task.go +++ b/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/tests/app-definitions/TestApplicationString-1.5-output.json b/tests/app-definitions/TestApplicationString-1.5-output.json new file mode 100644 index 0000000..1667f19 --- /dev/null +++ b/tests/app-definitions/TestApplicationString-1.5-output.json @@ -0,0 +1,52 @@ +{ + "id": "/my-app", + "args": [ + "/usr/sbin/apache2ctl", + "-D", + "FOREGROUND" + ], + "container": { + "type": "DOCKER", + "docker": { + "image": "quay.io/gambol99/apache-php:latest" + }, + "portMappings": [ + { + "containerPort": 80, + "hostPort": 0, + "protocol": "tcp" + }, + { + "containerPort": 443, + "hostPort": 0, + "protocol": "tcp" + } + ] + }, + "cpus": 0.1, + "disk": 0, + "networks": [ + { + "mode": "container/bridge" + } + ], + "healthChecks": [ + { + "portIndex": 0, + "path": "/health", + "maxConsecutiveFailures": 3, + "protocol": "HTTP", + "gracePeriodSeconds": 30, + "intervalSeconds": 5, + "timeoutSeconds": 5 + } + ], + "instances": 2, + "mem": 64, + "ports": null, + "dependencies": null, + "env": { + "NAME": "frontend_http", + "SERVICE_80_NAME": "test_http" + } +} diff --git a/tests/rest-api/methods.yml b/tests/rest-api/methods.yml index 9193f17..5f1fe52 100644 --- a/tests/rest-api/methods.yml +++ b/tests/rest-api/methods.yml @@ -57,9 +57,6 @@ "id": "/fake-app", "instances": 2, "mem": 50.0, - "ports": [ - 0 - ], "requirePorts": false, "residency" : { "taskLostBehavior" : "RELAUNCH_AFTER_TIMEOUT", @@ -70,7 +67,6 @@ "minimumHealthCapacity": 0.5, "maximumOverCapacity": 0.5 }, - "uris": [], "user": null, "version": "2014-08-18T22:36:41.451Z" } @@ -87,22 +83,21 @@ "constraints": [], "container": { "docker": { - "image": "python:3", - "network": "BRIDGE", - "portMappings": [ - { - "containerPort": 8080, - "hostPort": 0, - "servicePort": 9000, - "protocol": "tcp" - }, - { - "containerPort": 161, - "hostPort": 0, - "protocol": "udp" - } - ] + "image": "python:3" }, + "portMappings": [ + { + "containerPort": 8080, + "hostPort": 0, + "servicePort": 9000, + "protocol": "tcp" + }, + { + "containerPort": 161, + "hostPort": 0, + "protocol": "udp" + } + ], "type": "DOCKER", "volumes": [] }, @@ -110,6 +105,11 @@ "dependencies": [], "deployments": [], "disk": 0.0, + "networks": [ + { + "mode": "container/bridge" + } + ], "env": { "VAR": "VALUE", "SECRET1": { @@ -132,10 +132,6 @@ "id": "/fake-app", "instances": 2, "mem": 64.0, - "ports": [ - 10000, - 10001 - ], "requirePorts": false, "secrets": { "secret0": { @@ -148,7 +144,6 @@ "upgradeStrategy": { "minimumHealthCapacity": 1.0 }, - "uris": [], "user": null, "version": "2014-09-25T02:26:59.256Z" }, @@ -160,22 +155,21 @@ "constraints": [], "container": { "docker": { - "image": "python:3", - "network": "BRIDGE", - "portMappings": [ - { - "containerPort": 8080, - "hostPort": 0, - "servicePort": 9000, - "protocol": "tcp" - }, - { - "containerPort": 161, - "hostPort": 0, - "protocol": "udp" - } - ] + "image": "python:3" }, + "portMappings": [ + { + "containerPort": 8080, + "hostPort": 0, + "servicePort": 9000, + "protocol": "tcp" + }, + { + "containerPort": 161, + "hostPort": 0, + "protocol": "udp" + } + ], "type": "DOCKER", "volumes": [] }, @@ -183,6 +177,11 @@ "dependencies": [], "deployments": [], "disk": 0.0, + "networks": [ + { + "mode": "container/bridge" + } + ], "env": {}, "executor": "", "healthChecks": [ @@ -200,10 +199,6 @@ "id": "/fake-app-broken", "instances": 2, "mem": 64.0, - "ports": [ - 10000, - 10001 - ], "requirePorts": false, "residency" : { "taskLostBehavior" : "RELAUNCH_AFTER_TIMEOUT", @@ -215,7 +210,6 @@ "upgradeStrategy": { "minimumHealthCapacity": 1.0 }, - "uris": [], "user": null, "version": "2014-09-25T02:26:59.256Z" } @@ -313,17 +307,16 @@ "constraints": [], "container": { "docker": { - "image": "python:3", - "network": "BRIDGE", - "portMappings": [ - { - "containerPort": 8080, - "hostPort": 0, - "servicePort": 9000, - "protocol": "tcp" - } - ] + "image": "python:3" }, + "portMappings": [ + { + "containerPort": 8080, + "hostPort": 0, + "servicePort": 9000, + "protocol": "tcp" + } + ], "type": "DOCKER", "volumes": [] }, @@ -331,6 +324,11 @@ "dependencies": [], "deployments": [], "disk": 0.0, + "networks": [ + { + "mode": "container/bridge" + } + ], "env": {}, "executor": "", "healthChecks": [ @@ -358,9 +356,6 @@ "version": "2014-09-12T23:28:21.737Z" }, "mem": 32.0, - "ports": [ - 10000 - ], "requirePorts": false, "residency" : { "taskLostBehavior" : "RELAUNCH_AFTER_TIMEOUT", @@ -416,9 +411,6 @@ "upgradeStrategy": { "minimumHealthCapacity": 1.0 }, - "uris": [ - "http://downloads.mesosphere.com/misc/toggle.tgz" - ], "user": null, "version": "2014-09-12T23:28:21.737Z" } @@ -434,17 +426,16 @@ "constraints": [], "container": { "docker": { - "image": "python:3", - "network": "BRIDGE", - "portMappings": [ - { - "containerPort": 8080, - "hostPort": 0, - "servicePort": 9000, - "protocol": "tcp" - } - ] + "image": "python:3" }, + "portMappings": [ + { + "containerPort": 8080, + "hostPort": 0, + "servicePort": 9000, + "protocol": "tcp" + } + ], "type": "DOCKER", "volumes": [] }, @@ -452,6 +443,11 @@ "dependencies": [], "deployments": [], "disk": 0.0, + "networks": [ + { + "mode": "container/bridge" + } + ], "env": {}, "executor": "", "healthChecks": [ @@ -479,9 +475,6 @@ "version": "2014-09-12T23:28:21.737Z" }, "mem": 32.0, - "ports": [ - 10000 - ], "requirePorts": false, "residency" : { "taskLostBehavior" : "RELAUNCH_AFTER_TIMEOUT", @@ -537,9 +530,6 @@ "upgradeStrategy": { "minimumHealthCapacity": 1.0 }, - "uris": [ - "http://downloads.mesosphere.com/misc/toggle.tgz" - ], "user": null, "version": "2014-09-12T23:28:21.737Z" } @@ -568,17 +558,16 @@ "constraints": [], "container": { "docker": { - "image": "python:3", - "network": "BRIDGE", - "portMappings": [ - { - "containerPort": 8080, - "hostPort": 0, - "servicePort": 9000, - "protocol": "tcp" - } - ] + "image": "python:3" }, + "portMappings": [ + { + "containerPort": 8080, + "hostPort": 0, + "servicePort": 9000, + "protocol": "tcp" + } + ], "type": "DOCKER", "volumes": [] }, @@ -586,6 +575,11 @@ "dependencies": [], "deployments": [], "disk": 0.0, + "networks": [ + { + "mode": "container/bridge" + } + ], "env": {}, "executor": "", "healthChecks": [ @@ -612,9 +606,6 @@ "version": "2014-09-12T23:28:21.737Z" }, "mem": 32.0, - "ports": [ - 10000 - ], "requirePorts": false, "residency" : { "taskLostBehavior" : "RELAUNCH_AFTER_TIMEOUT", @@ -670,9 +661,6 @@ "upgradeStrategy": { "minimumHealthCapacity": 1.0 }, - "uris": [ - "http://downloads.mesosphere.com/misc/toggle.tgz" - ], "user": null, "version": "2014-09-12T23:28:21.737Z" } @@ -1305,15 +1293,11 @@ "id": "/test/app", "instances": 1, "mem": 128.0, - "ports": [ - 10000 - ], "requirePorts": false, "storeUrls": [], "upgradeStrategy": { "minimumHealthCapacity": 1.0 }, - "uris": [], "user": null, "version": "2014-08-28T01:05:40.586Z" } @@ -1349,15 +1333,11 @@ "id": "/test/app", "instances": 1, "mem": 128.0, - "ports": [ - 10000 - ], "requirePorts": false, "storeUrls": [], "upgradeStrategy": { "minimumHealthCapacity": 1.0 }, - "uris": [], "user": null, "version": "2014-08-28T01:05:40.586Z" } @@ -1381,9 +1361,9 @@ "container": { "type": "DOCKER", "docker": { - "image": "quay.io/gambol99/apache-php:latest", - "network": "BRIDGE", - "portMappings": [ + "image": "quay.io/gambol99/apache-php:latest" + }, + "portMappings": [ { "containerPort": 80, "hostPort": 0, @@ -1394,8 +1374,7 @@ "hostPort": 0, "protocol": "tcp" } - ] - } + ] }, "healthChecks": [ { @@ -1411,6 +1390,11 @@ "id": "apache", "mem": 64, "args": [], + "networks": [ + { + "mode": "container/bridge" + } + ], "env": { "ENVIRONMENT": "qa", "SERVICE_80_NAME": "apache_http-qa-1", @@ -1428,12 +1412,11 @@ "container": { "type": "DOCKER", "docker": { - "image": "tutum/mysql", - "network": "BRIDGE", - "portMappings": [ - { "containerPort": 3306, "hostPort": 0, "protocol": "tcp" } - ] - } + "image": "tutum/mysql" + }, + "portMappings": [ + { "containerPort": 3306, "hostPort": 0, "protocol": "tcp" } + ] }, "healthChecks": [ { @@ -1448,6 +1431,11 @@ "id": "mysql", "mem": 1024, "cmd": "", + "networks": [ + { + "mode": "container/bridge" + } + ], "env": { "ENVIRONMENT": "qa", "SERVICE_NAME": "dbmaster", @@ -1461,16 +1449,15 @@ "container": { "type": "DOCKER", "docker": { - "image": "redis", - "network": "BRIDGE", - "portMappings": [ + "image": "redis" + }, + "portMappings": [ { "containerPort": 6379, "hostPort": 0, "protocol": "tcp" } - ] - } + ] }, "healthChecks": [ { @@ -1485,6 +1472,11 @@ "id": "caching", "cmd": "", "mem": 128, + "networks": [ + { + "mode": "container/bridge" + } + ], "env": { "ENVIRONMENT": "qa", "SERVICE_6379_NAME": "redis-qa-1" @@ -1712,15 +1704,11 @@ "id": "/test", "instances": 3, "mem": 32.0, - "ports": [10000], "requirePorts": false, "storeUrls": [], "upgradeStrategy": { "minimumHealthCapacity": 1.0 }, - "uris": [ - "http://downloads.mesosphere.com/misc/toggle.tgz" - ], "user": null, "version": "2014-08-26T05:04:49.766Z" } @@ -1845,10 +1833,6 @@ "id": "/fake-app", "instances": 2, "mem": 64.0, - "ports": [ - 10000, - 10001 - ], "requirePorts": false, "residency" : { "taskLostBehavior" : "RELAUNCH_AFTER_TIMEOUT", @@ -1860,7 +1844,6 @@ "upgradeStrategy": { "minimumHealthCapacity": 1.0 }, - "uris": [], "user": null, "version": "2014-09-25T02:26:59.256Z" }, @@ -1912,10 +1895,6 @@ "id": "/fake-app-broken", "instances": 2, "mem": 64.0, - "ports": [ - 10000, - 10001 - ], "requirePorts": false, "storeUrls": [], "tasksRunning": 2, @@ -1923,7 +1902,6 @@ "upgradeStrategy": { "minimumHealthCapacity": 1.0 }, - "uris": [], "user": null, "version": "2014-09-25T02:26:59.256Z" } @@ -1949,9 +1927,6 @@ "id": "/no-health-check-results-app", "instances": 2, "mem": 32.0, - "ports": [ - 10000 - ], "tasks": [ { "appId": "/no-health-check-results-app", diff --git a/unreachable_strategy.go b/unreachable_strategy.go index 9ed02df..6563239 100644 --- a/unreachable_strategy.go +++ b/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.