Add support for multiple ports in Marathon (#2506)

- create a target for every port
- add meta labels for Marathon labels in portMappings and portDefinitions
This commit is contained in:
Robson Roberto Souza Peixoto 2017-03-18 17:10:44 -03:00 committed by Tobias Schmidt
parent 0a7c8e9da1
commit cc3e859d9e
2 changed files with 285 additions and 15 deletions

View file

@ -30,6 +30,7 @@ import (
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/util/httputil" "github.com/prometheus/prometheus/util/httputil"
"github.com/prometheus/prometheus/util/strutil"
) )
const ( const (
@ -45,6 +46,11 @@ const (
// taskLabel contains the mesos task name of the app instance. // taskLabel contains the mesos task name of the app instance.
taskLabel model.LabelName = metaLabelPrefix + "task" taskLabel model.LabelName = metaLabelPrefix + "task"
// portMappingLabelPrefix is the prefix for the application portMappings labels.
portMappingLabelPrefix = metaLabelPrefix + "port_mapping_label_"
// portDefinitionLabelPrefix is the prefix for the application portDefinitions labels.
portDefinitionLabelPrefix = metaLabelPrefix + "port_definition_label_"
// Constants for instrumentation. // Constants for instrumentation.
namespace = "prometheus" namespace = "prometheus"
) )
@ -188,9 +194,15 @@ type Task struct {
Ports []uint32 `json:"ports"` Ports []uint32 `json:"ports"`
} }
// PortMappings describes in which port the process are binding inside the docker container.
type PortMappings struct {
Labels map[string]string `json:"labels"`
}
// DockerContainer describes a container which uses the docker runtime. // DockerContainer describes a container which uses the docker runtime.
type DockerContainer struct { type DockerContainer struct {
Image string `json:"image"` Image string `json:"image"`
PortMappings []PortMappings `json:"portMappings"`
} }
// Container describes the runtime an app in running in. // Container describes the runtime an app in running in.
@ -198,13 +210,19 @@ type Container struct {
Docker DockerContainer `json:"docker"` Docker DockerContainer `json:"docker"`
} }
// PortDefinitions describes which load balancer port you should access to access the service.
type PortDefinitions struct {
Labels map[string]string `json:"labels"`
}
// App describes a service running on Marathon. // App describes a service running on Marathon.
type App struct { type App struct {
ID string `json:"id"` ID string `json:"id"`
Tasks []Task `json:"tasks"` Tasks []Task `json:"tasks"`
RunningTasks int `json:"tasksRunning"` RunningTasks int `json:"tasksRunning"`
Labels map[string]string `json:"labels"` Labels map[string]string `json:"labels"`
Container Container `json:"container"` Container Container `json:"container"`
PortDefinitions []PortDefinitions `json:"portDefinitions"`
} }
// AppList is a list of Marathon apps. // AppList is a list of Marathon apps.
@ -285,7 +303,7 @@ func createTargetGroup(app *App) *config.TargetGroup {
} }
for ln, lv := range app.Labels { for ln, lv := range app.Labels {
ln = appLabelPrefix + ln ln = appLabelPrefix + strutil.SanitizeLabelName(ln)
tg.Labels[model.LabelName(ln)] = model.LabelValue(lv) tg.Labels[model.LabelName(ln)] = model.LabelValue(lv)
} }
@ -298,15 +316,30 @@ func targetsForApp(app *App) []model.LabelSet {
if len(t.Ports) == 0 { if len(t.Ports) == 0 {
continue continue
} }
target := targetForTask(&t) for i := 0; i < len(t.Ports); i++ {
targets = append(targets, model.LabelSet{ targetAddress := targetForTask(&t, i)
model.AddressLabel: model.LabelValue(target), target := model.LabelSet{
taskLabel: model.LabelValue(t.ID), model.AddressLabel: model.LabelValue(targetAddress),
}) taskLabel: model.LabelValue(t.ID),
}
if i < len(app.PortDefinitions) {
for ln, lv := range app.PortDefinitions[i].Labels {
ln = portDefinitionLabelPrefix + strutil.SanitizeLabelName(ln)
target[model.LabelName(ln)] = model.LabelValue(lv)
}
}
if i < len(app.Container.Docker.PortMappings) {
for ln, lv := range app.Container.Docker.PortMappings[i].Labels {
ln = portMappingLabelPrefix + strutil.SanitizeLabelName(ln)
target[model.LabelName(ln)] = model.LabelValue(lv)
}
}
targets = append(targets, target)
}
} }
return targets return targets
} }
func targetForTask(task *Task) string { func targetForTask(task *Task, index int) string {
return net.JoinHostPort(task.Host, fmt.Sprintf("%d", task.Ports[0])) return net.JoinHostPort(task.Host, fmt.Sprintf("%d", task.Ports[index]))
} }

