Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rmirica/add port mapping #119

Merged
merged 4 commits into from
Sep 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ curl -H "Content-Type: application/json" \
http://eremetic_server:8080/task
```

These basic fields are required but you can also specify volumes, environment
These basic fields are required but you can also specify volumes, ports, environment
variables, and URIs for the mesos fetcher to download. See
[examples.md](examples.md) for more examples on how to use eremetic.

Expand All @@ -44,6 +44,13 @@ JSON format:
"host_path": "/var/run/docker.sock"
}
],
// Array of Objects, ports to forward to the container
"ports": [
{
"container_port": 80,
"protocol": "tcp"
}
],
// Object, Environment variables to pass to the container
"env": {
"KEY": "value"
Expand Down
1 change: 1 addition & 0 deletions scheduler/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ func (s *eremeticScheduler) ScheduleTask(request types.Request) (string, error)
"docker_image": request.DockerImage,
"command": request.Command,
"slave_constraints": request.SlaveConstraints,
"ports": request.Ports,
}).Debug("Adding task to queue")

task, err := types.NewEremeticTask(request, fmt.Sprintf("Eremetic task %s", nextID(s)))
Expand Down
60 changes: 60 additions & 0 deletions scheduler/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ func createTaskInfo(task types.EremeticTask, offer *mesos.Offer) (types.Eremetic
task.AgentIP = offer.GetUrl().GetAddress().GetIp()
task.AgentPort = offer.GetUrl().GetAddress().GetPort()

portMapping, portResources := buildPorts(task, offer)

taskInfo := &mesos.TaskInfo{
TaskId: &mesos.TaskID{Value: proto.String(task.ID)},
SlaveId: offer.SlaveId,
Expand All @@ -24,12 +26,15 @@ func createTaskInfo(task types.EremeticTask, offer *mesos.Offer) (types.Eremetic
Docker: &mesos.ContainerInfo_DockerInfo{
Image: proto.String(task.Image),
ForcePullImage: proto.Bool(task.ForcePullImage),
PortMappings: portMapping,
Network: mesos.ContainerInfo_DockerInfo_BRIDGE.Enum(),
},
Volumes: buildVolumes(task),
},
Resources: []*mesos.Resource{
mesosutil.NewScalarResource("cpus", task.TaskCPUs),
mesosutil.NewScalarResource("mem", task.TaskMem),
mesosutil.NewRangesResource("ports", portResources),
},
}
return task, taskInfo
Expand Down Expand Up @@ -71,6 +76,61 @@ func buildVolumes(task types.EremeticTask) []*mesos.Volume {
return volumes
}

func buildPorts(task types.EremeticTask, offer *mesos.Offer) ([]*mesos.ContainerInfo_DockerInfo_PortMapping, []*mesos.Value_Range) {
var portResources []*mesos.Value_Range
var portMapping []*mesos.ContainerInfo_DockerInfo_PortMapping

if(len(task.Ports) > 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a quite big block of code to inline in this function we should try to break it up.

For an initial suggestion it could be broken down into these steps (which each could be their own function)

  • a) Get N ports from an offer
  • b) Construct portMappings from the ports definition and a list of ports
  • c) Construct ranges resource of ports from list of ports

as an added bonus a could also be reused to implement the matcher

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem I had was with C... if it's not done in the same context as A it may lead to non-overlapping ranges (ie: you get offer (3001:3003),(3007:3010) and you need 5 ports, the list will be [3001, 2, 3, 7, 8] and the resulting range (3001, 3008) which will violate the offer)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course a helper function that finds contiguous sequences in a list would do...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. that's true. However reconstructing that into two ranges by finding sequences should be doable. There's also the really simple method of mapping that into 5 1-length ranges like (3001-3001, 3002-3002, ...) which I think would still be legal?

lastIndex := len(task.Ports)

for _, v := range offer.Resources {
if(lastIndex == 0) {
break
}

if(*v.Name != "ports") {
continue
}

for _, p_v := range v.Ranges.Range {
if(lastIndex == 0) {
break
}

startPort, endPort := *p_v.Begin, int(*p_v.Begin)
for portnumber := int(*p_v.Begin); portnumber <= int(*p_v.End); portnumber++ {
if(lastIndex == 0) {
break
}

lastIndex--
ask_port := &task.Ports[lastIndex]

if(ask_port.ContainerPort == 0) {
continue
}

endPort = portnumber + 1

ask_port.HostPort = uint32(portnumber)

portMapping = append(portMapping, &mesos.ContainerInfo_DockerInfo_PortMapping{
ContainerPort: proto.Uint32(ask_port.ContainerPort),
HostPort: proto.Uint32(ask_port.HostPort),
Protocol: proto.String(ask_port.Protocol),
})

}
if(int(startPort) != endPort) {
portResources = append(portResources, mesosutil.NewValueRange(startPort,uint64(endPort)))
}
}
}
}

return portMapping, portResources
}

func buildURIs(task types.EremeticTask) []*mesos.CommandInfo_URI {
var uris []*mesos.CommandInfo_URI
for _, v := range task.FetchURIs {
Expand Down
35 changes: 35 additions & 0 deletions scheduler/task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/golang/protobuf/proto"
"github.com/klarna/eremetic/types"
mesos "github.com/mesos/mesos-go/mesosproto"
"github.com/mesos/mesos-go/mesosutil"
. "github.com/smartystreets/goconvey/convey"
)

Expand All @@ -30,6 +31,8 @@ func TestTask(t *testing.T) {
Name: "Eremetic task 17",
}


portres := "ports"
offer := mesos.Offer{
FrameworkId: &mesos.FrameworkID{
Value: proto.String("framework-id"),
Expand All @@ -38,6 +41,15 @@ func TestTask(t *testing.T) {
Value: proto.String("slave-id"),
},
Hostname: proto.String("hostname"),
Resources: []*mesos.Resource{&mesos.Resource{
Name: &portres,
Type: mesos.Value_RANGES.Enum(),
Ranges: &mesos.Value_Ranges{
Range: []*mesos.Value_Range{
mesosutil.NewValueRange(31000, 31010),
},
},
}},
}

Convey("No volume or environment specified", func() {
Expand Down Expand Up @@ -85,6 +97,29 @@ func TestTask(t *testing.T) {
So(taskInfo.Command.Environment.Variables[1].GetValue(), ShouldEqual, eremeticTask.ID)
})

Convey("Given a portMapping", func() {
var ports []types.Port

ports = append(ports,
types.Port{
ContainerPort: 80,
Protocol: "tcp",
},
)

eremeticTask.Ports = ports

_, taskInfo := createTaskInfo(eremeticTask, &offer)

So(len(taskInfo.Container.Docker.PortMappings), ShouldEqual, 1)
So(taskInfo.Container.Docker.GetPortMappings()[0].GetContainerPort(), ShouldEqual, ports[0].ContainerPort)
So(taskInfo.GetResources()[2].GetName(), ShouldEqual, "ports")

expected_range := mesosutil.NewValueRange(31000, 31001)
So(taskInfo.GetResources()[2].GetRanges().GetRange()[0].GetBegin(), ShouldEqual, expected_range.GetBegin())
So(taskInfo.GetResources()[2].GetRanges().GetRange()[0].GetEnd(), ShouldEqual, expected_range.GetEnd())
})

Convey("Given archive to fetch", func() {
URI := []types.URI{types.URI{
URI: "http://foobar.local/cats.zip",
Expand Down
2 changes: 2 additions & 0 deletions static/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@
.volumes .field input,
.env .field input,
.uri .field input,
.ports .field input,
.ports .field select,
.slave_constraints .field input {
width: auto !important;
}
Expand Down
38 changes: 38 additions & 0 deletions static/js/eremetic.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ $(document).ready(function() {
}, []);
}

if (typeof json.ports !== "undefined") {
json.ports = json.ports.reduce(function(collector, element) {
collector.push({ 'container_port': parseInt(element.container_port), 'protocol': element.protocol });
return collector;
}, []);
}

json.task_cpus = parseFloat(json.task_cpus);
json.task_mem = parseFloat(json.task_mem);

Expand Down Expand Up @@ -143,6 +150,36 @@ $(document).ready(function() {

}

function addPorts(e) {
var $cont = $('#ports')
, index = $cont.data('count') + 1
, $input
;

e.preventDefault();

$input = $(
'<div class="field ui action input ports">' +
'<div class="field">' +
'<input name="ports[' + index + '][container_port]" placeholder="Container Port" type="number"/>' +
'</div>' +
'<div class="field">' +
'<select name="ports[' + index + '][protocol]">' +
'<option value="tcp" selected="selected">tcp</option>' +
'<option value="udp">udp</option>' +
'</select>' +
'</div>' +
'&nbsp;<button class="ui icon button">' +
'<i class="minus red icon"></i>' +
'</button>' +
'</div>'
);

$cont.append($input);
$cont.data('count', index);

}

function addEnvironments(e) {
var $cont = $('#env')
, index = $cont.data('count') + 1
Expand Down Expand Up @@ -200,6 +237,7 @@ $(document).ready(function() {
$('#new_task').on('submit', submitHandler);
$('#new_task #submit').on('click', submitHandler);
$('#new_task #volumes .plus').on('click', addVolumes);
$('#new_task #ports .plus').on('click', addPorts);
$('#new_task #env .plus').on('click', addEnvironments);
$('#new_task #uris .plus').on('click', addURIs);
$('#new_task #slave_constraints .plus').on('click', addSlaveConstraints);
Expand Down
10 changes: 10 additions & 0 deletions templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ <h4 class="ui dividing header">Optional</h4>
</div>
</div>
</div>
<div class="field">
<div class="two fields">
<div class="field" id="ports" data-count=0>
<label>
Ports
<i class="plus icon green add"></i>
</label>
</div>
</div>
</div>
<div class="ui buttons right floated">
<button class="ui button" id="cancel">Cancel</button>
<div class="or"></div>
Expand Down
1 change: 1 addition & 0 deletions types/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type Request struct {
DockerImage string `json:"docker_image"`
Command string `json:"command"`
Volumes []Volume `json:"volumes"`
Ports []Port `json:"ports"`
Environment map[string]string `json:"env"`
MaskedEnvironment map[string]string `json:"masked_env"`
SlaveConstraints []SlaveConstraint `json:"slave_constraints"`
Expand Down
8 changes: 8 additions & 0 deletions types/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ type Volume struct {
HostPath string `json:"host_path"`
}

type Port struct {
ContainerPort uint32 `json:"container_port"`
HostPort uint32 `json:"host_port"`
Protocol string `json:"protocol"`
}

type SlaveConstraint struct {
AttributeName string `json:"attribute_name"`
AttributeValue string `json:"attribute_value"`
Expand All @@ -41,6 +47,7 @@ type EremeticTask struct {
MaskedEnvironment map[string]string `json:"masked_env"`
Image string `json:"image"`
Volumes []Volume `json:"volumes"`
Ports []Port `json:"ports"`
Status []Status `json:"status"`
ID string `json:"id"`
Name string `json:"name"`
Expand Down Expand Up @@ -116,6 +123,7 @@ func NewEremeticTask(request Request, name string) (EremeticTask, error) {
SlaveConstraints: request.SlaveConstraints,
Image: request.DockerImage,
Volumes: request.Volumes,
Ports: request.Ports,
CallbackURI: request.CallbackURI,
ForcePullImage: request.ForcePullImage,
FetchURIs: mergeURIs(request),
Expand Down