View file

@ -80,7 +80,12 @@ func marathonTestAppList(labels map[string]string, runningTasks int) *AppList {
Host: "mesos-slave1", Host: "mesos-slave1",
Ports: []uint32{31000}, Ports: []uint32{31000},
} }
docker = DockerContainer{Image: "repo/image:tag"} docker = DockerContainer{
Image: "repo/image:tag",
PortMappings: []PortMappings{
{Labels: labels},
},
}
container = Container{Docker: docker} container = Container{Docker: docker}
app = App{ app = App{
ID: "test-service", ID: "test-service",
@ -88,6 +93,9 @@ func marathonTestAppList(labels map[string]string, runningTasks int) *AppList {
RunningTasks: runningTasks, RunningTasks: runningTasks,
Labels: labels, Labels: labels,
Container: container, Container: container,
PortDefinitions: []PortDefinitions{
{Labels: make(map[string]string)},
},
} }
) )
return &AppList{ return &AppList{
@ -119,6 +127,12 @@ func TestMarathonSDSendGroup(t *testing.T) {
if tgt[model.AddressLabel] != "mesos-slave1:31000" { if tgt[model.AddressLabel] != "mesos-slave1:31000" {
t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel]) t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel])
} }
if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "yes" {
t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel])
}
if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" {
t.Fatalf("Wrong first portDefinitions label from the first port: %s", tgt[model.AddressLabel])
}
default: default:
t.Fatal("Did not get a target group.") t.Fatal("Did not get a target group.")
} }
@ -189,6 +203,83 @@ func TestMarathonSDRunAndStop(t *testing.T) {
} }
} }
func marathonTestAppListWithMutiplePorts(labels map[string]string, runningTasks int) *AppList {
var (
task = Task{
ID: "test-task-1",
Host: "mesos-slave1",
Ports: []uint32{31000, 32000},
}
docker = DockerContainer{
Image: "repo/image:tag",
PortMappings: []PortMappings{
{Labels: labels},
{Labels: make(map[string]string)},
},
}
container = Container{Docker: docker}
app = App{
ID: "test-service",
Tasks: []Task{task},
RunningTasks: runningTasks,
Labels: labels,
Container: container,
PortDefinitions: []PortDefinitions{
{Labels: make(map[string]string)},
{Labels: labels},
},
}
)
return &AppList{
Apps: []App{app},
}
}
func TestMarathonSDSendGroupWithMutiplePort(t *testing.T) {
var (
ch = make(chan []*config.TargetGroup, 1)
client = func(client *http.Client, url, token string) (*AppList, error) {
return marathonTestAppListWithMutiplePorts(marathonValidLabel, 1), nil
}
)
if err := testUpdateServices(client, ch); err != nil {
t.Fatalf("Got error: %s", err)
}
select {
case tgs := <-ch:
tg := tgs[0]
if tg.Source != "test-service" {
t.Fatalf("Wrong target group name: %s", tg.Source)
}
if len(tg.Targets) != 2 {
t.Fatalf("Wrong number of targets: %v", tg.Targets)
}
tgt := tg.Targets[0]
if tgt[model.AddressLabel] != "mesos-slave1:31000" {
t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel])
}
if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "yes" {
t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel])
}
if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" {
t.Fatalf("Wrong first portDefinitions label from the first port: %s", tgt[model.AddressLabel])
}
tgt = tg.Targets[1]
if tgt[model.AddressLabel] != "mesos-slave1:32000" {
t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel])
}
if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" {
t.Fatalf("Wrong portMappings label from the second port: %s", tgt[model.AddressLabel])
}
if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "yes" {
t.Fatalf("Wrong portDefinitions label from the second port: %s", tgt[model.AddressLabel])
}
default:
t.Fatal("Did not get a target group.")
}
}
func marathonTestZeroTaskPortAppList(labels map[string]string, runningTasks int) *AppList { func marathonTestZeroTaskPortAppList(labels map[string]string, runningTasks int) *AppList {
var ( var (
task = Task{ task = Task{
@ -235,3 +326,149 @@ func TestMarathonZeroTaskPorts(t *testing.T) {
t.Fatal("Did not get a target group.") t.Fatal("Did not get a target group.")
} }
} }
func marathonTestAppListWithoutPortMappings(labels map[string]string, runningTasks int) *AppList {
var (
task = Task{
ID: "test-task-1",
Host: "mesos-slave1",
Ports: []uint32{31000, 32000},
}
docker = DockerContainer{
Image: "repo/image:tag",
}
container = Container{Docker: docker}
app = App{
ID: "test-service",
Tasks: []Task{task},
RunningTasks: runningTasks,
Labels: labels,
Container: container,
PortDefinitions: []PortDefinitions{
{Labels: make(map[string]string)},
{Labels: labels},
},
}
)
return &AppList{
Apps: []App{app},
}
}
func TestMarathonSDSendGroupWithoutPortMappings(t *testing.T) {
var (
ch = make(chan []*config.TargetGroup, 1)
client = func(client *http.Client, url, token string) (*AppList, error) {
return marathonTestAppListWithoutPortMappings(marathonValidLabel, 1), nil
}
)
if err := testUpdateServices(client, ch); err != nil {
t.Fatalf("Got error: %s", err)
}
select {
case tgs := <-ch:
tg := tgs[0]
if tg.Source != "test-service" {
t.Fatalf("Wrong target group name: %s", tg.Source)
}
if len(tg.Targets) != 2 {
t.Fatalf("Wrong number of targets: %v", tg.Targets)
}
tgt := tg.Targets[0]
if tgt[model.AddressLabel] != "mesos-slave1:31000" {
t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel])
}
if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" {
t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel])
}
if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" {
t.Fatalf("Wrong first portDefinitions label from the first port: %s", tgt[model.AddressLabel])
}
tgt = tg.Targets[1]
if tgt[model.AddressLabel] != "mesos-slave1:32000" {
t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel])
}
if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" {
t.Fatalf("Wrong portMappings label from the second port: %s", tgt[model.AddressLabel])
}
if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "yes" {
t.Fatalf("Wrong portDefinitions label from the second port: %s", tgt[model.AddressLabel])
}
default:
t.Fatal("Did not get a target group.")
}
}
func marathonTestAppListWithoutPortDefinitions(labels map[string]string, runningTasks int) *AppList {
var (
task = Task{
ID: "test-task-1",
Host: "mesos-slave1",
Ports: []uint32{31000, 32000},
}
docker = DockerContainer{
Image: "repo/image:tag",
PortMappings: []PortMappings{
{Labels: labels},
{Labels: make(map[string]string)},
},
}
container = Container{Docker: docker}
app = App{
ID: "test-service",
Tasks: []Task{task},
RunningTasks: runningTasks,
Labels: labels,
Container: container,
}
)
return &AppList{
Apps: []App{app},
}
}
func TestMarathonSDSendGroupWithoutPortDefinitions(t *testing.T) {
var (
ch = make(chan []*config.TargetGroup, 1)
client = func(client *http.Client, url, token string) (*AppList, error) {
return marathonTestAppListWithoutPortDefinitions(marathonValidLabel, 1), nil
}
)
if err := testUpdateServices(client, ch); err != nil {
t.Fatalf("Got error: %s", err)
}
select {
case tgs := <-ch:
tg := tgs[0]
if tg.Source != "test-service" {
t.Fatalf("Wrong target group name: %s", tg.Source)
}
if len(tg.Targets) != 2 {
t.Fatalf("Wrong number of targets: %v", tg.Targets)
}
tgt := tg.Targets[0]
if tgt[model.AddressLabel] != "mesos-slave1:31000" {
t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel])
}
if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "yes" {
t.Fatalf("Wrong first portMappings label from the first port: %s", tgt[model.AddressLabel])
}
if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" {
t.Fatalf("Wrong first portDefinitions label from the first port: %s", tgt[model.AddressLabel])
}
tgt = tg.Targets[1]
if tgt[model.AddressLabel] != "mesos-slave1:32000" {
t.Fatalf("Wrong target address: %s", tgt[model.AddressLabel])
}
if tgt[model.LabelName(portMappingLabelPrefix+"prometheus")] != "" {
t.Fatalf("Wrong portMappings label from the second port: %s", tgt[model.AddressLabel])
}
if tgt[model.LabelName(portDefinitionLabelPrefix+"prometheus")] != "" {
t.Fatalf("Wrong portDefinitions label from the second port: %s", tgt[model.AddressLabel])
}
default:
t.Fatal("Did not get a target group.")
}
}